在C#开发中,基于TCP/IP协议的Socket通信是实现跨进程、跨设备可靠数据传输的核心技术,相比UDP协议,它具备连接稳定、数据无丢失、传输顺序有保障的特点,广泛应用于即时通讯、物联网数据上报、分布式系统交互等场景。

Socket通信核心原理
TCP/IP协议下的Socket通信遵循三次握手建立连接、四次挥手断开连接的流程,通信双方分为服务端和客户端两个角色:
- 服务端需要先绑定指定端口,监听端口上的连接请求,接收客户端连接后与客户端建立独立的通信通道
- 客户端需要主动发起连接请求,指定服务端的IP地址和端口,连接建立后即可双向传输数据
- 每个成功的连接都会在服务端生成一个对应的Socket对象,用于和该客户端进行专属通信
TCP服务端搭建实现
基础服务端代码示例
以下代码实现了支持多客户端连接的异步TCP服务端,避免了同步阻塞导致的性能问题:
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace SocketServerDemo
{
class TcpServer
{
// 服务端监听的端口
private static int port = 8888;
// 用于统计客户端连接数
private static int clientCount = 0;
static void Main(string[] args)
{
// 创建Socket对象,使用IPv4、流类型、TCP协议
Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
// 绑定IP和端口,IPAddress.Any表示监听所有本机网卡
serverSocket.Bind(new IPEndPoint(IPAddress.Any, port));
// 开始监听,参数表示等待连接的最大队列长度
serverSocket.Listen(10);
Console.WriteLine($"服务端已启动,监听端口:{port},等待客户端连接...");
// 异步接收客户端连接
serverSocket.BeginAccept(AcceptCallback, serverSocket);
// 保持主线程运行,避免程序退出
Console.ReadLine();
}
// 接收客户端连接的回调函数
static void AcceptCallback(IAsyncResult ar)
{
Socket serverSocket = (Socket)ar.AsyncState;
try
{
// 完成连接接收,返回和客户端的通信Socket
Socket clientSocket = serverSocket.EndAccept(ar);
clientCount++;
Console.WriteLine($"客户端已连接,当前连接数:{clientCount},客户端地址:{clientSocket.RemoteEndPoint}");
// 给客户端发送欢迎消息
string welcomeMsg = "欢迎连接TCP服务端";
byte[] sendData = Encoding.UTF8.GetBytes(welcomeMsg);
clientSocket.Send(sendData);
// 开启异步接收客户端数据
byte[] buffer = new byte[1024];
clientSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, ReceiveCallback, new object[] { clientSocket, buffer });
// 继续接收下一个客户端连接
serverSocket.BeginAccept(AcceptCallback, serverSocket);
}
catch (Exception ex)
{
Console.WriteLine($"接收客户端连接异常:{ex.Message}");
}
}
// 接收客户端数据的回调函数
static void ReceiveCallback(IAsyncResult ar)
{
object[] objs = (object[])ar.AsyncState;
Socket clientSocket = (Socket)objs[0];
byte[] buffer = (byte[])objs[1];
try
{
// 完成数据接收,返回接收的字节数
int receiveLength = clientSocket.EndReceive(ar);
if (receiveLength > 0)
{
string receiveMsg = Encoding.UTF8.GetString(buffer, 0, receiveLength);
Console.WriteLine($"收到客户端{clientSocket.RemoteEndPoint}消息:{receiveMsg}");
// 回传数据给客户端
string responseMsg = $"服务端已收到你的消息:{receiveMsg}";
byte[] responseData = Encoding.UTF8.GetBytes(responseMsg);
clientSocket.Send(responseData);
// 继续接收下一段数据
clientSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, ReceiveCallback, new object[] { clientSocket, buffer });
}
else
{
// 接收长度为0说明客户端已断开连接
clientSocket.Close();
clientCount--;
Console.WriteLine($"客户端已断开连接,当前连接数:{clientCount}");
}
}
catch (SocketException)
{
// 客户端异常断开连接
clientSocket.Close();
clientCount--;
Console.WriteLine($"客户端异常断开,当前连接数:{clientCount}");
}
catch (Exception ex)
{
Console.WriteLine($"接收数据异常:{ex.Message}");
}
}
}
}
服务端进阶优化点
- 使用
BeginAccept、BeginReceive异步方法替代同步阻塞方法,避免单客户端阻塞导致其他客户端无法连接 - 添加心跳检测机制,定时向客户端发送心跳包,若多次未收到客户端响应则主动断开连接,清理无效连接
- 处理数据粘包问题:可以在数据开头添加长度字段,接收时先读取长度信息,再拼接完整数据包
TCP客户端搭建实现
基础客户端代码示例
以下代码实现了可主动连接服务端、发送消息并接收响应的TCP客户端:
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace SocketClientDemo
{
class TcpClient
{
// 服务端IP地址,本地测试使用127.0.0.1
private static string serverIp = "127.0.0.1";
// 服务端监听端口,需要和客户端一致
private static int serverPort = 8888;
static void Main(string[] args)
{
// 创建客户端Socket对象
Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
// 连接服务端
clientSocket.Connect(new IPEndPoint(IPAddress.Parse(serverIp), serverPort));
Console.WriteLine($"已成功连接服务端{serverIp}:{serverPort}");
// 开启异步接收服务端数据
byte[] buffer = new byte[1024];
clientSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, ReceiveCallback, new object[] { clientSocket, buffer });
// 循环读取用户输入并发送给服务端
while (true)
{
string sendMsg = Console.ReadLine();
if (string.IsNullOrEmpty(sendMsg))
{
continue;
}
if (sendMsg.ToLower() == "exit")
{
break;
}
byte[] sendData = Encoding.UTF8.GetBytes(sendMsg);
clientSocket.Send(sendData);
}
}
catch (Exception ex)
{
Console.WriteLine($"连接服务端异常:{ex.Message}");
}
finally
{
// 断开连接时关闭Socket
if (clientSocket.Connected)
{
clientSocket.Shutdown(SocketShutdown.Both);
}
clientSocket.Close();
Console.WriteLine("客户端已退出");
}
}
// 接收服务端数据的回调函数
static void ReceiveCallback(IAsyncResult ar)
{
object[] objs = (object[])ar.AsyncState;
Socket clientSocket = (Socket)objs[0];
byte[] buffer = (byte[])objs[1];
try
{
int receiveLength = clientSocket.EndReceive(ar);
if (receiveLength > 0)
{
string receiveMsg = Encoding.UTF8.GetString(buffer, 0, receiveLength);
Console.WriteLine($"收到服务端消息:{receiveMsg}");
// 继续接收下一段数据
clientSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, ReceiveCallback, new object[] { clientSocket, buffer });
}
}
catch (Exception ex)
{
Console.WriteLine($"接收服务端数据异常:{ex.Message}");
}
}
}
}
客户端进阶优化点
- 添加断线重连机制,当检测到连接断开时,间隔一定时间尝试重新连接服务端,提升程序鲁棒性
- 发送数据时添加队列机制,避免高频发送导致的数据冲突,同时可以在发送前对数据进行校验
- 处理连接超时问题,设置
SendTimeout和ReceiveTimeout属性,避免无效等待
常见问题与解决方案
| 问题描述 | 产生原因 | 解决方案 |
|---|---|---|
| 服务端启动时报端口被占用 | 该端口已被其他程序占用,或者上次程序退出未正确释放端口 | 更换监听端口,或者在程序退出时确保调用Socket的Close方法释放端口 |
| 数据传输出现乱码 | 服务端和客户端使用的编码格式不一致 | 统一使用UTF8编码进行数据的序列化和反序列化 |
| 客户端发送长数据后服务端接收不完整 | TCP协议是流传输,会出现粘包拆包问题 | 自定义通信协议,在数据包头部添加长度字段,接收端按长度拼接完整数据 |
| 客户端频繁断开重连导致服务端连接数暴涨 | 客户端断开后服务端未正确检测到连接断开,未释放Socket资源 | 添加心跳检测机制,同时服务端在接收数据长度为0时及时关闭对应Socket |
使用注意事项
在实际生产环境中使用Socket通信时,还需要注意以下几点:
- 所有Socket操作都需要放在try-catch块中,捕获SocketException等异常,避免程序崩溃
- 高并发场景下可以使用SocketAsyncEventArgs替代Begin/End异步方法,减少线程切换开销,提升性能
- 传输敏感数据时需要添加加密逻辑,比如使用AES加密传输内容,避免数据被窃听
- 服务端需要限制单个IP的最大连接数,避免恶意连接攻击导致服务不可用