通常要开发网络应用程序并不是一件轻松的事情,不过,实际上只要掌握几个关键的原则 也就可以了——创建和连接一个套接字,尝试进行连接,然后收发数据。真正难 的是要写出一个可以接纳少则一个,多则数千个连接的网络应用程序。本文将讨论如何通过 Winsock2在Windows NT 和 Windows 2000上开发高扩展能力的Winsock应用程序。文章主要的 焦点在客户机/服务器模型的服务器这一方,当然,其中的许多要点对模型的双方都适用。
API与响应规模
通过Win32的重叠I/O机制,应用程序可以提请一项I/O操作,重叠 的操作请求在后台完成,而同一时间提请操作的线程去做其他的事情。等重叠操作完成后线 程收到有关的通知。这种机制对那些耗时的操作而言特别有用。不过,像Windows 3.1上的 WSAAsyncSelect()及Unix下的select()那样的函数虽然易于使用,但是它们不能满足响应规 模的需要。而完成端口机制是针对操作系统内部进行了优化,在Windows NT 和 Windows 2000上,使用了完成端口的重叠I/O机制才能够真正扩大系统的响应规模。
完成端口
一个完成端口其实就是一个通知队列,由操作系统把已经完成的重叠I/O请求的通知 放入其中。当某项I/O操作一旦完成,某个可以对该操作结果进行处理的工作者线程就会收到 一则通知。而套接字在被创建后,可以在任何时候与某个完成端口进行关联。
通常情 况下,我们会在应用程序中创建一定数量的工作者线程来处理这些通知。线程数量取决于应 用程序的特定需要。理想的情况是,线程数量等于处理器的数量,不过这也要求任何线程都 不应该执行诸如同步读写、等待事件通知等阻塞型的操作,以免线程阻塞。每个线程都将分 到一定的CPU时间,在此期间该线程可以运行,然后另一个线程将分到一个时间片并开始执行 。如果某个线程执行了阻塞型的操作,操作系统将剥夺其未使用的剩余时间片并让其它线程 开始执行。也就是说,前一个线程没有充分使用其时间片,当发生这样的情况时,应用程序 应该准备其它线程来充分利用这些时间片。
完成端口的使用分为两步。首先创建完成 端口,如以下代码所示:
HANDLE hIocp;
hIocp = CreateIoCompletionPort(
INVALID_HANDLE_VALUE,
NULL,
(ULONG_PTR)0,
0);
if (hIocp == NULL) {
// Error
}
完成端口创建后,要把将使用该完成端口的套接字与之关联起来。方法是再次调用 CreateIoCompletionPort ()函数,第一个参数FileHandle设为套接字的句柄,第二个参数 ExistingCompletionPort 设为刚刚创建的那个完成端口的句柄。
以下代码创建了一 个套接字,并把它和前面创建的完成端口关联起来:SOCKET s;
s = socket (AF_INET, SOCK_STREAM, 0);
if (s == INVALID_SOCKET) {
// Error
if (CreateIoCompletionPort((HANDLE)s,
hIocp,
(ULONG_PTR)0,
0) == NULL)
{
// Error
}
...
}