最近在做一个C#的项目,需要作为Tcp的Server端来工作,于是研究了一下C#下面的完成端口用法,做了一些简单的封装。这里仅列出一些基础代码,全部的代码可以在 GitHub 获取。
另基于这个库分别给客户端与服务端写了一个demo,GitHub代码路径:TcpIOCPClient、TcpIOCPServer
封装了几个接口:
ITcpSubscribe
public interface ITcpSubscribe
{
public void OnConnected(UserToken userToken);
public void OnDisconnected(UserToken userToken);
public void OnRecv(UserToken userToken, byte[] msg, int offset, int len);
}
ITcp:
public interface ITcp
{
public void Init();
public void RegisterSubscribe(ITcpSubscribe tcpSubscribe);
public bool Send(UserToken userToken, byte[] msg, int offset, int len);
public bool Send(UserToken userToken, string msg);
}
对IOCP主要做了几个函数的封装,PostConnect、PostDisconnect、PostAccept、PostSend、PostRecv、ConnectCompleted、DisconnectCompleted、AcceptCompleted、SendCompleted、RecvCompleted。
此外还有一些参数设置的细节。
TcpIOCP.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace CommonLibrary
{
public class TcpIOCP : ITcp
{
protected long m_MaxSessionID;
protected int m_NumConnections;
protected BufferPool m_BufferPool;
protected IPEndPoint? m_IPEndPoint;
protected Socket? m_ListenSocket;
protected ITcpSubscribe? m_TcpSubscribe;
protected int m_ReceiveBufferSize;
protected SocketAsyncEventArgsPool m_ConnectPool;
protected SocketAsyncEventArgsPool m_SendPool;
protected SocketAsyncEventArgsPool m_RecvPool;
protected List m_Connects = new List();
public TcpIOCP(int numConnections, int receiveBufferSize)
{
m_MaxSessionID = 0;
m_NumConnections = numConnections;
m_ReceiveBufferSize = receiveBufferSize;
m_BufferPool = new BufferPool(numConnections, receiveBufferSize);
m_ConnectPool = new SocketAsyncEventArgsPool(numConnections);
m_SendPool = new SocketAsyncEventArgsPool(numConnections);
m_RecvPool = new SocketAsyncEventArgsPool(numConnections);
}
public void Init()
{
SocketAsyncEventArgs sendEventArg;
SocketAsyncEventArgs recvEventArg;
for (int i = 0; i < m_NumConnections; i++)
{
sendEventArg = GetSendEventArgs();
m_SendPool.Push(sendEventArg);
recvEventArg = GetRecvEventArgs();
m_RecvPool.Push(recvEventArg);
}
}
public void RegisterSubscribe(ITcpSubscribe tcpSubscribe)
{
m_TcpSubscribe = tcpSubscribe;
}
public bool Send(UserToken userToken, byte[] msg, int offset, int len)
{
SocketAsyncEventArgs sendEventArgs = GetSendEventArgs();
sendEventArgs.UserToken = userToken;
sendEventArgs.SetBuffer(msg, offset, len);
PostSend(sendEventArgs);
return true;
}
public bool Send(UserToken userToken, string msg)
{
SocketAsyncEventArgs sendEventArgs = GetSendEventArgs();
sendEventArgs.UserToken = userToken;
byte[] sendData = Encoding.UTF8.GetBytes(msg);
sendEventArgs.SetBuffer(sendData);
PostSend(sendEventArgs);
return true;
}
protected SocketAsyncEventArgs GetConnectEventArgs()
{
SocketAsyncEventArgs? connectEventArgs = m_ConnectPool.Pop();
if (connectEventArgs == null)
{
connectEventArgs = CreateEventArgs();
}
return connectEventArgs;
}
protected SocketAsyncEventArgs GetSendEventArgs()
{
SocketAsyncEventArgs? sendEventArgs = m_SendPool.Pop();
if (sendEventArgs == null)
{
sendEventArgs = CreateEventArgs();
}
return sendEventArgs;
}
protected SocketAsyncEventArgs GetRecvEventArgs()
{
SocketAsyncEventArgs? recvEventArgs = m_RecvPool.Pop();
if (recvEventArgs == null)
{
recvEventArgs = CreateEventArgs();
recvEventArgs.SetBuffer(m_BufferPool.Pop(), 0, m_ReceiveBufferSize);
}
return recvEventArgs;
}
protected SocketAsyncEventArgs CreateEventArgs()
{
SocketAsyncEventArgs eventArgs = new SocketAsyncEventArgs();
eventArgs.Completed += new EventHandler(IO_Completed);
return eventArgs;
}
protected void AddConnect(UserToken userToken)
{
lock (m_Connects)
{
m_Connects.Add(userToken);
}
m_TcpSubscribe?.OnConnected(userToken);
}
protected void RemoveConnect(UserToken userToken)
{
lock (m_Connects)
{
m_Connects.Remove(userToken);
}
m_TcpSubscribe?.OnDisconnected(userToken);
}
protected void PostConnect(IPEndPoint ipEndPoint)
{
Socket socket = new Socket(ipEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
UserToken token = new UserToken(++m_MaxSessionID, socket, ipEndPoint);
SocketAsyncEventArgs connectEventArg = GetConnectEventArgs();
connectEventArg.RemoteEndPoint = ipEndPoint;
connectEventArg.UserToken = token;
bool willRaiseEvent = socket.ConnectAsync(connectEventArg);
if (!willRaiseEvent)
{
ConnectCompleted(connectEventArg);
}
}
protected void PostDisconnect(UserToken userToken)
{
SocketAsyncEventArgs disconnectEventArg = GetConnectEventArgs();
disconnectEventArg.UserToken = userToken;
bool willRaiseEvent = userToken.Socket.DisconnectAsync(disconnectEventArg);
if (!willRaiseEvent)
{
DisconnectCompleted(disconnectEventArg);
}
}
protected void PostAccept(SocketAsyncEventArgs? acceptEventArg)
{
if (m_ListenSocket == null)
{
throw new ArgumentException("PostAccept Failed for ListenSocket is null");
}
if (acceptEventArg == null)
{
acceptEventArg = CreateEventArgs();
}
else
{
acceptEventArg.AcceptSocket = null;
}
bool willRaiseEvent = m_ListenSocket.AcceptAsync(acceptEventArg);
if (!willRaiseEvent)
{
AcceptCompleted(acceptEventArg);
}
}
protected void PostSend(SocketAsyncEventArgs sendEventArg)
{
if (sendEventArg.UserToken == null)
{
throw new ArgumentException("PostSend Failed for UserToken is null");
}
UserToken token = (UserToken)sendEventArg.UserToken;
bool willRaiseEvent = token.Socket.SendAsync(sendEventArg);
if (!willRaiseEvent)
{
SendCompleted(sendEventArg);
}
}
protected void PostRecv(SocketAsyncEventArgs recvEventArg)
{
if (recvEventArg.UserToken == null)
{
throw new ArgumentException("PostRecv Failed for UserToken is null");
}
UserToken token = (UserToken)recvEventArg.UserToken;
bool willRaiseEvent = token.Socket.ReceiveAsync(recvEventArg);
if (!willRaiseEvent)
{
RecvCompleted(recvEventArg);
}
}
protected void IO_Completed(object? sender, SocketAsyncEventArgs e)
{
switch (e.LastOperation)
{
case SocketAsyncOperation.Connect:
ConnectCompleted(e);
break;
case SocketAsyncOperation.Disconnect:
DisconnectCompleted(e);
break;
case SocketAsyncOperation.Accept:
AcceptCompleted(e);
break;
case SocketAsyncOperation.Send:
SendCompleted(e);
break;
case SocketAsyncOperation.Receive:
RecvCompleted(e);
break;
default:
throw new ArgumentException("The last operation completed on the socket was not a receive or send");
}
}
protected void ConnectCompleted(SocketAsyncEventArgs connectEventArgs)
{
Console.WriteLine($"ConnectCompleted RemoteEndPoint:{connectEventArgs.RemoteEndPoint?.ToString()}");
if (connectEventArgs.SocketError != SocketError.Success)
{
Console.WriteLine($"Connect Failed! SocketError:{connectEventArgs.SocketError}");
CloseClientSocket(connectEventArgs);
}
else
{
if (connectEventArgs.UserToken == null)
{
throw new ArgumentException("Connect Error, UserToken is null");
}
else
{
UserToken token = (UserToken)connectEventArgs.UserToken;
if(token.Socket != connectEventArgs.ConnectSocket)
{
throw new ArgumentException("Recv Error, Buffer is null");
}
AddConnect(token);
connectEventArgs.SetBuffer(m_BufferPool.Pop(), 0, m_ReceiveBufferSize);
PostRecv(connectEventArgs);
}
}
}
protected void DisconnectCompleted(SocketAsyncEventArgs disconnectEventArgs)
{
Console.WriteLine($"DisconnectCompleted SocketError:{disconnectEventArgs.SocketError}");
CloseClientSocket(disconnectEventArgs);
}
protected void AcceptCompleted(SocketAsyncEventArgs acceptEventArg)
{
Console.WriteLine($"AcceptCompleted RemoteEndPoint:{acceptEventArg.AcceptSocket?.RemoteEndPoint?.ToString()}");
if (acceptEventArg.SocketError != SocketError.Success)
{
Console.WriteLine($"AcceptCompleted Failed. SocketError:{acceptEventArg.SocketError}, AcceptSocket:{acceptEventArg.AcceptSocket}");
return;
}
else if(acceptEventArg.AcceptSocket == null)
{
throw new ArgumentException("AcceptSocket is null");
}
else
{
SocketAsyncEventArgs recvEventArgs = GetRecvEventArgs();
long sessionID = ++m_MaxSessionID;
UserToken userToken = new UserToken(sessionID, acceptEventArg.AcceptSocket, (IPEndPoint?)acceptEventArg.AcceptSocket.RemoteEndPoint);
recvEventArgs.UserToken = userToken;
AddConnect(userToken);
PostRecv(recvEventArgs);
PostAccept(acceptEventArg);
}
}
protected void SendCompleted(SocketAsyncEventArgs sendEventArg)
{
Console.WriteLine($"SendCompleted, BytesTransferred: {sendEventArg.BytesTransferred}");
if (sendEventArg.SocketError != SocketError.Success)
{
Console.WriteLine($"Send Failed! SocketError:{sendEventArg.SocketError}");
CloseClientSocket(sendEventArg);
}
else
{
m_SendPool.Push(sendEventArg);
}
}
protected void RecvCompleted(SocketAsyncEventArgs recvEventArg)
{
Console.WriteLine($"RecvCompleted, BytesTransferred: {recvEventArg.BytesTransferred}");
if (recvEventArg.SocketError != SocketError.Success)
{
Console.WriteLine($"Recv Failed! SocketError:{recvEventArg.SocketError}");
CloseClientSocket(recvEventArg);
}
else if (recvEventArg.BytesTransferred == 0)
{
Console.WriteLine($"Recv 0 byte. Do DisConnect.");
CloseClientSocket(recvEventArg);
}
else
{
if (recvEventArg.UserToken == null)
{
throw new ArgumentException("Recv Error, UserToken is null");
}
else if (recvEventArg.Buffer == null)
{
throw new ArgumentException("Recv Error, Buffer is null");
}
else
{
UserToken userToken = (UserToken)recvEventArg.UserToken;
m_TcpSubscribe?.OnRecv(userToken, recvEventArg.Buffer, recvEventArg.Offset, recvEventArg.BytesTransferred);
}
PostRecv(recvEventArg);
}
}
protected void CloseClientSocket(SocketAsyncEventArgs e)
{
if (e.UserToken == null)
{
throw new ArgumentException($"CloseClientSocket Error For UserToken is null.");
}
UserToken userToken = (UserToken)e.UserToken;
Console.WriteLine($"CloseClientSocket SessionID:{userToken.SessionID}, Socket:{userToken.Socket.Handle.ToInt64()}");
userToken.Socket.Close();
RemoveConnect(userToken);
if (e.LastOperation == SocketAsyncOperation.Connect || e.LastOperation == SocketAsyncOperation.Disconnect)
{
m_ConnectPool.Push(e);
}
else if (e.LastOperation == SocketAsyncOperation.Receive)
{
m_RecvPool.Push(e);
}
else if (e.LastOperation == SocketAsyncOperation.Send)
{
m_SendPool.Push(e);
}
else
{
Console.WriteLine($"Unexpected Operation while Close Client Socket. LastOperation:{e.LastOperation}");
}
}
}
}
Client与Server的定义就比较简单了。
TcpIOCPClient.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace CommonLibrary
{
public class TcpIOCPClient : TcpIOCP, ITcpClient
{
public TcpIOCPClient(int numConnections = 10, int receiveBufferSize = 64 * 1024)
: base(numConnections, receiveBufferSize)
{
}
public void Connect(IPEndPoint ipEndPoint)
{
PostConnect(ipEndPoint);
}
public void Disconnect(UserToken userToken)
{
PostDisconnect(userToken);
}
}
}
TcpIOCPServer.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace CommonLibrary
{
public class TcpIOCPServer : TcpIOCP, ITcpServer
{
public TcpIOCPServer(int numConnections = 10, int receiveBufferSize = 64 * 1024)
:base(numConnections, receiveBufferSize)
{
}
public void Start(IPEndPoint ipEndPoint)
{
m_IPEndPoint = ipEndPoint;
m_ListenSocket = new Socket(m_IPEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
m_ListenSocket.Bind(m_IPEndPoint);
m_ListenSocket.Listen(m_NumConnections);
for (int i = 0; i < m_NumConnections; i++)
{
PostAccept(null);
}
}
public void Stop()
{
lock(m_Connects)
{
foreach (var item in m_Connects)
{
PostDisconnect(item);
}
}
if (m_ListenSocket != null)
{
m_ListenSocket.Close();
}
}
}
}