socket复习

今天Jun和我说起一个C/S的网络模型,自己很久没写网络应用了,这块的东西生疏了很多。总结复习一下。

socket有两个模式,block和非block。阻塞型比较简单,Jun的应用就基于这个模式。非阻塞型的适合于较多客户端和较大数据量的处理,高性能的server必须用非阻塞方式实现。针对非阻塞模式又有多种的I/O处理模型可以使用。

block模式下,socket函数会一直等待直到数据处理完成。比如recv,会等待到从socket缓冲区中获得数据后在返回,此期间,调用此函数的线程就处于等待状态。block模式实现非常简单方便,只要实现一个send thread负责发数据,一个read thread负责收数据就好了。如果对数据的及时处理要求高,就再开个数据处理线程,注意好和read线程的数据同步就行了。但block模式下,应用程序很难同时处理多个socket连接。如果要处理大量连接,程序的灵活性和效率都很成问题。

非block模式下,socket的函数都是调用后立即返回的,所以程序的处理方式和block模式完全不同了。在调用recv函数时,如果缓冲区内没有数据,函数会立即返回一个WOULDBLOCK错误过来。那何时才能知道缓冲区内有数据,而又怎么才能正确的收取到数据呢?最简单的方法,是反复的调用recv,直到收到数据为止。这样的方式,和block模式就没什么区别了,甚至更糟,因为会产生无数次无意义的函数调用,通常是不会采用这种方法的。

为了解决非blcok模式中何时进行socket操作这个问题,有这么几种I/O模型:

select,WSAsyncSelect,WSEventSelect,Overlapped I/O,IO Completion Port

select是socket诞生时即产生的基本模式,在Berkeley Socket中就得到实现了。利用select函数,可以判断一个或一组socket上是否存在数据,或者是否能够写入数据。select接收一组socket作为参数,并检查这些socket上是否满足参数中所规定的条件,并将结果返回出来。比较特别的一点是,select函数本身是block状态的;也就是说,调用select后,函数会等待到检测结果完成,这就表明此时所检测的socket是可用的了。在这个时候,可以在此socket上进行需要的操作,例如send或recv。

概念讲起来有点抽象,实际则非常易用。比如我们accept了一个socket,现在准备收数据。则首先调用select( socket, NULL, NULL),如果此时缓冲区中没有数据,则select函数不会返回,一直停在那。等网络数据过来了,select就会返回。接着执行下一行代码,往往就是recv(socket)。嗯,非常简单。

select设计出来的目的是防止block socket因为不清楚当前socket状况而进入到block状态,同时也可以防止非block的socke函数总是产生WOULDBLOCK错误。在进行socket IO操作前,用select判断一下,就可以执行所希望的操作了。

WSAsyncSelect和WSAEventSelect类似,是基于事件消息为基础的。WSAsyncSelect可以将一个socket绑定到一个窗口上,当然这个窗口要有相应的window proc以维护消息循环。当此socket上有I/O事件时,对应的窗口会接收到指定的消息和操作类型。得到通知后就可以进行I/O操作了。一个小技巧是多个socket可以绑定到同一个窗口上。

WSAEventSelect则是将socket绑定到一个event句柄上,当有指定的I/O事件,则将event置为signaled。程序使用WSAWaitForMultiEvents来等待一组事件,当有event变成signaled时函数返回,然后可以使用WSAEnumNetEvents来判断是何类型的事件,再进行相应的操作。WASWaitForMultiEvents同时只能等待64个events,这意味着,如果你需要管理多于64个socket,则需要再增加线程的数量。

overlapped I/O我没用过,是基于WIN32 Overlapped机制的高性能架构。在这种模型下,socket函数会立即返回(连用来发或收数据的那么一小点时间都不用),并且调用时有一个特殊的参数,WSAOVERLAPPED结构。I/O操作的真正结果是由这个特殊结构WSAOVERLAPPED来负责通知的,有两种方式:事件对象和完成例程。

使用事件对象方式时,和WSAEventSelect非常类似,根据WSAOVERLAPPED结构中的event状态来判断I/O操作是否完成。使用完成例程方式时,则是在重叠I/O完成时调用事先实现好的指定的一些函数。overlapped I/O的优势在于系统会高效率的调度多个同时发生的I/O操作。

Completion Port I/O(IOCP)曾经花功夫研究过一阵,是目前最复杂而最能发挥系统性能的模型。如果要处理超大规模的并发,其效率是非常高的。另外一个显著的好处是性能可以随CPU数量的上升而线性提升。完成端口也是WIN32的一个I/O模型,不禁止于socket。

IOCP以指定数量的线程,对overlapped I/O进行管理,使用这些线程来为已经完成的重叠I/O请求提供服务。在重叠I/O的完成例程方式下,服务例程还是运行于socket所在线程上下文的,而IOCP则使用线程池的方式,将I/O操作分散到多个线程中以尽可能发挥系统性能。使用IOCP,首先要创建一个完成端口,和若干个worker thread,并指定在一个完成端口上,同一时刻允许运行的线程个数。考虑到总有些线程是锁定或者挂起状态,创建的线程数量一般比指定的峰值线程数要多些。然后将socket和完成端口绑定。

准备工作做好后,以使用重叠I/O的方法开始进行I/O操作,不同的时,投递过I/O请求后,使用GetQueuedCompletionStatus来使线程池中的work thread等待I/O事件,并在事件达到后进行相应的处理。

额外的,说下CSocket.CSocket是基于的WSAAsyncSelected模型的,在异步模式的基础上,也提供了同步方式的封装。CSocket虽然效率不那么高,封装的却很“漂亮”。从unix或者纯socket程序员的角度看,会觉得CSocket简直就不是个socket实现,和socket通常的编程方式相差很远。而从一个没有使用过纯socket的MFC程序员的角度看来,CSocket和MFC的风格非常统一,学习和使用成本很低。在PC及网络性能优异的今天,CSocket是轻松构架网络程序的很好选择。

posted on 2007-01-22 10:37  Realloc  阅读(197)  评论(0)    收藏  举报

导航