一 串行模式和并行模式
一般一个服务应用程序采用以下两个架构模型之一:
- 串行模式 一个线程等待一个客户发出的请求,当请求到达的时候,线程会被换醒来处理客户的请求。
- 并发模式。一个线程等待一个客户发出的请求,当请求到达的时候,线程会创建一个新的线程来处理客户的请求,而当前线程则会进入下一次循环继续等待客户的请求。
在当前多客户请求的环境下,串行模式远远不能满足当前服务器的性能要求。串行模式每次只能处理一个客户请求,而现在的计算机大多数都是多处理器的机器,所以不充分发挥出多处理器机器的优势。所以串行模式只能应用于一些简单的服务器应用程序,向Ping服务器程序。
由于串行模式的受限,所以在服务器应用中一般采用并发模式。在并发模式中,当服务器接受到一个客户请求时,会创建一个新的线程来处理客户请求。这种模式的优点是显而易见的,等待请求的线程只有很少的工作需要做。当客户请求到达的时候,该线程会被唤醒,创建一个新的处理线程,然后继续睡大觉。所以,服务器可以很快的对客户请求进行相应。而同时,每个客户请求都有一个线程来对其进行处理,能够发挥多处理器机器的优势。但是,这并不是没有缺点的:当一个机器中的线程远大于处理器的数目时,频繁的线程切换会极大的浪费CPU的性能。例如,如果有2000个客户请求,当前的并发模型就需要2000个线程来处理请求,每个线程都需要大量的虚拟内存空间。系统默认的每个线程消耗1MB的堆栈空间,则2000个线程消耗2G虚拟地址空间,而且线程上下文的切换会增加页错误,同时会CPU在线程的上下文切换上需要浪费太多的时间,而线程真正得到CPU执行时间很少。同时,虽然创建一个线程的开销相对于创建进程小的多,但是它的开销仍然不小。如果应用程序能够在初始化的时候创建一个线程池,这样当需要调用线程的时候直接从线程池中获取,这样便可以大大的提升性能了。
正是为了解决以上问题,Windows提出了I/O完成端口解决方案,使得我们在线程池中创建有限数量的服务器线程,这个线程池的调用由Windows系统来维护。同时使用大量的套接字或命名管道句柄,让每个句柄都与不通的客户相关联,句柄不和服务器线程配对。当一个重叠的I/O完成后,会被加入到完成队列中,系统会从线程池中唤醒一个线程来处理完成的I/O请求。
二 I/O完成端口解析
1 创建I/O完成端口
创建I/O完成端口调用函数:
HANDLE
WINAPI
CreateIoCompletionPort(
__in HANDLE FileHandle,
__in_opt HANDLE ExistingCompletionPort,
__in ULONG_PTR CompletionKey,
__in DWORD NumberOfConcurrentThreads
);
- FileHandle
FileHandle是要添加到端口中的一个重叠的设备句柄,注意一个I/O完成端口是以OVERLAPPED模式打开的文件句柄的集合。如果创建一个新的I/O完成端口,则此值为INVALID_HANDLE_VALUE,同时参数ExistingCompletionPort为空。
- ExistingCompletionPort
ExistingCompletionPort表示文件句柄将要关联的端口。
- CompletionKey
CompletionKey指定在完成包中为FileHandle所包含的键值。这个键值可以指向一个结构的指针,该结构包含包含了诸如操作类型、句柄以及指向数据缓冲区指针等信息。
- NumberOfConcurrentPort
表示允许并发执行的线程的最大数量,超过这个数量的等待端口的线程将保持阻塞状态。如果为0,那么最大的并发执行的线程数就是系统中处理器的数目。当ExistingCompletionPort不为NULL时,这个值会被忽略。
这个函数其实是完成两个任务:一是创建一个I/O完成端口,二是将一个设备与一个I/O完成端口关联起来。只是该函数将所有的参数放到一起,显得有点复杂。我们可以将其进行功能拆分,创建两个小函数来分别完成以上两个任务:
//创建新的I/O完成端口 HANDLE CreateNewCompletionPort(DWORD dwNumOfConcurrentThread) { return CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, dwNumOfConcurrentThread); } //将设备和IO完成端口相关联 BOOL AssociateDeviceWithCompletionPort(HANDLE hDevice, HANDLE hCompletionPort, DWORD dwCompKey) { HANDLE h = CreateIoCompletionPort(hDevice,hCompletionPort,dwCompKey,0); return (h == NULL); }