c#Socket学习,使用Socket创建一个在线聊天,日志笔记(5)

c#Socket学习,使用Socket创建一个在线聊天,日志笔记(5)

socket是基于TCP/IP 协议的一个实现。TCP/IP不是具体的东西,不能通过代码调用,socket实现了tcp/ip,

Socket 不是协议,而是操作系统提供的编程接口(API)。

Socket和HTTP,Websocket,MQTT都是基于tcp/ip的协议。

Socket属于tcp/ip协议中的,介于应用层和传输层中的一个实现,http是应用层的实现。

Socket实现跨进程/主机的网络通信。

提供可靠的字节流传输TCP或者无链接的数据报传输UDP。

网络通信只有两类:可靠字节流(TCP,SOCK_STREAM)+ 尽最大努力数据报(UDP,SOCK_DGRAM)。

  • SOCK_STREAM: 流式, TCP
  • SOCK_DGRAM: 广播, UDP
  • SOCK_RAW: 原始协议

OSI七层模型

  • 应用层:如HTTP, FTP, WebSocket等。
  • 表示层:负责数据格式化、加密解密等。
  • 会话层:管理应用程序之间的会话。
  • 传输层:TCP,UDP。
  • 网络层:IP,ICMP等。
  • 数据链路层:以太网,网卡等。
  • 物理层:物理线路,光纤等。

OSI五层模型

  • 应用层:整合了OSI模型中的应用层、表示层和会话层。
  • 传输层:TCP,UDP。
  • 网络层:IP,ICMP等。
  • 数据链路层:网卡,交换机等。
  • 物理层:物理媒体,如光纤。

粘包/分包

粘包:多个数据包被合并成一个大的数据包发送或接收

分包:一个数据包被拆分成多个小的数据包发送或接收

长度前缀法(最常用,示例项目中也是这么用的)

csharp 复制代码
// 发送
public static void SendMessage(NetworkStream stream, string message)
{
    byte[] data = Encoding.UTF8.GetBytes(message);
    byte[] lengthBytes = BitConverter.GetBytes(data.Length);
    
    // 确保网络字节序(大端)
    if (BitConverter.IsLittleEndian)
    {
        Array.Reverse(lengthBytes);
    }
    
    // 发送长度前缀
    stream.Write(lengthBytes, 0, 4);
    // 发送数据
    stream.Write(data, 0, data.Length);
}

// 接收
public static string ReceiveMessage(NetworkStream stream)
{
    byte[] lengthBytes = new byte[4];
    int bytesRead = stream.Read(lengthBytes, 0, 4);
    if (bytesRead != 4) return null;
    
    // 转换为主机字节序
    if (BitConverter.IsLittleEndian)
    {
        Array.Reverse(lengthBytes);
    }
    int length = BitConverter.ToInt32(lengthBytes, 0);
    
    // 接收数据
    byte[] buffer = new byte[length];
    bytesRead = 0;
    while (bytesRead < length)
    {
        int read = stream.Read(buffer, bytesRead, length - bytesRead);
        if (read == 0) return null;
        bytesRead += read;
    }
    
    return Encoding.UTF8.GetString(buffer);
}

分隔符法

使用 \r\n\r\n,假设数据中有这个怎么办呢?

或者自定义--END--

A5 22 33 44 5A

使用A5作为数据开头,5A作为数据结尾,缺点就是传输的数据里面可能有数据开头或者结尾,这时候需要进行转移,将数据中的结束位和开始位进行转义。

csharp 复制代码
// 发送
void SendWithDelimiter(Socket socket, string message)
{
    byte[] data = Encoding.UTF8.GetBytes(message + "|END|");
    socket.Send(data);
}

// 接收
string ReceiveByDelimiter(Socket socket)
{
    byte[] buffer = new byte[1024];
    int bytesRead = socket.Receive(buffer);
    string data = Encoding.UTF8.GetString(buffer, 0, bytesRead);
    
    int endIndex = data.IndexOf("|END|");
    return endIndex >= 0 ? data.Substring(0, endIndex) : data;
}

固定长度法

适合固定格式的协议,每次传输的协议长度固定。

每次都发送长度是一样的。

A5 11 22 33 44 5A

每次都发送相同长度的数据,解析的时候,根据不同的位进行解析。例如11是操作符,不同的操作符对数据进行不同的解析。

例如,不同的数据使用使用不同的解析方式,然后空位使用FF 补充,这个需要双方约定。

A5 12 22 43 FF 5A

A5 13 22 FF FF 5A

