高性能的iocp网络设计思路

IOCP是什么就不用介绍了,为什么要用IOCP就更不用提及。这里我们只简单讨论IOCP开发的一个思路,即能提高性能又能隆低开发复杂性。
“即能提高性能又能隆低开发复杂性”?觉得我说的有矛盾吗?不是复杂的代码才能换来高效吗?其实不一定,我认为简单是一切事务的根本,就像遗传学的四个规则就造就了地球千变万化的生命世界。
我们通常看到网上的代码示例都是一个IOCP句柄,多个IO线程和多个处理线程,由于是多线程环境,只有多个线程可能同时访问到的数据,就一定要加锁,那么问题复杂性就上升了:会不会哪个数据忘了加锁;会不会死锁;效率大大降低(降低了多CPU的优势)。
好,现在我们就把问题简单化。比如我现在要写一个服务器,我的设计只包含两个线程,一个是accept线程,一个是IOCP线程。伪代码如下:
C/C++ code ?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class  Acceptor
{
public :
};
 
class  IoThread
{
public :
     int  InitIoService();  // 创建IPCP,只启动一个线程
     int  OnLoadConfig( const  Config& config);  // 调用PostQueuedCompletionStatus
     int  AddConnection(ClientConnection* conn);  // 调用PostQueuedCompletionStatus
 
private :
     static  unsigned ThreadEntry( void *);
     unsigned ThreadProc();  // 调用GetQueuedCompletionStatus接收PostQueuedCompletionStatus或IO的消息
                             // PostQueuedCompletionStatus调用DoXXX处理,IO的消息调用IO处理函数
 
     void  DoLoadConfig( const  Config& config);
     void  DoAddConnection(SOCKET client);
 
     void  OnReadComplite( int  connId, unsigned length);
     void  OnWriteComplite( int  connId);
private :
     HANDLE  _ioservice;
     Config config;
     std::map< int , ClientConnection*>  clients;
};

Acceptor我们就不用管了,就是开一个线程,等待客户端的连接;IoThread是一个IOCP的线程对象,只开了一个线程,Acceptor和主线程不直接操作IoThread的数据,而是POST到IOCP,例如上面的OnLoadConfig由主线程调用,AddConnection由Acceptor线程调用,两者都是异步的POST。IOCP收到这些请求会做分发,代码中的DoXXX就是真正的处理函数。因为IOCP线程只有 个,那么对config,clients这些数据的处理就不需要加锁了,这里的编程就是单线程编程(虽然里面有异步)。
其实还能做进一步的简化:取消Acceptor线程,因为IOCP本来就能实现异步的Accept。
好了我们现在只有主线程和一个IOCP线程了,主线程只做初始化工作,之后就休息了,一个单线程的程序,写起来多么简单呀!

那么你可能要问了,机子CPU有N个核,单线程?哪有什么高效性!别急,只稍加改动即可。比如你的CPU有N个核,那么主程序创建N个IoThread对象不就得了,每个IoThread对象都有一份独立的数据,如上例中的config,clients。多个IoThread对象没有增加复杂性。写代码的时候仍然只需要单线程编程的思路。

