欢迎使用CSDN-markdown编辑器

来源:互联网 发布:英文字帖 知乎 编辑:程序博客网 时间:2024/05/16 07:46

#
fd_set参数:一个用于检查可读性(readfds),一个用于检查可写性(writefds),另一个用于例外数据(exceptfds)。从根本上说,
fd_set数据类型代表着一系列特定套接字的集合。其中,readfds集合包括符合下述任何一个条件的套接字:
■有数据可以读入。
■连接已经关闭、重设或中止。
■假如已调用了listen,而且一个连接正在建立,那么accept函数调用会成功。
writefds集合包括符合下述任何一个条件的套接字:
■有数据可以发出。
■如果已完成了对一个非锁定连接调用的处理,连接就会成功。
最后,exceptfds集合包括符合下述任何一个条件的套接字:
■假如已完成了对一个非锁定连接调用的处理,连接尝试就会失败。
■有带外(Out-of-band,OOB)数据可供读取。
用select对套接字进行监视之前,在自己的应用程序中,必须将套接字句柄分配给一个集
合,设置好一个或全部读、写以及例外fd_set结构。将一个套接字分配给任何一个集合后,再
来调用select,便可知道一个套接字上是否正在发生上述的I/O活动。Winsock提供了下列宏操作,可用来针对I/O活动,对fd_set进行处理与检查:
■FD_CLR(s,*set):从set中删除套接字s。
■FD_ISSET(s,*set):检查s是否set集合的一名成员;如答案是肯定的是,则返回TRUE。
■FD_SET(s,*set):将套接字s加入集合set。
■FD_ZERO(*set):将set初始化成空集合。
例如,假定我们想知道是否可从一个套接字中安全地读取数据,同时不会陷于无休止的
“锁定”状态,便可使用FD_SET宏,将自己的套接字分配给fd_read集合,再来调用select。要
想检测自己的套接字是否仍属fd_read集合的一部分,可使用FD_ISSET宏。采用下述步骤,便
可完成用select操作一个或多个套接字句柄的全过程:
1)使用FD_ZERO宏,初始化自己感兴趣的每一个fd_set。
2)使用FD_SET宏,将套接字句柄分配给自己感兴趣的每个fd_set。
3)调用select函数,然后等待在指定的fd_set集合中,I/O活动设置好一个或多个套接字句柄。
select完成后,会返回在所有fd_set集合中设置的套接字句柄总数,并对每个集合进行相应的更新。
4)根据select的返回值,我们的应用程序便可判断出哪些套接字存在着尚未完成(待决)
的I/O操作—具体的方法是使用FD_ISSET宏,对每个fd_set集合进行检查。
5)知道了每个集合中“待决”的I/O操作之后,对I/O进行处理,然后返回步骤1),继续进
行select处理。
select返回后,它会修改每个fd_set结构,删除那些不存在待决I/O操作的套接字句柄。

若WSAWaitForMultipleEvents收到一个事件对象的网络事件通知,便会返回一个值,指
出造成函数返回的事件对象。这样一来,我们的应用程序便可引用事件数组中已传信的事件,
并检索与那个事件对应的套接字,判断到底是在哪个套接字上,发生了什么网络事件类型。
对事件数组中的事件进行引用时,应该用WSAWaitForMultipleEvents的返回值,减去预定义
值WSA_WAIT_EVENT_0,得到具体的引用值(即索引位置)。如下例所示:

知道了造成网络事件的套接字后,接下来可调用WSAEnumNetworkEvents函数,调查发
生了什么类型的网络事件。该函数定义如下:

s参数对应于造成了网络事件的套接字。hEventObject参数则是可选的;它指定了一个事件句柄,对应于打算重设的那个事件对象。由于我们的事件对象处在一个“已传信”状态,所以可将它传入,令其自动成为“未传信”状态。如果不想用hEventObject参数来重设事件,那么可使用WSAResetEvent函数,该函数早先已经讨论过了。最后一个参数是lpNetworkEvents,代表一个指针,指向WSANETWORKEVENTS结构,用于接收套接字上发生的网络事件类型以及可能出现的任何错误代码。下面是WSANETWORKEVENTS结构的定义:

