提高网络通信组件的性能

尽量少等待

  1. 如何检测有新的客户端连接的到来?
  2. 如何接受客户端的连接请求?
  3. 如何检测客户端是否有数据发送过来?
  4. 如何收取客户端发送的数据?
  5. 如何检测异常的客户端连接?检测到之后,如何处理?
  6. 如何向客户端发送数据?
  7. 如何在客户端发送完数据后关闭连接?

对于第1、3个问题,使用I/O复用技术的select、poll、epoll等相关套接字函数。
对于第2个问题,使用socket API accept函数。
对于第4个问题,使用recv函数
对于第6个问题使用send函数。
能尽量满足少等待原则的程序就是高性能的。最好不仅不需要等待,而且在网络操作完成时能通知我们,利用等待的时间让程序做其他事情。

尽量减少做无用功的时间

目前Windows支持的I/O复用技术有select、WSAAsyncSelect、WSAEventSelect和IOCP(完成端口),Linux支持的I/O复用技术有select、poll和epoll模型。以上列举的I/O复用函数可以分为以下两个级别。

  • 第1级别:select和poll。
  • 第2级别:WSAAsyncSelect、WSAEventSelect、IOCP、epoll。
    第1级别的函数。在本质上还是在一定时间内主动查询在一组socket句柄上是否有网络事件,也就是我们必须每隔一段时间就主动做这些检测操作。

第2级别的函数。相当于变主动查询为被动通知,即网络事件发生时,系统会通知我们处理。只不过第2级别的丽数通知我们的方式各不相同,WSAAsyncSelect函数利用Windows窗口消息队列的事件机制将通知发给我们设置的窗口过程函数,IOCP模型利用GetQueuedCompletionStatus函数从挂起状态唤醒并返回,epoll 模型利用epoll wait函数返回就绪事件。

对网络通信组件的性能有高要求时,尽量不要主动查询各个socket事件,而是等待操作系统通知我们。

检测网络事件的高效做法

在高性能服务器设计中一般将socket设置成非阻塞模式,利用I/O复用函数检测各个socket上的事件。

对于第1、2个问题,在默认情况下,如果没有新的客户端连接请求,则对监听soket(调用bind和listen函数的socket)调用acept函数会阻塞调用线程,使用I/O复用函数以后,如果epoll wait 函数检测到监听socket有EPOLLIN事件,或者WSAAsyneSelect函数检测到有FD_ ACCEPT事件,就表明此时有新连接到来,再调用acept函数就不会阻塞调用线程了。

对于第3、4个问题,调用accept函数返回的新socket 也应该被设置成非阻塞模式,而且应该在epollwait或WSAAsyncSelect丽数报告这个socket有可读事件时收取数据,这样才不会做无用功。那么一次性收取多少数据合适呢?可以根据自己的实际需求来决定,甚至可以在一一个循环中反复调用recv(或read)函数。对于非阻塞模式的socket,如果没有数据可读,则reev(或read) 函数会立即返回( 返回值是-1 ),此时得到的错误码EWOULDBLOCK (或EAGAIN)表明当前已经没有数据了。

对于第5个问题,同样,若IO复用函数(如epoll wait、WSAAsyncSelect) 收到异常事件(如EPOLLERR)或关闭事件(如FD_CLOSE)的通知,我们就知道有异常产生了,对异常的处理一-般是关闭相应的socket。另外,如果send/recv (或read/write) 函数操作某个socket时返回0,则一般可以认为对端关闭了连接,对于本端,此时这路连接也没有存在的必要了,我们可以关闭本端对应的socket。需要说明的是,TCP连接是状态机,I/O复用函数一般无法检测出两个端点之间路由错误导致的链路问题,所以对于这种情形,我们需要通过定时器结合心跳包来检测。

丘俊伟 22:54:52
对于第6个问题,向客户端发送数据比收取数据稍微麻烦一点,也是需要讲究技巧的。对于epoll模型的水平触发模式( Level Tigger),我们首先不能像检测读事件一样一开始就注册检测写事件标志,因为一旦注册了检测写事件标志,则在一般情况下, 只要对端正常收取数据,对应的socket就通常是可写的,这会导致频繁触发写事件通知。但并不是每次有写事件触发时都有数据要发送,所以正确的做法是:在epoll模型水平触发模式下,如果有数据要发送,则先调用sendl或write函数尝试直接发送;如果发送不了或者只发送出去部分数据,则将剩余的数据先缓存起来(需要一个缓冲区来存放剩余的数据,即“发送缓冲区”),再为该socket注册检测写事件标志,等下次写事件触发时再发送剩余的数据;如果剩下的数据还是不能完全发送完,则继续等待下一次写事件触发通知,在下一次写事件触发通知后,继续发送数据。如此反复,直到所有数据都发送出去为止。一旦所有数据都发送出去了,就及时为socket移除检测写事件标志,避免再次触发无用的写事件通知。

对于第7个问题,比较难处理,因为这里的“发送完"不一定是真的发送完,发送数据用的send或write函数即使返回成功,也只能说明向操作系统网络协议栈里面写人数据成功,并不代表数据被成功发送到网络。至于最后操作系统协议栈中的数据能否被发送出去及何时被发送出去,很难判断,发送出去后对方是否收到,就更难判断了。所以,我们目前只能简单地认为send或者write 函数返回我们期望的字节数时,就算数据发送完成。在这种情形下发送“完”数据后,就可以关闭连接了。当然,我们也可以使用shutdown函数达到“半关闭”效果(即只关闭socket的发送或接收通道)。

来源:C++服务器开发精髓

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值