基于C#的通信过程与协议实操需要

在工业控制、物联网、游戏后端、金融交易等场景中,稳定、高效、可扩展的通信能力 是系统的核心。而 C# 凭借 System.Net.Sockets 和异步编程模型,成为构建高性能通信服务的理想选择。

但很多开发者只停留在 TcpClient.Connect()NetworkStream.Write() 的表面用法,一旦遇到粘包、断连重连、协议解析错误等问题就束手无策。

本文将带你从通信本质出发,设计一个轻量级应用层协议,并用 C# 实现完整的 TCP 客户端与服务器,涵盖:

  • 自定义二进制协议设计
  • 粘包/半包处理
  • 异步收发
  • 心跳保活
  • 消息回调机制

所有代码可直接运行,适用于工控设备对接、私有协议网关、游戏信令服务器等副业或企业项目。


一、通信核心问题:为什么需要自定义协议?

TCP 是字节流协议 ,不保证消息边界。例如你发送两条消息 "Hello""World",接收方可能一次收到 "HelloWorld"(粘包),也可能分三次收到 "He", "lloW", "orld"(半包)。

因此,必须在应用层定义消息帧结构。我们设计如下协议:

scss 复制代码
Text
编辑
1| Magic (2B) | Length (4B) | MsgID (2B) | Payload (Length-6B) |
  • Magic : 固定值 0x55AA,用于识别合法帧头
  • Length: 整个消息长度(含 Magic + Length + MsgID)
  • MsgID: 消息类型(如 0x01=心跳,0x02=数据上报)
  • Payload: 实际数据(JSON 或二进制)

✅ 此设计可有效解决粘包,并支持多类型消息。


二、完整 C# 代码实现

1. 公共协议定义(Protocol.cs

arduino 复制代码
Csharp
编辑
1// Protocol.cs
2using System;
3using System.Text;
4
5public static class Protocol
6{
7    public const ushort MAGIC = 0x55AA;
8    public const int HEADER_SIZE = 8; // 2 (magic) + 4 (length) + 2 (msgId)
9
10    // 打包消息
11    public static byte[] Pack(ushort msgId, byte[] payload)
12    {
13        int totalLen = HEADER_SIZE + payload.Length;
14        byte[] buffer = new byte[totalLen];
15
16        // 写 Magic (大端)
17        Buffer.BlockCopy(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)MAGIC)), 0, buffer, 0, 2);
18        // 写 Length (大端)
19        Buffer.BlockCopy(BitConverter.GetBytes(IPAddress.HostToNetworkOrder(totalLen)), 0, buffer, 2, 4);
20        // 写 MsgID (大端)
21        Buffer.BlockCopy(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((short)msgId)), 0, buffer, 6, 2);
22        // 写 Payload
23        if (payload.Length > 0)
24            Buffer.BlockCopy(payload, 0, buffer, HEADER_SIZE, payload.Length);
25
26        return buffer;
27    }
28
29    // 解析单条完整消息(返回是否成功)
30    public static bool TryParse(byte[] data, out ushort msgId, out byte[] payload)
31    {
32        msgId = 0;
33        payload = Array.Empty<byte>();
34
35        if (data.Length < HEADER_SIZE) return false;
36
37        // 读 Magic
38        ushort magic = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 0));
39        if (magic != MAGIC) return false;
40
41        // 读 Length
42        int length = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, 2));
43        if (length < HEADER_SIZE || length > data.Length) return false;
44
45        // 读 MsgID
46        msgId = (ushort)IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 6));
47
48        // 提取 Payload
49        int payloadLen = length - HEADER_SIZE;
50        if (payloadLen > 0)
51        {
52            payload = new byte[payloadLen];
53            Buffer.BlockCopy(data, HEADER_SIZE, payload, 0, payloadLen);
54        }
55
56        return true;
57    }
58}