csharp 复制代码
// 发送
void SendFixedLength(Socket socket, string message)
{
    byte[] data = Encoding.UTF8.GetBytes(message.PadRight(FIXED_LENGTH, '\0'));
    socket.Send(data);
}

// 接收
string ReceiveFixedLength(Socket socket)
{
    byte[] buffer = new byte[FIXED_LENGTH];
    int totalRead = 0;
    
    while (totalRead < FIXED_LENGTH)
    {
        int bytesRead = socket.Receive(buffer, totalRead, FIXED_LENGTH - totalRead, SocketFlags.None);
        if (bytesRead == 0) break;
        totalRead += bytesRead;
    }
    
    return Encoding.UTF8.GetString(buffer).TrimEnd('\0');
}

Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
clientSocket.Connect("127.0.0.1", 8888);

// 发送消息
SendWithDelimiter(clientSocket, "Hello Server");
SendFixedLength(clientSocket, "Fixed Data");

连接

Socket ≈ 打电话(建立连接后可以直接说话)

HTTP ≈ 发邮件(一发一收,每次都要写格式)

WebSocket ≈ 打电话,但先发短信确认身份,然后畅聊

socket连接(tcp)

四次握手三次挥手

text 复制代码
客户端                服务器
  |----SYN----->|
  |<---SYN+ACK--|
  |----ACK----->|
  
  连接建立,双向通信
  
  |----FIN----->|
  |<---ACK------|
  |<---FIN------|
  |----ACK----->|
  • 保持长连接
  • 双向实时通信
  • 需要手动管理连接状态

http连接

过程:TCP三次握手 → HTTP请求 → HTTP响应 → TCP四次挥手

四次握手三次挥手,因为http是基于socket的,所以也需要实现,请求的时候四次握手,然后发送数据,获取到数据之后,挥手完成请求。

复制代码
客户端                服务器
  |---TCP握手--->|(三次握手)
  |---GET/POST-->|(HTTP请求头+体)
  |<---200 OK----|(HTTP响应头+体)
  |---TCP挥手--->|(四次挥手)
  
  每次请求都重复此过程(HTTP/1.0)
  • 短连接(HTTP/1.0)或长连接(HTTP/1.1 Keep-Alive)
  • 客户端主动发起,服务器响应
  • 无状态,每个请求独立

websocket连接

过程:HTTP握手 → 协议升级 → WebSocket通信

四次握手三次挥手,因为http是基于socket的,所以也需要实现,请求的时候四次握手,然后发送数据,获取到数据之后,确认up头,更新完成成之后转成socket通信。

text 复制代码
 客户端                服务器
  |---TCP握手--->|    // 1. 底层TCP三次握手建立连接
  |               |
  |---HTTP握手--->|   // 2. WebSocket握手(HTTP协议)
  |<--HTTP响应----|   //   服务器响应升级协议
  |               |
  |===WebSocket===>|  // 3. 升级为WebSocket协议通信
  |<==双向数据=====|  //    全双工数据传输
  |               |
  |--Close帧----->|   // 4. WebSocket关闭握手
  |<--Close帧-----|   //    双向确认关闭
  |               |
  |---TCP挥手--->|    // 5. 底层TCP四次挥手断开连接
csharp 复制代码
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==  // 随机base64编码的16字节值
Sec-WebSocket-Version: 13
Origin: http://example.com
复制代码
Upgrade: websocket - 请求升级协议
Connection: Upgrade - 连接升级
Sec-WebSocket-Key - 随机密钥,用于安全性验证
Sec-WebSocket-Version: 13 - 协议版本

mqtt连接

过程:TCP连接 → MQTT握手 → 发布/订阅

text 复制代码
客户端                服务器
  |---TCP SYN--->|   // 1. TCP三次握手
  |<--SYN+ACK----|
  |---TCP ACK--->|
  
  |--CONNECT---->|   // 2. MQTT连接请求
  |<-CONNACK-----|   // 3. 连接确认
  
  |===通信阶段===|   // 发布/订阅消息
  
  |--DISCONNECT->|   // 4a. 优雅断开(MQTT层)
  |               |
  |---TCP FIN--->|   // 5. TCP四次挥手(传输层)
  |<---TCP ACK---|
  |<---TCP FIN---|
  |---TCP ACK--->|
  • 轻量级,适合IoT设备
  • 基于发布/订阅模式
  • 支持QoS(服务质量等级)
  • 心跳机制保持连接

心跳

心跳(Heartbeat) 是客户端定期向服务器发送的小数据包,目的是:

  1. 保活连接:告诉服务器"我还活着"
  2. 检测连接状态:及时发现断开的连接
  3. 防止超时断开:避免防火墙/NAT超时清理连接
  4. 网络质量检测:通过响应时间判断网络状况