创建套接字的时候,假如使用的是socket函数,而非WSASocket函数,那么会默认设置WSA_FLAG_OVERLAPPED标志。成功建好一个套接字,同时将其与一个本地接口绑定到一起后,便可开始进行重叠I/O操作,方法是调用下述的Winsock函数,同时指定一个WSAOVERLAPPED结构(可选):
■WSASend
■WSASendTo
■WSARecv
■WSARecvFrom
■WSAIoctl
■AcceptEx
■TrnasmitFile

。WSAOVERLAPPED结构在一个重叠I/O请求的初始
化,及其后续的完成之间,提供了一种沟通或通信机制。下面是这个结构的定义:

发现一次重叠请求完成之后,接着需要调用WSAGetOverlappedResult(取得重叠结构)函数,判断那个重叠调用到底是成功,还是失败。该函数的定义如下:

2.完成例程
“完成例程”是我们的应用程序用来管理完成的重叠I/O请求的另一种方法。完成例程其实就是一些函数。最开始的时候,我们将其传递给一个重叠I/O请求,在一个重叠I/O请求完成时由系统调用。它们的基本设计宗旨是通过调用者的线程,为一个已完成的I/O请求提供服务。除此以外,应用程序可通过完成例程,继续进行重叠I/O处理。如果希望用完成例程为重叠I/O请求提供服务,在我们的应用程序中,必须为一个I/O定界Winsock函数,指定一个完成例程,同时指定一个WSAOVERLAPPED结构。一个完成例程必须拥有下述函数原型:

在程序清单8-8中,我们向大家展示了如何构建一个简单的服务器应用,令其采用前述的
方法,通过完成例程,来实现对一个套接字请求的管理。该程序的编码主要按下述步骤进行:
1)新建一个套接字,开始在指定端口上,监听一个进入的连接。
2)接受一个进入的连接请求。
3)为接受的套接字创建一个WSAOVERLAPPED结构。
4)在套接字上投递一个异步WSARecv请求,方法是将WSAOVERLAPPED指定成为参数,同时提供一个完成例程。
5)在将fAlertable参数设为TRUE的前提下,调用WSAWaitForMultipleEvents,并等待一个重叠I/O请求完成。重叠请求完成后,完成例程会自动执行,而且WSAWaitForMultipleEvents会返回一个WSA_IO_COMPLETION。在完成例程内,可随一个完成例程一道,投递另一个重叠WSARecv请求。
6)检查WSAWaitForMultipleEvents是否返回WSA_IO_COMPLETION。
7)重复步骤5)和6)。
,我们的应用程序可用Win32的SleepEx函数将自己的线程置为一种可警告等待状态。该函数也设计用于将我们的线程置入一种可警告等待状态,并可为已经完成的重叠I/O请求进行完成例程的处理(前提是将fAlertable参数设为TRUE)。用一个完成例程结束了重叠I/O请求之后,返回值是WSA_IO_COMPLETION,而不是事件数组中的一个事件对象索引。SleepEx函数的行为实际上和WSAWaitForMultipleEvents差不多,只是它不需要任何事件对象。对SleepEx函数的定义如下:

dwMilliseconds参数定义了SleepEx函数的等待时间,以毫秒为单位。假如将dwMilliseconds设为INFINITE,那么SleepEx会无休止地等待下去。
bAlertable参数规定了一个完成例程的执行方式。假如将bAlertable设为FALSE,而且进行了一次I/O完成回调,那么I/O完成函数不会执行,而且函数不会返回,除非超过由dwMilliseconds规定的时间。若设为TRUE,那么完成例程会得到执行,同时SleepEx函数返回WAIT_IO_COMPLETION。