2. TCP 服务器(TcpServer.cs

csharp 复制代码
Csharp
编辑
1// TcpServer.cs
2using System;
3using System.Net;
4using System.Net.Sockets;
5using System.Threading.Tasks;
6using System.Collections.Concurrent;
7
8public class TcpServer
9{
10    private TcpListener _listener;
11    private readonly ConcurrentDictionary<string, TcpClient> _clients = new();
12
13    public async Task StartAsync(int port)
14    {
15        _listener = new TcpListener(IPAddress.Any, port);
16        _listener.Start();
17        Console.WriteLine($"✅ 服务器启动,监听端口 {port}");
18
19        while (true)
20        {
21            var client = await _listener.AcceptTcpClientAsync();
22            _ = HandleClientAsync(client); // 启动后台任务
23        }
24    }
25
26    private async Task HandleClientAsync(TcpClient client)
27    {
28        var endpoint = client.Client.RemoteEndPoint.ToString();
29        _clients[endpoint] = client;
30        Console.WriteLine($"🔌 客户端 {endpoint} 已连接");
31
32        byte[] buffer = new byte[1024];
33        var recvBuffer = new MemoryStream();
34
35        try
36        {
37            while (client.Connected)
38            {
39                int bytesRead = await client.GetStream().ReadAsync(buffer, 0, buffer.Length);
40                if (bytesRead == 0) break; // 客户端断开
41
42                recvBuffer.Write(buffer, 0, bytesRead);
43
44                // 尝试解析完整消息
45                while (recvBuffer.Length >= Protocol.HEADER_SIZE)
46                {
47                    byte[] data = recvBuffer.ToArray();
48                    if (Protocol.TryParse(data, out ushort msgId, out byte[] payload))
49                    {
50                        // 处理消息
51                        OnMessageReceived(endpoint, msgId, payload);
52
53                        // 从缓冲区移除已处理数据
54                        int msgLen = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, 2));
55                        recvBuffer = new MemoryStream();
56                        if (data.Length > msgLen)
57                        {
58                            recvBuffer.Write(data, msgLen, data.Length - msgLen);
59                        }
60                    }
61                    else
62                    {
63                        break; // 等待更多数据
64                    }
65                }
66            }
67        }
68        catch (Exception ex)
69        {
70            Console.WriteLine($"❌ 客户端 {endpoint} 错误: {ex.Message}");
71        }
72        finally
73        {
74            _clients.TryRemove(endpoint, out _);
75            client.Close();
76            Console.WriteLine($"🔚 客户端 {endpoint} 已断开");
77        }
78    }
79
80    private void OnMessageReceived(string client, ushort msgId, byte[] payload)
81    {
82        string text = Encoding.UTF8.GetString(payload);
83        Console.WriteLine($"📩 来自 {client} 的消息 (ID={msgId}): {text}");
84
85        // 示例:收到心跳,回复 ACK
86        if (msgId == 0x01)
87        {
88            SendToClient(client, 0x81, Encoding.UTF8.GetBytes("ACK"));
89        }
90    }
91
92    public void SendToClient(string endpoint, ushort msgId, byte[] payload)
93    {
94        if (_clients.TryGetValue(endpoint, out var client) && client.Connected)
95        {
96            var packet = Protocol.Pack(msgId, payload);
97            _ = client.GetStream().WriteAsync(packet, 0, packet.Length);
98        }
99    }
100}

3. TCP 客户端(TcpClientApp.cs

csharp 复制代码
Csharp
编辑
1// TcpClientApp.cs
2using System;
3using System.Net.Sockets;
4using System.Text;
5using System.Threading.Tasks;
6
7public class TcpClientApp
8{
9    private TcpClient _client;
10    private NetworkStream _stream;
11
12    public async Task ConnectAsync(string host, int port)
13    {
14        _client = new TcpClient();
15        await _client.ConnectAsync(host, port);
16        _stream = _client.GetStream();
17        Console.WriteLine($"✅ 已连接到 {host}:{port}");
18
19        _ = ReceiveLoop(); // 启动接收
20    }
21
22    private async Task ReceiveLoop()
23    {
24        byte[] buffer = new byte[1024];
25        var recvBuffer = new MemoryStream();
26
27        try
28        {
29            while (_client.Connected)
30            {
31                int bytesRead = await _stream.ReadAsync(buffer, 0, buffer.Length);
32                if (bytesRead == 0) break;
33
34                recvBuffer.Write(buffer, 0, bytesRead);
35
36                while (recvBuffer.Length >= Protocol.HEADER_SIZE)
37                {
38                    byte[] data = recvBuffer.ToArray();
39                    if (Protocol.TryParse(data, out ushort msgId, out byte[] payload))
40                    {
41                        string text = Encoding.UTF8.GetString(payload);
42                        Console.WriteLine($"📨 收到服务器消息 (ID={msgId}): {text}");
43
44                        // 移除已处理数据
45                        int msgLen = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, 2));
46                        recvBuffer = new MemoryStream();
47                        if (data.Length > msgLen)
48                        {
49                            recvBuffer.Write(data, msgLen, data.Length - msgLen);
50                        }
51                    }
52                    else
53                    {
54                        break;
55                    }
56                }
57            }
58        }
59        catch (Exception ex)
60        {
61            Console.WriteLine($"❌ 接收错误: {ex.Message}");
62        }
63    }
64
65    public async Task SendMessage(ushort msgId, string text)
66    {
67        var payload = Encoding.UTF8.GetBytes(text);
68        var packet = Protocol.Pack(msgId, payload);
69        await _stream.WriteAsync(packet, 0, packet.Length);
70        Console.WriteLine($"📤 已发送 (ID={msgId}): {text}");
71    }
72
73    public void Close() => _client?.Close();
74}

