只为方便自己查阅,原文地址:传送门
客户端:创建套接字,连接服务器,然后不停的发送和接收数据。
比较容易想到的一种服务器模型就是采用一个主线程,负责监听客户端的连接请求,当接收到某个客户端的连接请求后,创建一个专门用于和该客户端通信的套接字和一个辅助线程。以后该客户端和服务器的交互都在这个辅助线程内完成。这种方法比较直观,程序非常简单而且可移植性好,但是不能利用平台相关的特性。例如,如果连接数增多的时候(成千上万的连接),那么线程数成倍增长,操作系统忙于频繁的线程间切换,而且大部分线程在其生命周期内都是处于非活动状态的,这大大浪费了系统的资源。所以,如果你已经知道你的代码只会运行在Windows平台上,建议采用Winsock I/O模型。
一.Select模型: 轮询fd_set集合
利用select函数,实现对I/O 的管理。最初设计该模型时,主要面向的是某些使用UNIX操作系统的计算机,它们采用的是Berkeley套接字方案。Select模型已集成到 Winsock 1.1中,它使那些想避免在套接字调用过程中被无辜“锁定”的应用程序,采取一种有序的方式,同时进行对多个套接字的管理。
int nfds ,
fd_set* readfds ,
fd_set* writefds ,
fd_set* exceptfds ,
const struct timeval* timeout
);
readfds:(可选)指针,指向一组等待可读性检查的套接口。
writefds:(可选)指针,指向一组等待可写性检查的套接口。
exceptfds:(可选)指针,指向一组等待错误检查的套接口。
timeout:select()最多等待时间,对阻塞操作则为NULL。
FD_CLR(s,*set):从集合set中删除描述字s。
FD_ISSET(s,*set):若s为集合中一员,非零;否则为零。
FD_SET(s,*set):向集合添加描述字s。
FD_ZERO(*set):将set初始化为空集NULL。
timeout参数控制select()完成的时间。若timeout参数为空指针,则select()将一直阻塞到有一个描述字满足条件。否则的话,timeout指向一个timeval结构,其中指定了select()调用在返回前等待多长时间。如果timeval为{0,0},则 select()立即返回,这可用于探询所选套接口的状态。
服务器来轮询查看某个套接字是否仍然处于读集中,如果是,则接收数据。如果接收的数据长度为0,或者发生WSAECONNRESET错误,则表示客户端套接字主动关闭,这时需要将服务器中对应的套接字所绑定的资源释放掉,然后调整我们的套接字数组(将数组中最后一个套接字挪到当前的位置上)
除了需要有条件接受客户端的连接外,还需要在连接数为0的情形下做特殊处理,因为如果读集中没有任何套接字,select函数会立刻返回。
当调用非阻塞模式时,可以说socket在select上设置超时时间阻塞应用。
select 会在超时时间测试fd_set集合是否可用,如果超时/没有数据可读了会清除当前集合成员。
二.异步选择
应用程序可以在一个套接字上接收以WINDOWS消息为基础的网络事件通知。该模型的实现方法是通过调用WSAAsynSelect函数 自动将套接字设置为非阻塞模式,并向WINDOWS注册一个或多个网络时间,并提供一个通知时使用的窗口句柄。当注册的事件发生时,对应的窗口将收到一个基于消息的通知。三.事件选择
Winsock 提供了另一个有用的异步I/O模型。和WSAAsyncSelect模型类似的是,它也允许应用程序在一个或多个套接字上,接收以事件为基础的网络事件通知。基本思想是将每个套接字都和一个WSAEVENT对象对应起来,并且在关联的时候指定需要关注的哪些网络事件。一旦在某个套接字上发生了我们关注的事件(FD_READ和FD_CLOSE),与之相关联的WSAEVENT对象被Signaled。
四.重叠I/O模型
readfile或者writefile的调用马上就会返回,这时候你可以去做你要做的事,系统会自动替你完成readfile或者writefile,在你调用了readfile或者writefile后,你继续做你的事,系统同时也帮你完成readfile或writefile的操作,这就是所谓的重叠。
1.用事件通知方式实现的重叠I/O模型
异步I/O函数WSARecv。在调用WSARecv时,指定一个 WSAOVERLAPPED结构,这个调用不是阻塞的,也就是说,它会立刻返回。一旦有数据到达的时候,被指定的WSAOVERLAPPED结构中的 hEvent被Signaled。使得与该套接字相关联的WSAEVENT对象也被Signaled,所以WSAWaitForMultipleEvents的调用操作成功返回。
2.用完成例程方式实现的重叠I/O模型
WSARecv时传递CompletionROUTINE指针,回调函数,当IO请求完成时调用该回调函数完成我们需要处理的工作,在这个模型中,主线程只用不停的接受连接即可;辅助线程判断有没有新的客户端连接被建立,如果有,就为那个客户端套接字激活一个异步的WSARecv操作,然后调用SleepEx使线程处于一种可警告的等待状态,以使得I/O完成后 CompletionROUTINE可以被内核调用。如果辅助线程不调用SleepEx,则内核在完成一次I/O操作后,无法调用完成例程(因为完成例程的运行应该和当初激活WSARecv异步操作的代码在同一个线程之内)。
五.完成端口模型
只有在你的应用程序需要同时管理数百乃至上千个套接字的时候,而且希望随着系统内安装的CPU数量的增多,应用程序的性能也可以线性提升,才应考虑采用“完成端口”模型。首先要创建一个 I / O完成端口对象
HANDLE FileHandle, // handle to file
HANDLE ExistingCompletionPort, // handle to I/O completion port
ULONG_PTR CompletionKey, // completion key
DWORD NumberOfConcurrentThreads // number of threads to execute concurrently
CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)
工作者线程调用 GetQueuedCompletionStatus 来轮询完成端口队列