1、Windows Socket IO 模型Windows Socket IO 模型套接字架构应用程序使用Winsock与传输协议驱动沟通时AFD.SYS负责缓冲区的管理。这就意味着当一个程序调用send或者WSASend发送数据时,数据将被复制到AFD.SYS它自己的内部缓冲区中(依赖SO_SNDBUF的设置)WSASend调用立即返回。然后AFD.SYS在程序后台将数据发送出去。当然,如果程序想要处理一个比SO_SNDBUF设置的缓冲区需求更大的发送请求,WSASend的调用就会阻塞直到所有的数据都被发送出去。类似的,从远程客户端接收数据时,只要SO_RCVBUF设置的缓冲区还没有满,AFD.
2、SYS就会将数据复制进它自己的缓冲区直到所有的发送都已完成。当程序调用recv或者是WSARecv,数据就从AFD.SYS的缓冲区复制到了程序提供的缓冲区中了。使用Winsock的时候还会间接碰到另外两种资源的限制。第一个页面锁定的限制。注意重叠操作可能偶然性地以 ERROR_INSUFFICIENT_RESOURCES调用失败,这基本上意味着有太多的发送和接收操作在等待中。另外一个限制是操作系统的非分页池(non-paged pool)的限制。阻塞模型intrecv(SOCKETs,char*buf,intlen,intflags);intsend(SOCKETs,constchar*buf
3、,intlen,intflags);这种方式最为大家熟悉,Socket默认的就是阻塞模式。在recv的时候,Socket会阻塞在那里,直到连接上有数据可读,把数据读到buffer里后recv函数才会返回,不然就会一直阻塞在那里。如果在主线程中被阻塞,而数据迟迟没有过来,那么程序就会被锁死。这样的问题可以用多线程解决,但是在有多个套接字连接的情况下,这不是一个好的选择,扩展性很差,而且也容易有锁的问题。线程过多,也导致上下文切换过于频繁,导致系统变慢,而且大部分线程是处于非活动状态的话,这就大大浪费了系统的资源。非阻塞模型intioctlsocket(INSOCKETs,INlongcmd,IN
4、OUTu_longFAR*argp);#define FIONBIO/* set/clear non-blocking i/o */调用ioctlsocket函数设置FIONBIO为1就转为非阻塞模式。当recv和send函数没有准备好数据时,函数不会阻塞,立即返回错误值,用GetLastError返回的错误码为WSAEWOULDBLOCK,中文解释为“无法立即完成一个非阻挡性套接字的操作”。当然,这里你可以用非阻塞模拟阻塞模式,就是用while循环不停调用recv,直到recv返回成功为止。这样的效率也不高,但好处在于你能在没接收到数据时,有空进行其他操作,或者直接Sleep。Select模
5、型intselect(intnfds,fd_set*readfds,fd_set*writefds,fd_set*exceptfds,conststructtimeval*timeout);Select 模型是非阻塞的,函数内部自动检测WSAEWOULDBLOCK状态,还能有超时设定。对read,write,except三种事件进行分别检测,except指带外数据可读取,read和write的定义是广义的,accept,close等消息也纳入到read。Select函数使用fd_set结构,它的结构非常的简单,只有一个数组和计数器。Timeval结构里可以设置超时的时间。Select函数返回值
6、表示集合中有事件触发的sock总数,其余操作使用fd_set的宏来完成。#ifndef FD_SETSIZE#define FD_SETSIZE 64#endif/* FD_SETSIZE */ typedefstructfd_setu_intfd_count;/* how many are SET? */SOCKETfd_arrayFD_SETSIZE;/* an array of SOCKETs */fd_set;FD_CLR(s,*set)FD_ISSET(s,*set)FD_SET(s,*set)FD_ZERO(*set)Select模型流程如下:fd_setfdread;timeva
7、ltv=1,0;while(1)/ 初始化fd_setFD_ZERO(&fdread);for(inti=0;i0&i 0。完成例程完成例程(Completion Routine),不是完成端口。它是使用APC(Asynchronous Procedure Calls)异步回调函数来实现,大致流程和事件通知模型差不多,只不过WSARecv注册时,加上了lpCompletionRoutine参数。VoidCALLBACKCompletionROUTINE(DWORDdwError,/ in 标志咱们投递的重叠操作完成的状态DWORDcbTransferred,/ in 重叠操作期间,实际传输的字
8、节量是多大LPWSAOVERLAPPEDlpOverlapped,/ in 传递到最初IO调用的重叠结构DWORDdwFlags/ in 返回操作结束时可能用的标志(一般没用);但完成例程有一个比较隐晦的地方,就是APC机制本身。APC机制ReadFileEx / WriteFileEx在发出IO请求的同时,提供一个回调函数(APC过程),当IO请求完成后,一旦线程进入可告警状态,回调函数将会执行。以下五个函数能够使线程进入告警状态:SleepExWaitForSingleObjectExWaitForMultipleObjectsExSignalObjectAndWaitMsgWaitFor
9、MultipleObjectsEx线程进入告警状态时,内核将会检查线程的APC队列,如果队列中有APC,将会按FIFO方式依次执行。如果队列为空,线程将会挂起等待事件对象。以后的某个时刻,一旦APC进入队列,线程将会被唤醒执行APC,同时等待函数返回WAIT_IO_COMPLETION。回到完成例程的话题上。需要一个辅助线程,辅助线程的工作是判断有没有新的客户端连接被建立,如果有,就为那个客户端套接字激活一个异步的WSARecv操作,然后调用 SleepEx使线程处于一种可警告的等待状态,以使得I/O完成后 CompletionROUTINE可以被内核调用,而CompletionROUTINE
10、会在当初激活WSARecv异步操作的代码的同一个线程之内!而且调用SleepEx时,需要把bAlertable参数设为TRUE,这样当有APC唤醒时立即调用完成例程,否则例程就不会被执行。当然也可以使用 WSAWaitForMultipleEvents函数,但这样就需要一个事件对象。从图中就能看到CompletionROUTINE是在辅助线程(调用过WSARecv)里执行的。Completion Port模型“完成端口”模型是迄今为止最为复杂的一种I/O模型。假若一个应用程序同时需要管理为数众多的套接字,那么采用这种模型,往往可以达到最佳的系统性能!它能最大限度的减少上下文切换的同时最大限度的
11、提高系统并发量。但不幸的是,该模型只适用于Windows NT和Windows 2000操作系统。因其设计的复杂性,只有在你的应用程序需要同时管理数百乃至上千个套接字的时候,而且希望随着系统内安装的CPU数量的增多,应用程序的性能也可以线性提升,才应考虑采用“完成端口”模型。要记住的一个基本准则是,假如要为Windows NT或Windows 2000开发高性能的服务器应用,同时希望为大量套接字I/O请求提供服务(Web服务器便是这方面的典型例子),那么I/O完成端口模型便是最佳选择!完成端口是一种WINDOWS内核对象。完成端口用于异步方式的重叠I/O。简单地,可以把完成端口看成系统维护的一个队列,操作系统把重叠IO操作完成的事件通知放到该队列里,由于是暴露 “操作完成”的事件通知,所以命名为“完成端口”(Completion Ports)。完成端口内部提供了线程池的管理,可以避免反复创建线程的开销,同时可以根据CPU的个数灵活的决定线程个数,而且可以让减少线程调度的次数从而提高性
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1