基于TCP的异步Socket通信实现解析
在C#网络编程中,Socket通信遵循一套标准的流程,涉及套接字创建、绑定、监听、连接、数据收发和关闭等阶段。以下是对这一过程的详细阐述,包括异步模式的实现原理和代码示例。
Socket通信的基本流程
- 启动连接前,必须先创建Socket对象并调用Bind方法。Socket用于初始化套接字实例,Bind用于将套接字绑定到特定的IP地址和端口号(对于客户端,Connect时会由系统自动分配端口,因此可省略Bind)。
- 服务端通过Listen方法进入监听状态,等待客户端连接请求。
- 客户端调用Connect建立连接,服务端通过Accept接受连接。在Connect-Accept交互中,操作系统底层会完成TCP三次握手。
- 连接建立后,双方通过Write和Read(或Send/Receive)交换数据,操作系统负责TCP数据的确认与重发等可靠性机制。
- 通信结束后,调用Close关闭连接,操作系统执行四次挥手以终止连接。
异步通信原理
Socket编程继承了文件操作的核心思想:打开、读写、关闭。在C/S模式下,服务端执行:打开通道并绑定,进入监听状态,接收客户端请求并创建专用连接进行读写,处理完成后关闭专用连接(可重复循环)。客户端则简化为:打开通道并连接,数据交互,关闭通道。
Socket通信分为两种模式:
- 同步模式:客户端发送请求后必须等待服务端响应才能继续,串行执行。
- 异步模式:客户端发送请求后无需等待响应即可继续其他操作,并行执行。
套接字的工作模式:
- 阻塞模式:调用Socket方法时,当前线程会被挂起,直到操作完成才返回结果。
- 非阻塞模式:调用Socket方法立即返回,无论操作是否完成。
套接字工作步骤:
- 服务器监听:服务端套接字不绑定特定客户端,而是处于等待连接的状态,持续监控网络。
- 客户端请求连接:客户端发送连接请求,目标为服务端套接字的地址和端口。
- 连接确认:服务端监听到请求后,响应并创建新线程,将服务端套接字描述发送给客户端,确认后连接建立;原服务端套接字继续监听其他请求。
异步通信示例代码
以下示例展示了基于TCP的异步Socket服务器实现,包括连接池管理、异步接受连接和接收数据。
// 初始化连接池,maxConn为最大客户端数量
Conn[] connections = new Conn[maxConn];
for (int i = 0; i < maxConn; i++)
{
connections[i] = new Conn();
}
// 创建TCP套接字并绑定本地端点
Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint localEP = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 11000);
serverSocket.Bind(localEP);
serverSocket.Listen(maxConn);
Console.WriteLine("[服务器]启动成功,等待连接...");
// 开始异步接受连接
serverSocket.BeginAccept(AcceptCallback, null);
// 保持服务器运行,输入"quit"退出
while (true)
{
if (Console.ReadLine() == "quit")
return;
}
/// <summary>
/// Accept回调,处理新客户端连接
/// </summary>
private static void AcceptCallback(IAsyncResult asyncResult)
{
try
{
Socket clientSocket = serverSocket.EndAccept(asyncResult);
int slotIndex = GetAvailableIndex();
if (slotIndex < 0)
{
clientSocket.Close();
Console.WriteLine("[警告] 连接池已满,拒绝连接");
}
else
{
Conn conn = connections[slotIndex];
conn.Initialize(clientSocket);
string clientAddress = conn.GetRemoteAddress();
Console.WriteLine($"客户端连接 [{clientAddress}],连接池索引:{slotIndex}");
// 开始异步接收数据
conn.socket.BeginReceive(conn.receiveBuffer, conn.bufferOffset, conn.GetRemainingBufferSize(),
SocketFlags.None, ReceiveCallback, conn);
}
// 继续接受下一个连接
serverSocket.BeginAccept(AcceptCallback, null);
}
catch (Exception ex)
{
Console.WriteLine($"Accept回调异常:{ex.Message}");
}
}
/// <summary>
/// 接收回调,处理客户端发送的数据
/// </summary>
private static void ReceiveCallback(IAsyncResult asyncResult)
{
Conn conn = (Conn)asyncResult.AsyncState;
try
{
int bytesReceived = conn.socket.EndReceive(asyncResult);
// 客户端断开连接
if (bytesReceived <= 0)
{
Console.WriteLine($"客户端 [{conn.GetRemoteAddress()}] 断开连接");
conn.Close();
return;
}
// 解析接收到的数据
string receivedMessage = Encoding.UTF8.GetString(conn.receiveBuffer, 0, bytesReceived);
Console.WriteLine($"收到 [{conn.GetRemoteAddress()}] 数据:{receivedMessage}");
// 构建响应消息
string response = $"{conn.GetRemoteAddress()} 发送的消息:{receivedMessage}";
byte[] responseBytes = Encoding.UTF8.GetBytes($"服务端已收到:{response}");
// 广播给当前连接池中的第一个活跃连接(示例为点播)
for (int i = 0; i <= 0; i++)
{
if (connections[i] != null && connections[i].isActive)
{
Console.WriteLine($"转发消息给 {connections[i].GetRemoteAddress()}");
connections[i].socket.Send(responseBytes);
}
}
// 继续异步接收数据
conn.socket.BeginReceive(conn.receiveBuffer, conn.bufferOffset, conn.GetRemainingBufferSize(),
SocketFlags.None, ReceiveCallback, conn);
}
catch (Exception ex)
{
Console.WriteLine($"接收数据时客户端 [{conn.GetRemoteAddress()}] 断开,异常:{ex.Message}");
conn.Close();
}
}