4. 主程序(Program.cs

csharp 复制代码
Csharp
编辑
1// Program.cs
2using System;
3using System.Threading.Tasks;
4
5class Program
6{
7    static async Task Main(string[] args)
8    {
9        Console.WriteLine("选择模式:1=服务器  2=客户端");
10        var mode = Console.ReadLine();
11
12        if (mode == "1")
13        {
14            var server = new TcpServer();
15            await server.StartAsync(8888);
16        }
17        else if (mode == "2")
18        {
19            var client = new TcpClientApp();
20            await client.ConnectAsync("127.0.0.1", 8888);
21
22            // 发送一条测试消息
23            await client.SendMessage(0x02, "Hello from C#!");
24
25            // 发送心跳
26            await client.SendMessage(0x01, "");
27
28            Console.WriteLine("按任意键退出...");
29            Console.ReadKey();
30            client.Close();
31        }
32    }
33}

三、如何运行?

  1. 创建 .NET 6+ 控制台项目
  2. 将上述四个文件放入项目
  3. 先运行服务器(输入 1
  4. 再运行客户端(输入 2

✅ 输出示例:

ini 复制代码
Text
编辑
1【服务器】
2✅ 服务器启动,监听端口 8888
3🔌 客户端 127.0.0.1:54321 已连接
4📩 来自 127.0.0.1:54321 的消息 (ID=2): Hello from C#!
5📩 来自 127.0.0.1:54321 的消息 (ID=1): 

四、为什么这个案例能提升你的工程能力?

能力维度 本项目体现
协议设计 自定义帧结构,解决粘包
网络编程 异步 TCP 收发,内存流缓冲
错误处理 断连检测、异常捕获
可扩展性 MsgID 支持多类型消息
工业适用 可直接用于设备通信、私有协议网关

五、下一步扩展方向

  • 添加 TLS 加密(SslStream
  • 支持 UDP 通信(UdpClient
  • 集成 MQTT/Modbus 等标准协议
  • 增加连接管理(超时踢出、最大连接数)
  • 用 SignalR 构建 WebSocket 网关

结语:通信能力,是高级 .NET 工程师的分水岭

别再只会用 HttpClient 调 API。

真正的系统掌控力,体现在你能否从 socket 层构建可靠通信

这个不到 300 行的 C# 项目,就是你迈向高并发、低延迟、高可靠系统的第一块基石。

从 0 到 1 的距离,不在想法,而在你是否愿意亲手写下第一个 TcpListener

现在,打开 Visual Studio,新建项目------让数据,在你的协议中,精准流动。

相关推荐
一 乐4 小时前
办公系统|基于springboot + vueOA办公管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·spring
Tony Bai4 小时前
Go 1.26 新特性前瞻:从 Green Tea GC 到语法糖 new(expr),性能与体验的双重进化
开发语言·后端·golang
资源站shanxueit或com4 小时前
Python入门教程:从零到实战的保姆级指南(避坑大全) 原创
后端
越千年4 小时前
工作中常用到的二进制运算
后端·go
转转技术团队4 小时前
转转大数据与AI——数据治理安全打标实践
大数据·人工智能·后端
利刃大大4 小时前
【SpringBoot】SpringMVC && 请求注解详解 && 响应注解详解 && Lombok
java·spring boot·后端
梨子同志4 小时前
Java 介绍与开发环境安装
后端
她说..4 小时前
Spring AOP场景4——事务管理(源码分析)
java·数据库·spring boot·后端·sql·spring·springboot