在工业控制、物联网、游戏后端、金融交易等场景中,稳定、高效、可扩展的通信能力 是系统的核心。而 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}
三、如何运行?
- 创建 .NET 6+ 控制台项目
- 将上述四个文件放入项目
- 先运行服务器(输入
1) - 再运行客户端(输入
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,新建项目------让数据,在你的协议中,精准流动。