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等。
  • 数据链路层:网卡,交换机等。
  • 物理层:物理媒体,如光纤。

粘包/分包

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

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

// 发送
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作为数据结尾,缺点就是传输的数据里面可能有数据开头或者结尾,这时候需要进行转移,将数据中的结束位和开始位进行转义。


// 发送
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

// 发送
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)

四次握手三次挥手

客户端                服务器
  |----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通信。

 客户端                服务器
  |---TCP握手--->|    // 1. 底层TCP三次握手建立连接
  |               |
  |---HTTP握手--->|   // 2. WebSocket握手(HTTP协议)
  |<--HTTP响应----|   //   服务器响应升级协议
  |               |
  |===WebSocket===>|  // 3. 升级为WebSocket协议通信
  |<==双向数据=====|  //    全双工数据传输
  |               |
  |--Close帧----->|   // 4. WebSocket关闭握手
  |<--Close帧-----|   //    双向确认关闭
  |               |
  |---TCP挥手--->|    // 5. 底层TCP四次挥手断开连接
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握手 → 发布/订阅

客户端                服务器
  |---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. 网络质量检测:通过响应时间判断网络状况
客户端                服务器
  |---心跳包---->|  // 定期发送
  |<---响应------|  // 服务器确认
  (等待一段时间)
  |---心跳包---->|  // 再次发送
  |<---响应------|
  ...循环...
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()
    {
        // 重连逻辑...
    }
}

大端/小端

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

什么是高低位字?

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}");

转换方式

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的无状态 + 状态管理技巧

Cookie-Session机制:
客户端请求 → 服务器生成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 61GIF89aGIF89a格式
47 49 46 38 37 61GIF87aGIF87a格式
ZIP压缩文件50 4B 03 04PK..ZIP文件标准签名
PDF文档25 50 44 46 2D%PDF-PDF文件开头标识
BMP图像42 4DBMWindows位图文件
ELF可执行文件7F 45 4C 46.ELFUnix/Linux可执行文件格式
数据结构魔数
结构类型魔数值说明
Java类文件0xCAFEBABEJava虚拟机识别.class文件的标志
Mach-O可执行文件0xFEEDFACE32位macOS可执行文件
0xFEEDFACF64位macOS可执行文件
SQLite数据库53 51 4C 69 74 65 20 66 6F 72 6D 61 74 20 33 00SQLite format 3 + null终止符
特定条件魔数
场景位置魔数值作用
x86 MBR引导扇区第511-512字节0x55 0xAA标识有效的主引导记录
FAT文件系统引导扇区偏移0x1FE-0x1FF0x55 0xAA标识有效的引导扇区
Java对象序列化流开头四个字节0xACED0005Java序列化流的起始标识
编程语言和框架魔数
类型示例值说明
Python .pyc文件0x16 0x0D 0x0D 0x0APython字节码文件的魔数,用于版本兼容性检查
协议魔数
协议魔数/标识说明
HTTP协议HTTP/响应开头标识协议版本,如HTTP/1.1
TLS/SSL协议0x16 0x03ClientHello消息的前两个字节,标识TLS版本和握手类型
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值