text 复制代码
客户端                服务器
  |---心跳包---->|  // 定期发送
  |<---响应------|  // 服务器确认
  (等待一段时间)
  |---心跳包---->|  // 再次发送
  |<---响应------|
  ...循环...
csharp 复制代码
using System;
using System.Net.Sockets;
using System.Text;
using System.Threading;

public class HeartbeatManager
{
    private Socket socket;
    private Timer heartbeatTimer;
    private DateTime lastResponseTime;
    private Thread receiveThread;
    
    public void StartHeartbeat()
    {
        // 每30秒发送一次心跳
        heartbeatTimer = new Timer(SendHeartbeat, null, 0, 30000);
        
        // 启动接收线程
        receiveThread = new Thread(ReceiveMessages);
        receiveThread.IsBackground = true;
        receiveThread.Start();
    }
    
    private void SendHeartbeat(object state)
    {
        try
        {
            // 发送心跳包
            byte[] heartbeat = Encoding.UTF8.GetBytes("HEARTBEAT");
            socket.Send(heartbeat);
            
            // 检查上次响应时间
            if ((DateTime.Now - lastResponseTime).TotalSeconds > 90)
            {
                Reconnect();  // 90秒没响应,重连
            }
        }
        catch (SocketException)
        {
            Reconnect();  // 发送失败,重连
        }
    }
    
    private void ReceiveMessages()
    {
        byte[] buffer = new byte[1024];
        
        while (true)
        {
            try
            {
                int bytesRead = socket.Receive(buffer);
                if (bytesRead > 0)
                {
                    string message = Encoding.UTF8.GetString(buffer, 0, bytesRead);
                    
                    if (message == "HEARTBEAT_RESPONSE")
                    {
                        OnHeartbeatResponse();  // 心跳响应
                    }
                    else
                    {
                        ProcessMessage(message);  // 业务消息
                    }
                }
            }
            catch (SocketException)
            {
                break;  // 连接异常,退出接收循环
            }
        }
    }
    
    public void OnHeartbeatResponse()
    {
        lastResponseTime = DateTime.Now;  // 更新响应时间
    }
    
    private void ProcessMessage(string message)
    {
        lastResponseTime = DateTime.Now;  // 收到任何消息都更新响应时间
        // 处理业务逻辑...
    }
    
    private void Reconnect()
    {
        // 重连逻辑...
    }
}

大端/小端

在网络传输中统一使用大端传输。

什么是高低位字?

text 复制代码
32位整数:0x12345678
高位字(High Word):0x1234(高16位)
低位字(Low Word):0x5678(低16位)

64位整数:0x123456789ABCDEF0
高位双字(High Dword):0x12345678(高32位)
低位双字(Low Dword):0x9ABCDEF0(低32位)

大端(Big Endian)

复制代码
高位字节在低地址,低位字节在高地址
就像正常阅读数字:0x12345678 → 内存:12 34 56 78
网络字节序标准

bool isLittleEndian = BitConverter.IsLittleEndian;

Console.WriteLine($"系统是端序.true大,false小: {isLittleEndian}");

小端(Little Endian)

复制代码
低位字节在低地址,高位字节在高地址
x86架构默认:0x12345678 → 内存:78 56 34 12

bool isLittleEndian = BitConverter.IsLittleEndian;
Console.WriteLine($"系统是端序.true大,false小: {isLittleEndian}");

转换方式

csharp 复制代码
using System.Net;

// 系统提供的转换(注意:只支持short/int)
short hostShort = 0x1234;
short networkShort = IPAddress.HostToNetworkOrder(hostShort);
short backToHost = IPAddress.NetworkToHostOrder(networkShort);

int hostInt = 0x12345678;
int networkInt = IPAddress.HostToNetworkOrder(hostInt);
int backToInt = IPAddress.NetworkToHostOrder(networkInt);

有状态和无状态

有状态(statelful)

一个有状态的系统会在执行某个操作时,将当前操作的上下文和状态记录下来。这些上下文和状态信息可以用来支持更复杂的操作,比如说处理多个请求,或者在不同的时间点上执行一系列的操作。在这种系统中,用户的每个请求都会被认为是不同的,并且需要针对每个请求单独维护状态信息。就像是AI 一样会联系上下文。用户的多个请求是相关联的,服务器能识别这是同一个用户的连续请求。

例如

  • 传统Session(如PHP Session、Tomcat Session)
  • TCP连接
  • 数据库连接池
  • SSH连接

无状态(stateless)

