S.l.e!ep.¢%

像打了激速一样,以四倍的速度运转,开心的工作
简单、开放、平等的公司文化;尊重个性、自由与个人价值;
posts - 1098, comments - 335, trackbacks - 0, articles - 1
  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

昨天优化了一下服务器的网络部分,测试了一下,在不考虑吞吐率的情况下,并发5W

昨天优化了一下服务器的网络部分,测试了一下,在不考虑吞吐率的情况下,并发5W

下面是俺的一些经验

1.基本结构

服务器结构如下图
33.JPG

使用2个不同的线程池来分别来处理 网络数据包的发送接收 以及 消息的处理.
这样可以避免繁重的业务处理导致网络数据包接收的阻塞.

根据服务器CPU情况创建线程. 服务器是2*4核心. 即双CPU, 每CPU上有4个核心.
在逻辑上就有8个处理单元.

在第1个CPU上的每个核心上创建x个线程用于发送和接收.
即: 发送接收线程池有线程数 x*4个,位于第1个CPU上.

在第2个CPU上的每个核心上创建y个线程,用于业务处理
即: 业务处理线程池有线程数 y*4个,位于第2个CPU上

具体的x,y应该按照实际的系统设置.设置的原则是:
1. 尽量小的线程上下文切换开销
2. 尽量高的CPU利用率(注意,是利用率,不是占用率)
一般来说,y>x

BOOL WINAPI SetProcessAffinityMask(
  __in          HANDLE hProcess,
  __in          DWORD_PTR dwProcessAffinityMask
);
//  此API用于设置进程的CPU亲缘属性,第2个参数是"位或"表示. 对于2*4核系统,则设置位0xFF

DWORD_PTR WINAPI SetThreadAffinityMask(
  __in          HANDLE hThread,
  __in          DWORD_PTR dwThreadAffinityMask
);
//  此API用于设置线程的CPU亲缘属性,第2个参数是"位或"表示. 需要注意的是,dwThreadAffinityMask必须是dwProcessAffinityMask的子集

DWORD WINAPI SetThreadIdealProcessor(
  __in          HANDLE hThread,
  __in          DWORD dwIdealProcessor
);
//  此API用于设置线程的首选CPU,操作系统在调度线程时优先考虑首选核心, 第2个参数是以0为基数的处理器ID

上述3个API都可以用来设置线程的执行单元是哪个. 一般来说,线程调度是由操作系统负责.人为的控制有时候反而会降低效率.但针对高负荷的线程处理,完全可以指定独立的CPU来优化.
比如,设定dwThreadAffinityMask=0xF,表示此线程在1-4核上执行,具体是哪个核还是由操作系统调度.这样可以将不同用途的线程分配到不同的CPU上,因为每个CPU有自己独立的L2 Cache,这样做可以避免不同类型线程在不同CPU之间切换带来的损失.
参考: 面向共享高速缓存多核系统的软件技术

上面所说的这些都只是理论,到实际的系统中,必须经过反复的性能对比试验来确定最佳方案

2. 登陆数据的接收
在IOCP中,经常使用AcceptEx来投递前置式的accept请求. 这里有一个问题: 登陆数据如何接收?
一般来说,登陆数据包是连接后的第1个或者第2个数据包.必须先保证一个TCP连接是信任的才能进行业务处理.有下列方法

方法1:
如果登陆数据包是第一个客户端发送的数据包,那么AcceptEx的dwReceiveDataLength 可以设置成登陆数据包大小,AcceptEx只有在收到此数据包后才投递到完成队列进行验证

方法2:
AcceptEx的dwReceiveDataLength 设置成0,表示在接收到连接后立即投递到完成队列.在完成队列中,投递WSARecv获得登陆数据包

方法3:
AcceptEx的dwReceiveDataLength 设置成0,表示在接收到连接后立即投递到完成队列.在Accept时,使用阻塞的recv来接收登陆数据包,如果recv超时,则踢掉连接

这3种方式没有哪种能够彻底的解决D.O.S的问题,只能在一定程度上缓解.
方法1:客户端只连接不发送数据,大量的这种连接会导致拒绝服务(一般采用附加线程定时检测超时)
方法2:客户端只连接不发送数据,大量的这种连接会导致拒绝服务(一般采用附加线程定时检测超时)
方法3:recv的超时时间设置很敏感,过大的超时时间同样会因为大量连接而拒绝服务(recv超时时间设定得如果合适是能够在一定程度上缓解)

3. 设置LINGER缩短连接关闭时间

//  ÉèÖÃSO_DONTLINGER
BOOL bDontLinger  =  FALSE;
::setsockopt( lpWsaOverlappedPlus
-> hSocket
    , SOL_SOCKET
    , SO_DONTLINGER
    , (
const   char * ) & bDontLinger
    , sizeof(BOOL)
    );

//  ÉèÖÃSO_LINGER
linger stLinger;
stLinger.l_onoff 
=   1 ;
stLinger.l_linger 
=   0 ;
::setsockopt( lpWsaOverlappedPlus
-> hSocket
    , SOL_SOCKET
    , SO_LINGER
    , (CHAR
* ) & stLinger
    , sizeof(linger)
    );

4. 修改注册表,修改TCP参数,具体的含义可以查阅MSDN
Windows Registry Editor Version  5.00

[ HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Performance ]
" MaxUserPort " = dword:0000fffe

[ HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters ]
" TCPFinWait2Delay  " = dword:0000001e
" SynAttackProtect " = dword: 1  
" TcpMaxPortsExhausted " = dword: 5  
" TcpMaxHalfOpen " = dword: 500  
" TcpMaxHalfOpenRetried " = dword: 400  
" TcpMaxConnectResponseRetransmissions " = dword: 2  
" TcpMaxDataRetransmissions " = dword: 2  
" EnablePMTUDiscovery " = dword: 0  
" KeepAliveTime " = dword: 300000  
" NoNameReleaseOnDemand " = dword: 1  
" DefaultTTL " = dword: 256  
" EnableDeadGWDetect " = dword: 0  
" DisableIPSourceRouting " = dword: 1  
" EnableFragmentChecking " = dword: 1  
" EnableMulticastForwarding " = dword: 0  
" IPEnableRouter " = dword: 0  
" EnableAddrMaskReply " = dword: 0  
" TcpTimedWaitDelay " = dword:0000001e

[ HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\AFD\Parameters ]  
" EnableICMPRedirect " = dword: 0  
" EnableDynamicBacklog " = dword: 1  
" MinimumDynamicBacklog " = dword: 20  
" MaximumDynamicBacklog " = dword: 20000  
" DynamicBacklogGrowthDelta " = dword: 10  

Feedback

# re: 昨天优化了一下服务器的网络部分,测试了一下,在不考虑吞吐率的情况下,并发5W   回复  更多评论   

2009-01-16 11:16 by true
一个很关键的问题:
对于一个客户端的收包及发包顺序是怎么判断的呢,举例:客户端给服务器传输文件,肯定是多次发包,同时服务器给客户端多次确认,这种情况,请问是怎么实现的同步的呢?或者是怎么解决的文件传输的呢

# re: 昨天优化了一下服务器的网络部分,测试了一下,在不考虑吞吐率的情况下,并发5W   回复  更多评论   

2009-01-16 18:07 by tzhang
可以提供个代码下载么?

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理