昨天优化了一下服务器的网络部分,测试了一下,在不考虑吞吐率的情况下,并发5W
下面是俺的一些经验
1.基本结构
服务器结构如下图
使用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