C# Socket通信知识总结——从TCP到UDP
在 C# 上位机开发中,除了串口通信之外,Socket 网络通信也是非常重要的一部分。
例如:
上位机与 PLC 通信
上位机与机器人通信
上位机与视觉软件通信
客户端与服务器通信
多台电脑之间的数据通信
设备之间通过以太网通信
C# 中常见的网络通信方式主要有:
TCP
UDP
而 Socket 是进行网络通信的基础类之一。
一、什么是 Socket?
可以把 Socket 理解成:
程序进行网络通信的一个接口。
例如两台电脑:
电脑A 电脑B
客户端 服务器
│ │
│ 网络通信 │
└──────── Socket ─────────────┘
Socket 本身不是 TCP 或 UDP。
它更像是一个通信工具,可以使用:
TCP
UDP
等不同的通信方式。
二、IP地址和端口
学习 Socket,首先要理解两个东西:
IP地址
端口
例如:
192.168.1.100:60000
可以理解成:
192.168.1.100
↓
哪台电脑
60000
↓
哪个程序
所以:
IP 地址用于找到哪台计算机,端口用于找到这台计算机上的哪个网络程序。
例如:
服务器:
IP:192.168.1.100
Port:60000
客户端需要连接:
192.168.1.100
60000
三、TCP和UDP的区别
Socket 最常见的两种通信方式就是:
TCP
UDP
两者的区别非常重要。
特点 TCP UDP
是否建立连接 是 否
是否可靠 是 不保证
是否保证顺序 是 不保证
是否保证到达 是 不保证
是否有连接状态 有 无
速度 相对较慢 相对较快
数据形式 字节流 数据报
常见用途 聊天、文件、控制 广播、实时数据
简单理解:
TCP:
我先和你建立连接,再通信。
UDP:
我直接把数据扔给你。
四、TCP Socket通信
TCP通信通常采用:
服务器
↓
创建Socket
↓
Bind
↓
Listen
↓
Accept
↓
等待客户端
客户端:
创建Socket
↓
Connect
↓
连接服务器
↓
Send / Receive
整体流程:
TCP服务器
│
Socket
│
Bind
│
Listen
│
Accept
│
│
TCP客户端
│
Socket
│
Connect
│
▼
通信
Send / Receive
五、TCP服务器Socket
先创建 Socket:
Socket serverSocket = new Socket(
AddressFamily.InterNetwork,
SocketType.Stream,
ProtocolType.Tcp
);
这里三个参数非常重要。
AddressFamily
AddressFamily.InterNetwork
表示使用 IPv4。
SocketType
TCP 使用:
SocketType.Stream
表示流式 Socket。
ProtocolType
TCP:
ProtocolType.Tcp
所以:
Socket serverSocket = new Socket(
AddressFamily.InterNetwork,
SocketType.Stream,
ProtocolType.Tcp
);
就是:
创建一个 IPv4 + TCP + 流式 Socket。
六、Bind绑定IP和端口
创建 Socket 后,需要绑定服务器的 IP 和端口。
IPAddress ip = IPAddress.Parse(“192.168.1.100”);
IPEndPoint endPoint =
new IPEndPoint(ip, 60000);
serverSocket.Bind(endPoint);
这里:
IPAddress.Parse()
负责把字符串:
192.168.1.100
转换成 IP 地址对象。
而:
IPEndPoint
表示:
IP + Port
例如:
192.168.1.100:60000
七、Listen监听
服务器绑定以后:
serverSocket.Listen(100);
表示服务器开始监听客户端连接。
其中:
100
表示等待连接队列的相关容量。
八、Accept接收客户端
服务器可以:
Socket clientSocket =
serverSocket.Accept();
Accept() 的作用:
等待客户端连接。
客户端连接以后:
服务器
│
Listen
│
Accept
│
└────── 客户端连接
Accept() 返回一个新的 Socket。
这个 Socket 专门负责:
服务器 ↔ 这个客户端
九、一个简单的TCP服务器
完整代码:
Socket serverSocket = new Socket(
AddressFamily.InterNetwork,
SocketType.Stream,
ProtocolType.Tcp
);
IPAddress ip =
IPAddress.Parse(“192.168.1.100”);
IPEndPoint endPoint =
new IPEndPoint(ip, 60000);
serverSocket.Bind(endPoint);
serverSocket.Listen(100);
Socket clientSocket =
serverSocket.Accept();
执行流程:
创建Socket
↓
Bind
↓
Listen
↓
Accept
↓
客户端连接成功
十、TCP客户端Socket
客户端也需要创建 Socket:
Socket clientSocket = new Socket(
AddressFamily.InterNetwork,
SocketType.Stream,
ProtocolType.Tcp
);
然后连接服务器:
IPAddress ip =
IPAddress.Parse(“192.168.1.100”);
IPEndPoint endPoint =
new IPEndPoint(ip, 60000);
clientSocket.Connect(endPoint);
也可以直接:
clientSocket.Connect(
“192.168.1.100”,
60000
);
连接成功以后:
客户端
│
Connect
│
▼
服务器
十一、TCP发送数据
Socket发送数据使用:
Send()
例如:
string message = “Hello”;
byte[] data =
Encoding.UTF8.GetBytes(message);
clientSocket.Send(data);
数据流程:
string
↓
Encoding
↓
byte[]
↓
Send()
↓
网络
十二、TCP接收数据
接收数据:
byte[] buffer = new byte[1024];
int length =
clientSocket.Receive(buffer);
Receive() 返回值:
length
表示实际收到多少个字节。
例如:
buffer大小:
1024 byte
实际收到:
5 byte
那么:
length == 5
十三、把接收的字节转换成字符串
如果发送的是 UTF-8 字符串:
string message =
Encoding.UTF8.GetString(
buffer,
0,
length
);
完整:
byte[] buffer = new byte[1024];
int length =
clientSocket.Receive(buffer);
string message =
Encoding.UTF8.GetString(
buffer,
0,
length
);
十四、TCP完整通信过程
例如客户端发送:
Hello
过程:
客户端
“Hello”
↓
Encoding.UTF8
↓
byte[]
↓
Send()
↓
网络
================
↓
Receive()
↓
byte[]
↓
Encoding.UTF8
↓
“Hello”
↓
服务器
所以 TCP 通信本质上仍然是:
发送和接收 byte[]。
十五、TCP是字节流
这是 Socket 学习中非常重要的一点。
TCP:
没有消息边界。
例如客户端连续发送:
Hello
World
服务器不一定正好收到:
Hello
World
也可能收到:
HelloWorld
或者:
Hel
loWorld
甚至:
HelloW
orld
这就是 TCP 的:
粘包 / 拆包问题
十六、为什么会出现粘包拆包?
因为 TCP 本质上是:
字节流
而不是:
一条一条的消息
例如:
发送:
[消息A][消息B][消息C]
TCP只看到:
[一大串字节]
所以接收端需要自己判断:
消息A在哪里结束?
消息B在哪里结束?
十七、解决TCP粘包拆包
常见的方法:
方法一:固定长度
例如规定:
每条消息固定100字节
那么:
0~99 → 消息1
100~199 → 消息2
方法二:结束符
例如:
Hello\r\n
World\r\n
规定:
\r\n
就是消息结束。
接收:
Hello\r\nWorld\r\n
解析成:
Hello
World
方法三:长度 + 数据
例如:
长度
+
数据
假设:
05Hello
05World
程序先读取:
05
知道后面有:
5个字节
再读取:
Hello
这是实际项目中非常常见的方式。
十八、Socket异步通信
在 WinForms 中,不建议使用:
while (true)
{
socket.Receive(buffer);
}
直接放在 UI 线程。
因为:
Receive()
可能一直等待数据。
这样会导致:
UI线程
↓
一直等待
↓
界面卡死
因此实际项目中应该使用:
Task
async / await
或者后台线程。
例如:
private async Task ReceiveData()
{
byte[] buffer = new byte[1024];
while (true)
{
int length =
await clientSocket.ReceiveAsync(buffer);
if (length == 0)
{
break;
}
string message =
Encoding.UTF8.GetString(
buffer,
0,
length
);
}
}
这样接收数据时不会直接卡住 UI。
十九、TcpClient和Socket是什么关系?
C# 中除了直接使用:
Socket
还经常使用:
TcpClient
TcpListener
它们可以理解为对 Socket 的进一步封装。
例如:
Socket
↑
底层
而:
TcpClient
TcpListener
↑
更方便的TCP封装
所以如果只是做普通 TCP 上位机通信:
TcpClient
TcpListener
通常比直接操作 Socket 更简单。
例如你之前做的 TCP 项目就使用了:
TcpListener server;
TcpClient client;
这其实就是 C# 中非常常见的 TCP 编程方式。
二十、TcpListener服务器
使用 TcpListener 可以更加简单地创建 TCP 服务器:
TcpListener server =
new TcpListener(
IPAddress.Parse(“192.168.1.100”),
60000
);
server.Start();
等待客户端:
TcpClient client =
await server.AcceptTcpClientAsync();
然后:
NetworkStream stream =
client.GetStream();
发送:
byte[] data =
Encoding.UTF8.GetBytes(“Hello”);
await stream.WriteAsync(data, 0, data.Length);
接收:
byte[] buffer = new byte[1024];
int length =
await stream.ReadAsync(
buffer,
0,
buffer.Length
);
二十一、UDP Socket
TCP需要建立连接:
客户端
↓
Connect
↓
服务器
UDP则不需要建立连接。
UDP更像:
发送方
│
│ 数据
↓
网络
│
↓
接收方
UDP Socket创建:
Socket socket = new Socket(
AddressFamily.InterNetwork,
SocketType.Dgram,
ProtocolType.Udp
);
这里:
SocketType.Dgram
表示数据报。
二十二、UDP发送数据
UDP可以使用:
SendTo()
例如:
byte[] data =
Encoding.UTF8.GetBytes(“Hello”);
IPEndPoint endPoint =
new IPEndPoint(
IPAddress.Parse(“192.168.1.100”),
60000
);
socket.SendTo(data, endPoint);
二十三、UDP接收数据
UDP接收:
byte[] buffer = new byte[1024];
EndPoint remoteEndPoint =
new IPEndPoint(
IPAddress.Any,
0
);
int length =
socket.ReceiveFrom(
buffer,
ref remoteEndPoint
);
然后转换:
string message =
Encoding.UTF8.GetString(
buffer,
0,
length
);
二十四、TCP和UDP怎么选择?
实际开发中可以简单参考:
需要可靠传输
↓
TCP
例如:
聊天
文件传输
设备控制
配置参数
数据库通信
如果更加关注:
实时性
广播
组播
可以考虑:
UDP
例如:
设备发现
局域网广播
实时状态
视频/音频相关数据
但具体选择还是要根据设备协议和业务要求决定。
二十五、Socket通信中常见问题
- IP地址不对
例如服务器:
192.168.1.100
客户端却连接:
192.168.1.101
当然无法连接。
- 端口不一致
服务器:
60000
客户端:
50000
也无法连接。
- 防火墙
即使:
IP正确
端口正确
代码正确
Windows 防火墙也可能阻止通信。
- 服务器没有启动
客户端:
Connect()
但是服务器没有:
Listen()
客户端自然连接不上。
- Receive一直等待
例如:
socket.Receive(buffer);
如果没有数据:
Receive()
↓
等待
↓
等待
↓
等待
如果这个代码运行在 UI 线程:
界面卡死
所以需要考虑异步或者后台线程。
二十六、TCP连接断开怎么判断?
TCP中:
Receive()
如果返回:
0
通常表示:
对方已经正常关闭连接。
例如:
int length =
await stream.ReadAsync(buffer, 0, buffer.Length);
if (length == 0)
{
// 客户端断开
}
这个判断在实际项目中非常重要。
另外:
client.Connected
不能简单理解为:
只要是 true 就代表对方现在一定在线。
它不能单独作为可靠的连接存活判断。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)