相反,一个无状态的系统不会维护任何状态信息,它会处理每个请求并给出一个结果。在这种系统中,所有请求都是相同的,并且没有任何请求在上下文上具有优劣之分。这种系统通常更加简单和可扩展,因为它不需要维护额外的状态信息。不会联系上下文,每次请求都是一个独立的请求。

例如

  • RESTful API(理想情况)
  • HTTP协议(本身是无状态的)
  • 静态文件服务器

现实中的混合使用

HTTP的无状态 + 状态管理技巧

text 复制代码
客户端请求 → 服务器生成Session ID → 存状态到服务器 → 返回Session ID
后续请求携带Session ID → 服务器读取对应状态
JWT(JSON Web Token):
复制代码
// 无状态认证示例
// 请求头中携带自包含的token
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
// token包含所有必要信息,服务器无需存储状态

其他

魔数(Magic Number)

在计算机领域,"魔数"(Magic Number)通常指用于标识文件格式、协议类型或数据结构的特定字节序列。它是一种约定俗成的"签名",用于快速判断一段数据是否符合预期格式。

在编程中,"魔数"一词也有另一层含义------指代码中出现但未加解释的硬编码数字或字符串(如 if (status == 42)),这类"魔法值"应尽量避免,推荐使用具名常量代替。

在自定义通信协议时,我们也可以在消息头部加入一个固定的"魔数",用于校验消息合法性、防止误解析或抵御非法连接。

文件格式魔数
文件格式 魔数值(十六进制) ASCII表示 说明
JPEG图像 FF D8 ÿØ 文件开头两个字节标识JPEG格式
PNG图像 89 50 4E 47 0D 0A 1A 0A .PNG.... 八个字节的固定签名
GIF图像 47 49 46 38 39 61 GIF89a GIF89a格式
47 49 46 38 37 61 GIF87a GIF87a格式
ZIP压缩文件 50 4B 03 04 PK.. ZIP文件标准签名
PDF文档 25 50 44 46 2D %PDF- PDF文件开头标识
BMP图像 42 4D BM Windows位图文件
ELF可执行文件 7F 45 4C 46 .ELF Unix/Linux可执行文件格式
数据结构魔数
结构类型 魔数值 说明
Java类文件 0xCAFEBABE Java虚拟机识别.class文件的标志
Mach-O可执行文件 0xFEEDFACE 32位macOS可执行文件
0xFEEDFACF 64位macOS可执行文件
SQLite数据库 53 51 4C 69 74 65 20 66 6F 72 6D 61 74 20 33 00 SQLite format 3 + null终止符
特定条件魔数
场景 位置 魔数值 作用
x86 MBR引导扇区 第511-512字节 0x55 0xAA 标识有效的主引导记录
FAT文件系统引导扇区 偏移0x1FE-0x1FF 0x55 0xAA 标识有效的引导扇区
Java对象序列化流 开头四个字节 0xACED0005 Java序列化流的起始标识
编程语言和框架魔数
类型 示例值 说明
Python .pyc文件 0x16 0x0D 0x0D 0x0A Python字节码文件的魔数,用于版本兼容性检查
协议魔数
协议 魔数/标识 说明
HTTP协议 HTTP/ 响应开头标识协议版本,如HTTP/1.1
TLS/SSL协议 0x16 0x03 ClientHello消息的前两个字节,标识TLS版本和握手类型
相关推荐
峰顶听歌的鲸鱼2 小时前
20.MySql数据库
运维·数据库·笔记·mysql·云计算·学习方法
_Kayo_2 小时前
css 练习笔记1
前端·css·笔记
崇山峻岭之间2 小时前
Matlab学习记录14
开发语言·学习·matlab
embrace992 小时前
【数据结构学习】数据结构和算法
c语言·数据结构·c++·学习·算法·链表·哈希算法
峰顶听歌的鲸鱼2 小时前
19.docker 图形化管理界面
运维·笔记·docker·容器·学习方法
Lynnxiaowen2 小时前
今天我们开始学习腾讯云产品介绍及功能概述与应用场景
学习·云计算·腾讯云
程序猿零零漆2 小时前
Spring之旅 - 记录学习 Spring 框架的过程和经验(五)Spring的后处理器BeanFactoryPostProcessor
java·学习·spring
AI360labs_atyun2 小时前
OpenAI应用商店,试试用它写年终PPT!
人工智能·科技·学习·ai·chatgpt·powerpoint
love530love2 小时前
【笔记】把已有的 ComfyUI 插件发布到 Comfy Registry(官方节点商店)全流程实录
人工智能·windows·笔记·python·aigc·comfyui·torchmonitor