http://bbs.csdn.net/topics/370130831

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
最近有项目要做一个高性能网络服务器,决定下功夫搞定完成端口(IOCP),最终花了一个星期终于把它弄清楚了,并用C++写了一个版本,效率很不错。 但,从项目的总体需求来考虑,最终决定上.net平台,因此又花了一天一夜弄出了一个C#版,在这与大家分享。 一些心得体会: 1、在C#中,不用去面对完成端口的操作系统内核对象,Microsoft已经为我们提供了SocketAsyncEventArgs,它封装了IOCP的使用。请参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socketasynceventargs.aspx?cs-save-lang=1&cs-lang=cpp#code-snippet-1。 2、我的SocketAsyncEventArgsPool使用List对象来存储对客户端来通信的SocketAsyncEventArgs对象,它相当于直接使用内核对象时的IoContext。我这样设计比用堆栈来实现的好处理是,我可以在SocketAsyncEventArgsPool池中找到任何一个与服务器连接的客户,主动向它发信息。而用堆栈来实现的话,要主动给客户发信息,则还要设计一个结构来存储已连接上服务器的客户。 3、对每一个客户端不管还发送还是接收,我使用同一个SocketAsyncEventArgs对象,对每一个客户端来说,通信是同步进行的,也就是说服务器高度保证同一个客户连接上要么在投递发送请求,并等待;或者是在投递接收请求,等待中。本例只做echo服务器,还未考虑由服务器主动向客户发送信息。 4、SocketAsyncEventArgs的UserToken被直接设定为被接受的客户端Socket。 5、没有使用BufferManager ,因为我在初始化时给每一个SocketAsyncEventArgsPool中的对象分配一个缓冲区,发送时使用Arrary.Copy来进行字符拷贝,不去改变缓冲区的位置,只改变使用的长度,因此在下次投递接收请求时恢复缓冲区长度就可以了!如果要主动给客户发信息的话,可以new一个SocketAsyncEventArgs对象,或者在初始化中建立几个来专门用于主动发送信息,因为这种需求一般是进行信息群发,建立一个对象可以用于很多次信息发送,总体来看,这种花销不大,还减去了字符拷贝和消耗。 6、测试结果:(在我的笔记本上时行的,我的本本是T420 I7 8G内存) 100客户 100,000(十万次)不间断的发送接收数据(发送和接收之间没有Sleep,就一个一循环,不断的发送与接收) 耗时3004.6325 秒完成 总共 10,000,000 一千万次访问 平均每分完成 199,691.6 次发送与接收 平均每秒完成 3,328.2 次发送与接收 整个运行过程中,内存消耗在开始两三分种后就保持稳定不再增涨。 看了一下对每个客户端的延迟最多不超过2秒。
///////////////////////////////////////////////////////////////// // 初始化Socket bool CIOCPModel::_InitializeListenSocket() { // AcceptEx 和 GetAcceptExSockaddrs 的GUID,用于导出函数指针 GUID GuidAcceptEx = WSAID_ACCEPTEX; GUID GuidGetAcceptExSockAddrs = WSAID_GETACCEPTEXSOCKADDRS; // 服务器地址信息,用于绑定Socket struct sockaddr_in ServerAddress; // 生成用于监听的Socket的信息 m_pListenContext = new PER_SOCKET_CONTEXT; // 需要使用重叠IO,必须得使用WSASocket来建立Socket,才可以支持重叠IO操作 m_pListenContext->m_Socket = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED); if (INVALID_SOCKET == m_pListenContext->m_Socket) { this->_ShowMessage("初始化Socket失败,错误代码: %d.\n", WSAGetLastError()); return false; } else { TRACE("WSASocket() 完成.\n"); } // 将Listen Socket绑定至完成端口中 if( NULL== CreateIoCompletionPort( (HANDLE)m_pListenContext->m_Socket, m_hIOCompletionPort,(DWORD)m_pListenContext, 0)) { this->_ShowMessage("绑定 Listen Socket至完成端口失败!错误代码: %d/n", WSAGetLastError()); RELEASE_SOCKET( m_pListenContext->m_Socket ); return false; } else { TRACE("Listen Socket绑定完成端口 完成.\n"); } // 填充地址信息 ZeroMemory((char *)&ServerAddress, sizeof(ServerAddress)); ServerAddress.sin_family = AF_INET; // 这里可以绑定任何可用的IP地址,或者绑定一个指定的IP地址 //ServerAddress.sin_addr.s_addr = htonl(INADDR_ANY); ServerAddress.sin_addr.s_addr = inet_addr(m_strIP.GetString()); ServerAddress.sin_port = htons(m_nPort); // 绑定地址和端口 if (SOCKET_ERROR == bind(m_pListenContext->m_Socket, (struct sockaddr *) &ServerAddress, sizeof(ServerAddress))) { this->_ShowMessage("bind()函数执行错误.\n"); return false; } else { TRACE("bind() 完成.\n"); } // 开始进行监听 if (SOCKET_ERROR == listen(m_pListenContext->m_Socket,SOMAXCONN)) { this->_ShowMessage("Listen()函数执行出现错误.\n"); return false; } else { TRACE("Listen() 完成.\n"); } // 使用AcceptEx函数,因为这个是属于WinSock2规范之外的微软另外提供的扩展函数 // 所以需要额外获取一下函数的指针, // 获取AcceptEx函数指针 DWORD dwBytes = 0; if(SOCKET_ERROR == WSAIoctl
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值