态连接mswsock.lib,在程序中直接调用函数),那么性能将很受影响。因为 AcceptEx()被置于Winsock2架构之外,每次调用时它都被迫通过
WSAIoctl()取得函数指针。要避免这种性能损失,需要使用这些API的应用程序应该通过调用WSAIoctl()直接从底层的提供者那里取得函数的指针。
参见Figure 3 套接字架构:
application || ||/ /
winsock 2.0 dll (ws2_32.dll) || ||/ /
layered/Base Providers
RSVP | Proxy | Default Microsoft Providers (mswsock.dll/msafd.dll) || ||/ /
Windows Sockets kernel-mode driver (afd.sys) || ||/ /
Tramsport Protocols TCP/IP | ATM | Other
TransmitFile 和 TransmitPackets Winsock
提供两个专门为文件和内存数据传输进行了优化的函数。其中
TransmitFile()这个API函数在Windows NT 4.0 和 Windows 2000上都可以使用,而TransmitPackets()则将在未来版本的Windows中实现。
TransmitFile()用来把文件内容通过Winsock进行传输。通常发送文件的做法是,先调用CreateFile()打开一个文件,然后不断循环调用ReadFile() 和WSASend ()直至数据发送完毕。但是这种方法很没有效率,因为每次调用ReadFile() 和 WSASend ()都会涉及一次从用户模式到内核模式的转换。如果换成TransmitFile(),那么只需要给它一个已打开文件的句柄和要发送的字节数,而所涉及的模式转换操作将只在调用CreateFile()打开文件时发生一次,然后TransmitFile()时再发生一次。这样效率就高多了。
TransmitPackets()比TransmitFile()更进一步,它允许用户只调用一次就
可以发送指定的多个文件和内存缓冲区。函数原型如下: BOOL TransmitPackets(
SOCKET hSocket, LPTRANSMIT_PACKET_ELEMENT lpPacketArray, DWORD nElementCount, DWORD nSendSize,
LPOVERLAPPED lpOverlapped, DWORD dwFlags ;
其中,lpPacketArray是一个结构的数组,其中的每个元素既可以是一个文件句柄或者内存缓冲区,该结构定义如下:
typedef struct _TRANSMIT_PACKETS_ELEMENT { DWORD dwElFlags; DWORD cLength; union {
struct {
LARGE_INTEGER nFileOffset; HANDLE hFile; };
PVOID pBuffer; };
} TRANSMIT_FILE_BUFFERS;
其中各字段是自描述型的(self explanatory)。
dwElFlags字段:指定当前元素是一个文件句柄还是内存缓冲区(分别通过常量TF_ELEMENT_FILE和TF_ELEMENT_MEMORY指定); cLength字段:指定将从数据源发送的字节数(如果是文件,这个字段值为0表示发送整个文件);
结构中的无名联合体:包含文件句柄的内存缓冲区(以及可能的偏移量)。
使用这两个API的另一个好处,是可以通过指定TF_REUSE_SOCKET和
TF_DISCONNECT标志来重用套接字句柄。每当API完成数据的传输工作后,就会在传输层级别断开连接,这样这个套接字就又可以重新提供给AcceptEx()使用。采用这种优化的方法编程,将减轻那个专门做接受操作的线程创建套接字的压力(前文述及)。
这两个API也都有一个共同的弱点:Windows NT Workstation 或 Windows 2000 专业版中,函数每次只能处理两个调用请求,只有在Windows NT、Windows 2000服务器版、Windows 2000高级服务器版或 Windows 2000 Data Center中才获得完全支持。
完成端口详解(4) [转]
放在一起看看
以上各节中,我们讨论了开发高性能的、大响应规模的应用程序所需的函数、方法和可能遇到的资源瓶颈问题。这些对你意味着什么呢?其实,这取决于你如何构造你的服务器和客户端。当你能够在服务器和客户端设计上进行更好地控制时,那么你越能够避开瓶颈问题。
来看一个示范的环境。我们要设计一个服务器来响应客户端的连接、发送请求、接收数据以及断开连接。那么,服务器将需要创建一个监听套接字,把它与某个完成端口进行关联,为每颗CPU创建一个工作线程。再创建一个线程专门用来发出AcceptEx()。我们知道客户端会在发出连接请求后立刻传送数据,所以如果我们准备好接收缓冲区会使事情变得更为容易。当然,不要忘记不时地轮询AcceptEx()调用中使用的套接字(使用SO_CONNECT_TIME选项参数)来确保没有恶意超时的连接。
该设计中有一个重要的问题要考虑,我们应该允许多少个AcceptEx()进行守候。这是因为,每发出一个AcceptEx()时我们都同时需要为它提供一个接收缓冲区,那么内存中将会出现很多被锁定的页面(前文说过了,每个重叠操作都会消耗一小部分未分页内存池,同时还会锁定所有涉及的缓冲区)。这个问题很难回答,没有一个确切的答案。最好的方法是把这个值做成可以调整的,通过反复做性能测试,你就可以得出在典型应用环境中最佳的值。
好了,当你测算清楚后,下面就是发送数据的问题了,考虑的重点是你希望服务器同时处理多少个并发的连接。 通常情况下,服务器应该限制并发连接的数量以及等候处理的发送调用。因为并发连接数量越多,所消耗的未分页内存池也越多;等候处理的发送调用越多,被锁定的内存页面也越多(小心别超过了极限)。这同样也需要反复测试才知道答案。
对于上述环境,通常不需要关闭单个套接字的缓冲区,因为只在AcceptEx()中有一次接收数据的操作,而要保证给每个到来的连接提供接收缓冲区并不是太难的事情。但是,如果客户机与服务器交互的方式变一变,客户机在发送了一次数据之后,还需要发送更多的数据,在这种情况下关闭接收缓冲就不太妙了,除非你想办法保证在每个连接上都发出了重叠接收调用来接收更多的数据。
结论
开发大响应规模的Winsock服务器并不是很可怕,其实也就是设置一个监听套接字、接受连接请求和进行重叠收发调用。通过设置合理的进行守候的重叠调用的数量,防止出现未分页内存池被耗尽,这才是最主要的挑战。按照我们前面讨论的一些原则,你就可以开发出大响应规模的服务器应用程序。
(完)
Tip 1 : 使用WSASend/WSARecv来收发数据,而不是使用ReadFile/WriteFile 一句话,前者具有更好的性能
Tip 2: 理解IOCP的最大并发线程数和工作线程数
应该让工作线程数(调用GetQueuedCompletionStatus那些线程)大于等于在CreateIoCompletionPort 指定的NumberOfConcurrentThreads数。 标准做法是永远设置NumberOfConcurrentThreads=0
Tip 3: 利用GetQueuedCompletionStatus的completion key和overlapped structure参数在异步操作中来传递信息
通常completion key用来传递和handle/socket/session的信息
而overlapped structure用来传递每次异步I/O的一些信息,通常的做法是会定义一个structure来派生于OVERLAPPED
struct MY_IO_DATA : public OVERLAPPED Tip 4: 理解IOCP的完成包的排队行为
从GetQueuedCompletionStatus得到完成包的次序可能跟调用WSASend/WSARecv的次序不一样。
微软唯一保证是如果调用WSASend/WSARecv得到SUCCESS或者IO_PENDING,就一定会有一个完成包出现在IOCP的队列上,不管这个socket是否关闭了。
如果关闭socket,那么之后的WSASend/WSARecv调用就一定返回失败的结果。 关于IOCP包可能次序错乱和解决方法,有一篇文章可以参考: http://www.codeproject.com/KB/IP/reusablesocketserver4.aspx 我的做法是避免多次调用WSARecv
Tip 5: IOCP的清除
最重要的一点是,在I/O完成之前,不要释放overlapped structure。可以用HasOverlappedIoCompleted来监测OV是否完成。 通常的做法是
1) 调用PostQueueCompletionStatus N次(N=工作线程数),来传递特殊的退出信息给所有的工作线程
2) 关闭所有的socket,如果很在意处理完未完成的数据包,需要使用一个计数器来跟踪异步I/O事件,直到计数器为0,才关闭相应的socket 3) 关闭completion port
(完)
IOCP 完成端口心得
2009年05月16日 星期六 10:31 P.M.
嗯前不久搞了下IOCP,因为网络服务器端模型用这个最OK了,刚看,还以为是port,是SOCKET的那个端口,结果一点关系也没有,很郁闷,后来想想也是,优化端口干什么,换几个端口还完成??好象不可能这样理解...搞了老半天就是I_O的异步,然后控制多个线程,不浪费线程用户与内河的切换,充分利用CPU... 用这个模型不错的,用CreateIoCompletionPort创建一个完成端口,然后TCP的话连接进来的每一个SOCKET再用CreateIoCompletionPort绑定,然后就是工作线程的GetQueuedCompletionStatus完成消息了.还可以用
PostQueuedCompletionStatus来给完成消息中添加自己的消息!至于怎么用IOCP,网上文章很多,我就不多说了,那我就是想说说IOCP中的经常会遇到到问题,
首先要创建多少个IOCP工作线程呢,一致认为是CPU个数的两备再加二.就是这样
SYSTEM_INFO SystemInfo;
GetSystemInfo(&SystemInfo);
for(UINT i = 0; i < SystemInfo.dwNumberOfProcessors*2+2; i++) {
HANDLE
hProcessIO=CreateThread(NULL,0,ProcessIO,&m_ThreadStruct,0,NULL); CloseHandle(hProcessIO); }
嗯应该就是这样了...
问题最严重的是缓冲问题,也就是说WSARecv和WSASend的问题,千万别在调用后就释放,也就是说delete内存...一定要在完成消息返回中处理.到底是需要内存就申请,不需要就释放呢?反复申请释放内存会导致内存碎片,导致性能降低,这不是我们所需要的,这样可以使用内存池,内存池大家可以去找找,注意若多个工作线程使用一个内存池,可要同步好,临界区是最好的选择.若你的服务器响应模式是一个命令,返回一条响应消息,那将WSARecv的缓冲内存放在单句柄数据中,接到信息后投递一个WSARecv,这样对于一个用户只有一个WSARecv请求.嗯,这样是不是很棒呢.只要我们在用户断开的时候将他的单句柄数据删了就行了.\怎么得到用户的单句柄?\你问我这个问题的话,外行了不是,自己去