从本质上说,完成端口模型要求我们创建一个Win32完成端口对象,通过指定数量的线程,
对重叠I/O请求进行管理,以便为已经完成的重叠I/O请求提供服务。要注意的是,所谓“完成
端口”,实际是Win32、WindowsNT以及Windows2000采用的一种I/O构造机制,除套接字句
柄之外,实际上还可接受其他东西。然而,本节只打算讲述如何使用套接字句柄,来发挥完
成端口模型的巨大威力。使用这种模型之前,首先要创建一个I/O完成端口对象,用它面向任
意数量的套接字句柄,管理多个I/O请求。要做到这一点,需要调用CreateCompletionPort函数。
该函数定义如下:
intraweb(★人贱人爱★)

在我们深入探讨其中的各个参数之前,首先要注意该函数实际用于两个明显有别的目的:
■用于创建一个完成端口对象。
■将一个句柄同完成端口关联到一起。
对完成端口来说,将一个套结字邦定到完成端口后,WSARecv和WSASend会立即返回,提高了系统的效率。可以调用GetQueuedCompletionStatus来判断WSARecv和WSASend是否完成。这样主程序就可以完全等待接受新的连接,线程程序来等待WSARecv和WSASend是否完成了。我想这也是完成端口的真正含义吧。
完成端口
一个完成端口其实就是一个通知队列,由操作系统把已经完成的重叠I/O请求的通知放入其中。当某项I/O操作一旦完成,某个可以对该操作结果进行处理的工作者线程就会收到一则通知。而套接字在被创建后,可以在任何时候与某个完成端口进行关联。通常情况下,我们会在应用程序中创建一定数量的工作者线程来处理这些通知。线程数量取决于应用程序的特定需要。理想的情况是,线程数量等于处理器的数量,不过这也要求任何线程都不应该执行诸如同步读写、等待事件通知等阻塞型的操作,以免线程阻塞。每个线程都将分到一定的CPU时间,在此期间该线程可以运行,然后另一个线程将分到一个时间片并开始执行。如果某个线程执行了阻塞型的操作,操作系统将剥夺其未使用的剩余时间片并让其它线程开始执行。也就是说,前一个线程没有充分使用其时间片,当发生这样的情况时,应用程序应该准备其它线程来充分利用这些时间片。
其实可以把完成端口看成是系统维护的一个队列,操作系统把重叠的I/O操作完成的事件通知放到该队列中,由于是“操作完成”的事件通知所以命名为“完成端口”

//———————————————————————————-
系统为你开一根内部线程去处理I/O请求
////////////////////////////////////////////////
你这个说法有误,可以参考DDK中WDM驱动文档
系统不会新开内部线程来等待,它只是简单交给设备去处理IO
直到设备以它自己的方式(多数是中断,即使有DMA)通知CPU,IO操作完成,系统(多数是响应中断时)再通知Iocp(就是挂个消息了)
因此,Iocp就是一个消息队列
//———————————————————————————-

我们基本上按下述步骤行事:
1)创建一个完成端口。第四个参数保持为0,指定在完成端口上,每个处理器一次只允许
执行一个工作者线程。
2)判断系统内到底安装了多少个处理器。
3)创建工作者线程,根据步骤2)得到的处理器信息,在完成端口上,为已完成的I/O请求提供服务。在这个简单的例子中,我们为每个处理器都只创建一个工作者线程。这是由于事先已预计到,到时不会有任何线程进入“挂起”状态,造成由于线程数量的不足,而使处理器空闲的局面(没有足够的线程可供执行)。调用CreateThread函数时,必须同时提供一个工作者例程,由线程在创建好执行。本节稍后还会详细讨论线程的职责。
4)准备好一个监听套接字,在端口5150上监听进入的连接请求。
5)使用accept函数,接受进入的连接请求。
6)创建一个数据结构,用于容纳“单句柄数据”,同时在结构中存入接受的套接字句柄。
7)调用CreateIoCompletionPort,将自accept返回的新套接字句柄同完成端口关联到一起。通过完成键(CompletionKey)参数,将单句柄数据结构传递给CreateIoCompletionPort。
8)开始在已接受的连接上进行I/O操作。在此,我们希望通过重叠I/O机制,在新建的套接字上投递一个或多个异步WSARecv或WSASend请求。这些I/O请求完成后,一个工作者线程会为I/O请求提供服务,同时继续处理未来的I/O请求,稍后便会在步骤3)指定的工作者例程中,体验到这一点。
9)重复步骤5)~8),直至服务器中止。
2.完成端口和重叠I/O
将套接字句柄与一个完成端口关联在一起后,便可以套接字句柄为基础,投递发送与接收请求,开始对I/O请求的处理。接下来,可开始依赖完成端口,来接收有关I/O操作完成情况的通知。从本质上说,完成端口模型利用了Win32重叠I/O机制。在这种机制中,象WSASend和WSARecv这样的WinsockAPI调用会立即返回。此时,需要由我们的应用程序负责在以后的某个时间,通过一个OVERLAPPED结构,来接收调用的结果。在完成端口模型中,要想做到这一点,需要使用GetQueuedCompletionStatus(获取排队完成状态)函数,让一个或多个工作者线程在完成端口上等待。该函数的定义如下:

。一旦所有套接字句柄都已关闭,
便需在完成端口上,终止所有工作者线程的运行。要想做到这一点,需要使用
PostQueuedCompletionStatus函数,向每个工作者线程都发送一个特殊的完成数据包。该函数
会指示每个线程都“立即结束并退出”。下面是PostQueuedCompletionStatus函数的定义:

I/O模型的问题
现在,对于如何挑选最适合自己应用程序的I/O模型,大家心中可能还没什么数。前面已
经提到,每种模型都有自己的优点和缺点。同开发一个简单的锁定模式应用相比(运行许多
服务线程),其他每种I/O模型都需要更为复杂的编程工作。因此,针对客户机和服务器应用
的开发,我们分别提供了下述建议。
1.客户机的开发
若打算开发一个客户机应用,令其同时管理一个或多个套接字,那么建议采用重叠I/O或
WSAEventSelect模型,以便在一定程度上提升性能。然而,假如开发的是一个以Windows为
基础的应用程序,要进行窗口消息的管理,那么WSAAsyncSelect模型恐怕是一种最好的选择,
因为WSAAsyncSelect本身便是从Windows消息模型借鉴来的。若采用这种模型,我们的程序
一开始便具备了处理消息的能力。
2.服务器的开发
若开发的是一个服务器应用,要在一个给定的时间,同时控制几个套接字,建议大家采
用重叠I/O模型,这同样是从性能出发点考虑的。但是,如果预计到自己的服务器在任何给定
的时间,都会为大量I/O请求提供服务,便应考虑使用I/O完成端口模型,从而获得更好的性
能。
//————————————————————————————————————————
重叠I/O—-顾名思义,当你调用了某个函数(比如ReadFile)就立刻返回做自己的其他动作的时候,同时系统也在对I/0设备进行你要求的操作,在这段时间内你的程序和系统的内部动作是重叠的,因此有更好的性能。所以,重叠I/O是用于异步方式下使用I/O设备的。
重叠I/O需要使用的一个非常重要的数据结构OVERLAPPED。
完成端口—是一种WINDOWS内核对象。完成端口用于异步方式的重叠I/0情况下,当然重叠I/O不一定非使用完成端口不可,还有设备内核对象、事件对象、告警I/0等。但是完成端口内部提供了线程池的管理,可以避免反复创建线程的开销,同时可以根据CPU的个数灵活的决定线程个数,而且可以让减少线程调度的次数从而提高性能。
//————————————————————————————————————————
typedefstruct_OVERLAPPED{
ULONG_PTRInternal;//被系统内部赋值,用来表示系统状态
ULONG_PTRInternalHigh;//被系统内部赋值,传输的字节数
//通常被保留,但是当GetOverlappedResult的返回植为True的时候,这个字段里填充的是“传输的数据长度”
union{
struct{
DWORDOffset;//和OffsetHigh合成一个64位的整数,用来表示从文件头部的多少字节开始
DWORDOffsetHigh;//操作,如果不是对文件I/O来操作,则必须设定为0
};
PVOIDPointer;
};
HANDLEhEvent;//如果不使用,就务必设为0,否则请赋一个有效的Event句柄
}OVERLAPPED,*LPOVERLAPPED;
这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

0 0
原创粉丝点击