前言
socket是软件之间通讯最常用的一种方式。c#实现socket通讯有很多中方法,其中效率最高就是异步通讯。
异步通讯实际是利用windows完成端口(IOCP)来处理的,关于完成端口实现原理,大家可以参考网上文章。
我这里想强调的是采用完成端口机制的异步通讯是windows下效率最高的通讯方式,没有之一!
异步通讯比同步通讯处理要难很多,代码编写中会遇到许多“坑“。如果没有经验,很难完成。
我搜集了大量资料,完成了对异步socket的封装。此库已用稳定高效的运行几个月。
纵观网上的资料,我还没有遇到一个满意的封装库。许多文章把数据收发和协议处理杂糅在一块,代码非常难懂,也无法扩展。
在编写该库时,避免以上缺陷。将逻辑处理层次化,模块化!同时实现了高可用性与高性能。
为了使大家对通讯效率有初步了解,先看测试图。
客户端和服务端都是本机测试,最大连接数为64422,套接字已耗尽!
主机配置情况
百兆带宽基本占满,cpu占用40%,我的电脑在空闲时,cpu占用大概20%,也就是说程序占用cpu 20%左右。
这个库是可扩展的,就是说即使10万个连接,收发同样的数据,cpu占用基本相同。
库的结构图
- 即可作为服务端(监听)也可以作为客户端(主动连接)使用。
- 可以适应任何网络协议。收发的数据针对字节流或一个完整的包。对协议内容不做处理。
- 高可用性。将复杂的底层处理封装,对外接口非常友好。
- 高性能。最大限度优化处理。单机可支持数万连接,收发速度可达几百兆bit。
实现思路
网络处理逻辑可以分为以下几个部分:
- 网络监听 可以在多个端口实现监听。负责生成socket,生成的socket供后续处理。监听模块功能比较单一,如有必要,可对监听模块做进一步优化。
- 主动连接 可以异步或同步的连接对方。连接成功后,对socket的后续处理,与监听得到的socket完全一样。注:无论是监听得到的socket,还是连接得到的socket,后续处理完全一样。
- Socket收发处理 每个socket对应一个收发实例,socket收发只针对字节流处理。收发时,做了优化。比如发送时,对数据做了粘包,提高发送性能;接收时,一次投递1K的数据。
- 组包处理 一般数据包都有包长度指示;比如 报头的前俩个字节表示长度,根据这个值就可以组成一个完整的包。
NetListener 监听
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace IocpCore
{
class NetListener
{
private Socket listenSocket;
public ListenParam _listenParam { get; set; }
public event Action<ListenParam, AsyncSocketClient> OnAcceptSocket;
bool start;
NetServer _netServer;
public NetListener(NetServer netServer)
{
_netServer = netServer;
}
public int _acceptAsyncCount = 0;
public bool StartListen()
{
try
{
start = true;
IPEndPoint listenPoint = new IPEndPoint(IPAddress.Parse("0.0.0.0"), _listenParam._port);
listenSocket = new Socket(listenPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
listenSocket.Bind(listenPoint);
listenSocket.Listen(200);
Thread thread1 = new Thread(new ThreadStart(NetProcess));
thread1.Start();
StartAccept();
return true;
}
catch (Exception ex)
{
NetLogger.Log(string.Format("**监听异常!{0}", ex.Message));
return false;
}
}
AutoResetEvent _acceptEvent = new AutoResetEvent(false);
private void NetProcess()
{
while (start)
{
DealNewAccept();
_acceptEvent.WaitOne(1000 * 10);
}
}
private void DealNewAccept()
{
try
{
if(_acceptAsyncCount <= 10)
{
StartAccept();
}
while (true)
{
AsyncSocketClient client = _newSocketClientList.GetObj();
if (client == null)
break;
DealNewAccept(client);
}
}
catch (Exception ex)
{
NetLogger.Log(string.Format("DealNewAccept 异常 {0}***{1}", ex.Message, ex.StackTrace));
}
}
private void DealNewAccept(AsyncSocketClient client)
{
client.SendBufferByteCount = _netServer.SendBufferBytePerClient;
OnAcceptSocket?.Invoke(_listenParam, client);
}
public bool StartAccept()
{
return true;
}
ObjectPool<AsyncSocketClient> _newSocketClientList = new ObjectPool<AsyncSocketClient>();
private void ProcessAccept(SocketAsyncEventArgs acceptEventArgs)
{
try
{
using (acceptEventArgs)
{
}
}
catch (Exception ex)
{
NetLogger.Log(string.Format("ProcessAccept {0}***{1}", ex.Message, ex.StackTrace));
}
}
}
}
NetConnectManage连接处理
1 using System; 2 using System.Net; 3 using System.Net.Sockets; 4 5 namespace IocpCore 6 { 7 class NetConnectManage 8 { 9 public event Action<SocketEventParam, AsyncSocketClient> OnSocketConnectEvent; 10 11 public bool ConnectAsyn(string peerIp, int peerPort, object tag) 12 { 13 try 14 { 15 Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp); 16 SocketAsyncEventArgs socketEventArgs = new SocketAsyncEventArgs(); 17 socketEventArgs.RemoteEndPoint = new IPEndPoint(IPAddress.Parse(peerIp), peerPort); 18 socketEventArgs.Completed += SocketConnect_Completed; 19 20 SocketClientInfo clientInfo = new SocketClientInfo(); 21 socketEventArgs.UserToken = clientInfo; 22 clientInfo.PeerIp = peerIp; 23 clientInfo.PeerPort = peerPort; 24 clientInfo.Tag = tag; 25 26 bool willRaiseEvent = socket.ConnectAsync(socketEventArgs); 27 if (!willRaiseEvent) 28 { 29 ProcessConnect(socketEventArgs); 30 socketEventArgs.Completed -= SocketConnect_Completed; 31 socketEventArgs.Dispose(); 32 } 33 return true; 34 } 35 catch (Exception ex) 36 { 37 NetLogger.Log("ConnectAsyn",ex); 38 return false; 39 } 40 } 41 42 private void SocketConnect_Completed(object sender, SocketAsyncEventArgs socketEventArgs) 43 { 44 ProcessConnect(socketEventArgs); 45 socketEventArgs.Completed -= SocketConnect_Completed; 46 socketEventArgs.Dispose(); 47 } 48 49 private void ProcessConnect(SocketAsyncEventArgs socketEventArgs) 50 { 51 SocketClientInfo clientInfo = socketEventArgs.UserToken as SocketClientInfo; 52 if (socketEventArgs.SocketError == SocketError.Success) 53 { 54 DealConnectSocket(socketEventArgs.ConnectSocket, clientInfo); 55 } 56 else 57 { 58 SocketEventParam socketParam = new SocketEventParam(EN_SocketEvent.connect, null); 59 socketParam.ClientInfo = clientInfo; 60 OnSocketConnectEvent?.Invoke(socketParam, null); 61 } 62 } 63 64 65 void DealConnectSocket(Socket socket, SocketClientInfo clientInfo) 66 { 67 clientInfo.SetClientInfo(socket); 68 69 AsyncSocketClient client = new AsyncSocketClient(socket); 70 client.SetClientInfo(clientInfo); 71 72 //触发事件 73 SocketEventParam socketParam = new SocketEventParam(EN_SocketEvent.connect, socket); 74 socketParam.ClientInfo = clientInfo; 75 OnSocketConnectEvent?.Invoke(socketParam, client); 76 } 77 78 public bool Connect(string peerIp, int peerPort, object tag, out Socket socket) 79 { 80 socket = null; 81 try 82 { 83 Socket socketTmp = new Socket(SocketType.Stream, ProtocolType.Tcp); 84 85 SocketClientInfo clientInfo = new SocketClientInfo(); 86 clientInfo.PeerIp = peerIp; 87 clientInfo.PeerPort = peerPort; 88 clientInfo.Tag = tag; 89 90 EndPoint remoteEP = new IPEndPoint(IPAddress.Parse(peerIp), peerPort); 91 socketTmp.Connect(remoteEP); 92 if (!socketTmp.Connected) 93 return false; 94 95 DealConnectSocket(socketTmp, clientInfo); 96 socket = socketTmp; 97 return true; 98 } 99 catch (Exception ex) 100 { 101 NetLogger.Log(string.Format("连接对方:({0}:{1})出错!", peerIp, peerPort), ex); 102 return false; 103 } 104 } 105 } 106 }