关闭

socket编程之登峰造极------完成端口(二)

标签: socket编程工作服务器api网络协议
2762人阅读 评论(0) 收藏 举报
分类:

socket编程之登峰造极------完成端口(二)

TagCreateIoComplet                                          

2.完成端口和重叠I/O

    将套接字句柄与一个完成端口关联在一起后,便可以套接字句柄为基础。投递发送或接

收请求。开始I/O请求的处理。接下来,可开始依赖完成端口,来接收有关I/O操作完成情况的通知。从本质上说、完成瑞口模型利用了Win32重叠I/O机制。在这种机制中。象WSASendWSARecv这样的Winsock API调用会立即返回。此时, 需要由我们的应用程序负责在以后的某个时间。通过一个OVERLAPPED结构,来接收调用的结果。在完成端口模型中。要想做到这一点,需要使用GetQueuedCompletionStatus(获取排队完成状态)函数。让一个或者多个工作者线程在完成端口上等待。该函数的定义如下:

BOOL GetQueuedCompletionStatus(

                               HANDLE CompletionPort,

                               LPDWORD lpNumberOfBytesTransferred,

                               LPDWORD lpCompletionKey,

                               LPOVERLAPPED *lpOverlapped,

                               DWORD dwMilliseconds

                               )

其中,

CompletionPort参数对应与要在上面等待的完成端口.

lpNumberOfBytesTransferred参数负责在完成了—次I/O操作后(WSASendWSARecv)、接收实际传输的字节数。

lpCompletionKey参数为原先传递进入CreateCompletionPort函数的套接字返回“单句柄数据”。如我们早先所述,大家最好将套接字句柄保存在这个“键”(Key)中。

lpOverlapped参数用于接收完成的I/O操作的重叠结果。这实际是一个相当重要的参数,因为要用它获取每个I/O操作的数据。

DwMilliseconds用于指定调用者希望等待一个完成数据包在完成端门上出现的时间。假如将其设为INFINITE。调用会无休止地等持下去。

    

3.单句柄数据和单I/O操作数据

    —个工作者线程从GetQueuedCompletionStatus这个API调用接收到I/O完成通知后。在lpCompletionKeylpOverlapped参数中,会包含—些必要的套接字信息。利用这些信息,可通过完成端口,继续在一个套接字上的I/O处理,通过这些参数。可获得两方面重要的套接字数据: 单句柄数据,以及单I/O操作数据。

    其中,lpCompletionKey参数包含了“单句柄数据”,因为在—个套接字首次与完成端口关联到—起的时候。那些数据便与一个特定的套接字句柄对应起来了。这些数据正是我们在进行CreateIoCompletionPort  API调用的时候,通过CompletionKey参数传递的。  如早先所述。应用程序可通过该参数传递任意类型的数据。通常情况下,应用程序会将与I/O请求有关的套接字句柄保存在这里。

    lpOVerlapped参数则包含了—个OVERLAPPED结构,在它后边跟随“单I/O操作数据”。

我们的工作者线程处理—个完成数据包时(将数据原封不动打转回去,接受连接,投递另—个线程,等等). 这些信息是它必须要知道的. I/O操作数据可以是追加到一个OVERLAPPED结构末尾的任意数量的字节。假如一个函数要求用到一个OVERLAPPED结构,我们便必须将这样的—个结构传递进去,以满足它的耍求。要想做到这一点,一个简单的方法是定义—个结构。然后将OVERLAPPED结构作为新结构的第一个元素使用。举个例子来说。  可定义上述数据结构,实现对单I/O操作数据的管理:

typedef  struct  {

  OVERLAPPED Overlapped;

  WSABUF    DataBuf;

  CHAR      Bufferl[DATA_BUFSIZE];

  BOOL      OperationType;

}PER_IO_OPERATION_DATA;

该结构演示了通常要与I/O操作关联在—起的某些重要数据元素,比如刚才完成的那个I/O操作的类型(发送或接收请求).在这个结构中。我们认为用于已完成I/O操作的数据缓冲区是非常有用的。要想调用—个Winsock API函数,同时为其分配一个OVERLAPPED结构,既可将自己的结构“造型”为一个OVERLAPPED指针,亦可简单地撤消对结构中的OVBRLAPPED元素的引用。如下例所示:

PER_IO_OPERATION_DATA PerIoData;

