共有五种类型的套接字I/O模型,可让Winsock应用程序对I/O进行管理,它们包括:select(选择)、WSAAsyncSelect(异步选择)、WSAEventSelect(事件选择)、overlapped(重叠)以及completion port(完成端口)
1.select
最初设计该模型,主要是面向某些使用Unix操作系统的计算机.使用大概原理设置一个集合,通过一个宏的定义来查询集合类的套接字可否使用。做个端口扫描用这个还是比较开心的。
2.WSAAsyncSelect
模仿WINDOWS消息机制来实现,使用起来很方便。个人比较喜欢。MFC中的CSOCKET也采用了这个模型。
相关函数:
int WSAAsyncSelect(SOCKET s, HWND hWnd, unsigned int wMsg, long lEvent);
lEvent表示一个掩码组合,比如FD_CONNECT|FD_READ
LREWULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
wParam: 发生了一个网络事件的套接字
lParam: (低位)指定了已经发生的网络事件WSAGETSELECTEVENT (高位)包含了可能出现的任何错误代码 用WSAGETSELECTERROR获取
大概使用框架如下:
创建套接字,使用WSAAsyncSelect进行套接字和事件绑定,在回调函数中写清消息响应
#define WM_SOCKET WM_USER+1
int WINAPI WinMain(HINSTANCE hINstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
SOCKET Listen;
HWND Window;
//Create a window and assign the winproc
//Start Winsock and create a socket
WSAStartup(...);
Listen = Socket();
bind(...);
WSAAsyscSelect(Listen, Window, WM_SOCKET, FD_ACCEPT | FD_CLOSE);
listen(Listen, 5);
}
BOOL CALLBACK WinProc(HWND hDlg, WORD wMsg, WORD wParam, DWORD lParam)
{
SOCKET Accept;
switch(wMsg)
{
case WM_SOCKET:
//whether an error occurred on the socket by using the WSAGETSELECTERROR() macro
if(WSAGETSELECTERROR(lParam))
{
closesocket...
}
//what event occurred on the socket
switch( WSAGETSELECTEVENT(lParam) )
{
case FD_ACCEPT:
case FD_READ:
case FD_WRITE:
}
}
}
FD_WRITE说明:三种条件下才会发出
使用connect WSAConnect,一个套接字首次建立连接
使用accept WSAAccept,套接字被接受以后
若send WSASend sendto WSASendTo操作失败,返回WSAEWOULDBLOCK错误,而且缓冲区空间变得可用
也就是说,收到首条FD_WRITE消息,便应认为自己必然能在一个套接字上发数据.
3 WSAEventSelect
你可以想象着,一排的空套接字等着对方的连接...
如上章所述,async投递一个窗口例程,而Event投递一个事件对象句柄.
或者更清楚解释为,套接字和事件对象对应着,当一个套接字有事件发生,事件对象返回相应的值,通过这个值来索引这个套接字。
要注意该模式里两种工作状态和模式,signaled/nonsignaled manual reset/auto reset
一开始默认为未传信(nonsignaled)和人工重设(manual reset)状态,随着网络事件的触发,工作状态从未船信到已传信.在完成一个I/O请求处理后,要负责将工作状态改变
首先创建一个事件对象 WSAEVENT WSACreateEvent(void);
其次将事件对象和套接字联系在一起 int WSAEventSelect(SOCKET s, WSAEVENT hEventObject, long lNetworkEvents);
等待网络事件触发事件对象句柄状态 DWORD WSAWaitForMultipleEvents(....DWORD dwTimeout, BOOL fAlertable);
dwTimeout参数规定等待一个网络事件发生有多长时间,毫秒为单位.超过立即返回,即使由fWaitAll参数规定条件未满足也如此.如果设为0, 函数会检测指定的事件对象状态,然后返回.这样造成一个"轮询",尽量避免设为0.若设为WSA_INFINITE,那么只有在一个网络事件传回一个事件对象后,才会返回.
fAlertable:忽略,设为FALSE,主要用于重叠式I/O模型中
将已传信状态更改为未传信 BOOL WSAResetEvent(WSAEVENT hEvent);
释放事件句柄使用的系统资源 BOOL WSACloseEvent(WSAEVENT hEvent);
现在还需解决的一问题是,怎么获得发生事件的套接字,async直接用一个socket变量接收,而在event里,是通过事先的一个数组索引获得.这个方法名为WSAEnumNetworkEvents();
代码框架如下:
//先创建事件数组和套接字数组
SOCKET Socket[常量];
WSAEVENT Event[];
DWORD EvenTotal = 0;
//这里进行套接字准备到bind那步,创建一个事件,并且联系
NewEvent = WSACreateEvent();
WSAEventSelect(Listen, NewEvent, FD_ACCEPT|FD_CLOSE);
//最重要一步,加入到数组
Socket[EventTotal] = Listen;
Event[EventTotal] = NewEvent;
EventTotal++;
while(TRUE)
{
//等待一个套接字连接
Index = WSAWaitForMultipleEvents(..);
//在EnumNetworkEvents中进行查询,判断在哪个套接字上,发生什么网络事件类型.
WSAEnumNetworkEvents(
SocketArray[Index - WSA_WAIT_EVENT_0],
EventArray[Index - WSA_WAIT_EVENT_0],
NetworkEvents);
}
对一个发生事件套接字操作
WSAWaitForMultipleEvents返回值:减去预定值WSA_WAIT_EVENT_0
Iindex = WSAWaitForMultipleEvents(..);
MyEvent = EventArray[Index - WSA_WAIT_EVENT_0];
接下来可调用WSAEnumNetworkEvents函数,调查发生什么类型网络事件
4IO重叠
对一个套接字多次利用,利用CRITICAL_SECTION来进行互斥
5。。。略