可以象下边这样调用一个函数

  WSARecv(socket,…,(OVERLAPPED *)&PerIoData;

或者象下边这样

  WSARecv(socket,…,&( PerIoData.Overlapped));

    在工作线程的后面部分。等GetQueuedCompletionStatus函数返回了—个重叠结构(和完成键)后。便可通过撤消对OperationType成员的引用。调查到底是哪个操作投递到了这个句柄之上(只需将返回的重叠结构造型为自的PER_IO_OPERATlON_DATA结构)。对单I/O操作数据来说,它最大的—个优点便是允许我们在同一个句柄上。同时管理多个I/O操作(/写、多个读、多个写,等等)。大家此时或许会产生这样的疑问:在同—个套接字上,真的有必要同时投递多个I/O操作吗?答案在于系统的“伸缩性”,或者说“扩展能力”。例如,假定我们的机器安装了多个中央处理器。每个处理器都在远行一个工作者线程,那么在同一个时候、完全可能有几个不同的处理器在同一个套接字上,进行数据的收发操作。

    为了完成前述的简单回应服务器示例,我们需要提供一个ServerWorkerThread(服务器工作者线程)函数。在程序消单8.10中,我们展示了如何设计一个工作者线程例程,令其使用单句柄数据以及单I/O操作数据,I/O请求提供服务。

     程序代码见下节……

在程序清单8-9和程序清单8-10列出的简单服务器示例中(配套光盘也有),最后要注意的一处细节是如何正确地关闭I/O完成端口一—特别是同时运行了一个或多个线程,在几个不同的套接字上执行I/O操作的时候。要避免的一个重要问题是在进行重叠I/O操作的同时,强行释放—个OVERLAPPED结构。要想避免出现这种情况,最好的办法是针对每个套接字句柄,调用closesocket函数。任何尚未进行的重叠I/O操作都会完成。—旦所有套接字句柄都已关闭。便需在完成端口上,终止所有工作者线程的运行。要想做到这一点,需要使用

PostQueuedCompletionStatus函数,向每个工作者线程都发送—个特殊的完成数据包。该函数会指示每个线程都“立即结束并退出”.下面是PostQueuedCompletionStatus函数的定义:

BOOL PostQueuedCompletionStatus(

    HANDLE CompletlonPort,

    DW0RD  dwNumberOfBytesTrlansferred,

    DWORD  dwCompletlonKey,

LPOVERLAPPED lpoverlapped,

);

    其中,CompletionPort参数指定想向其发送一个完成数据包的完成端口对象。而就dwNumberOfBytesTransferred,dwCompletionKeylpOverlapped这三个参数来说.每—个都允许我们指定—个值,直接传递给GetQueuedCompletionStatus函数中对应的参数。这样—来。—个工作者线程收到传递过来的三个GetQueuedCompletionStatus函数参数后,便可根据由这三个参数的某一个设置的特殊值,决定何时应该退出。例如,可用dwCompletionPort参数传递0,而—个工作者线程会将其解释成中止指令。一旦所有工作者线程都已关闭,便可使用CloseHandle函数,关闭完成端口。最终安全退出程序。

    4.其他问题

另外还有几种颇有价值的技术。可用来进—步改善套接字应用程序的总体I/O性能。值得考虑的一项技术是试验不同的套接字缓冲区大小,以改善I/O性能和应用程序的扩展能力。例如,假如某个程序只采用了—个比较大的缓冲区,仅能支持—个wSARecv请求,而不是同时设置了三个较小的缓冲区。提供对三个WSARecv请求的支持,那么该程序的扩展能力并不是很好,特别是在转移到安装了多个处理器的机器上之后。这是由于单独一个缓冲区每次只能处理一个线程!除此以外,单缓冲区设计还会对性能造成一定的干扰,假如—次仅能进行—次接收操作。网络协议驱动程序的潜力使不能得到充分发挥(它经常都会很“闲”)。换言之,假如在接收更多的数据前、需要等待—次WSARecv操作的完成,那么在WSARecv完成和下一次接收之间,整个协议实际上处于“休息”状态。

 另—个值得考虑的性能改进措施是用套接宇选项SO_SNDBUFSO_RCVBUF对内部套接字缓冲区的大小进行控制。利用这些选项,应用程序可更改—个套接字的内部数据缓冲区的大小。如将该设为0,Winsock便会在重叠I/O调用中直接使用应用程序的缓冲区、进行数据在

 

协议堆栈里的传人,传出。这样一来,在应用程序与Winsock之间,便避免了进行—次缓冲区复制的必要。下述代码片断阐释了如何使用SO_SNDBUF选项,来进行setsockopt函数的调用:

    setsockopt(socketSOL_S0CKET,SO_SNDBUF

    (char *)&nZero,sizeof(nZero));

要注意的是,将这些缓冲区的大小设为0,只有在一段给定的时间内,存在着多个I/O请求的前提下才会产生积极作用。等到第9章,我们会向大家更深入地讲述套接字选项的知识。

提升性能的最后一项措施是使用AcceptEx这个API调用,来进行连接请求的处理,并投递少量数据。这样一来,我们的应用程序只需通过一次API调用,便可为一次接受请求和数据的接收提供服务。从而减少了单独进行accept WSARecv调用造成的开销。这样做还有另一个好处,我们可使完成端口为AcceptEx提供服务,因为它也提供了一个OVERLAPPED结构。

假如事先预计到自己的服务器应用在一个连接建立好之后,只会进行少量的recv-send(收发)操作,那么AcceptEx便显得相当有用(比如在设计 一个Web服务器的时候)。否则的话,接受一个连接后,假如程序要负责数百上千次数据 的传输操作,这样的对性能便没有多大的助益。

最后提醒大家,注意,在Winsock中,一个Winsock应用不应使用ReadFile WriteFile 这两个Win32函数,在一个完成端口上进行IO处理。尽管这两个函数确实提供了一个OVERLAPPED结构,而且可在完成端口上成功的使用,但就在Winsock 2环境下进行IO处理来说,WSARecv WSASend 这两个函数却进行了更大程序的优化。若使用ReadFile WriteFile ,需在进行大量不必要的内核/用户模式进行调用、线程执行场景的频繁切换以及参数的汇集等等,使总体性能大打折扣

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:186955次
    • 积分:2273
    • 等级:
    • 排名:第16499名
    • 原创:32篇
    • 转载:74篇
    • 译文:0篇
    • 评论:15条
    最新评论
    名家专栏