Windows的网络编程-之四-套接字模型

来源:互联网 发布:数据库方言 编辑:程序博客网 时间:2024/05/25 21:34

1      套接字模型

1.1     select模型

利用select( ),我们可以判断套接字上是否存在数据,或者能否向一个套接字写入数据。

intselect(  int nfds,  fd_set* readfds,  fd_set* writefds,  fd_set* exceptfds,

conststruct timeval* timeout  );

typedefstruct fd_set

{

u_int fd_count;                                     //Number of sockets in theset

SOCKET fd_array[FD_SETSIZE];  // Array of sockets that are in the set

}fd_set;

FD_CLR(int fd, fd_set *set )            :从set中删除fd

FD_ISSET(intfd, fd_set *set)            :检查fd是否是set的一名成员

FD_SET(intfd, fd_set *set)               :将fd加入set

FD_ZERO(fd_set *set)                      :将set初始化成空

参数nfds被忽略。

参数readfds包括符合下述任何一个条件的套接字:

1.该套接字内存在数据可供读取;

2.该套接字已经被关闭、重设或中止。

3.如果已经调用过listen(),对该套接字调用accept( )会成功返回。

参数writefds包括符合下述任何一个条件的套接字:

1.该套接字内存在数据可以发出;

2.如果已经在非阻塞模式下调用过connect( ),该套接字的连接已经成功建立。

参数exceptfds包括符合下述任何一个条件的套接字:

1.该套接字内存在OOB数据可供读取;

2.如果已经在非阻塞模式下调用过connect( ),该套接字的连接没有被建立。

例如我们想测试一个套接字是否可读,必须将该套接字增添到readfds,select( )完成之后,必须判断该套接字是否仍为readfds的一部分。

参数timeout指定select( )最多等待多长时间。如果timeout是一个空指针,那么select()会无限期地阻塞下去,直到有一个套接字符合条件后结束。如果timeout指定的时间是0,表明select( )会立即返回,允许应用程序对select()进行轮询。

如果成功,就返回三个set中的所有套接字的数目;

如果超时,就返回0;

如果失败,就返回SOCKET_ERROR。

1.2     WSAAsyncSelect模型

要使用WSAAsyncSelect模型,首先必须用CreateWindow()创建一个窗口,再为该窗口提供一个窗口例程函数(Winproc)。

intWSAAsyncSelect(  SOCKET s,  HWND hWnd, unsigned int wMsg,  long lEvent  );

参数hWnd指定网络事件发生之后,收到消息的窗口。

参数wMsg指定在发生网络事件时发送到窗口的消息。

参数lEvent指定应用程序感兴趣的一系列网络事件的组合,若设为0,将清除所有网络事件。

 

FD_READ

套接字可读

FD_WRITE

套接字可写事件

FD_OOB

OOB数据到达套接字

FD_ACCEPT

连接请求事件

FD_CONNECT

连接事完成件

FD_CLOSE

套接字关闭事件

FD_QOS

套接字QoS更改事件

FD_GROUP_QOS

套接字组QoS更改事件

FD_ROUTING_INTERFACE_CHANGE

套接字的路由接口变化事件

FD_ADDRESS_LIST_CHANGE

套接字协议家族的地址列表变化事件

 

注意:若应用程序对一个套接字调用了WSAAsyncSelect( ),该套接字会自动变成非阻塞模式。

应用程序在一个套接字上成功调用了WSAAsyncSelect( )之后,在hWnd窗口例程中会以Windows消息的形式接收到网络事件通知。窗口例程通常定义如下:

LRESULTCALLBACK WindowProc(  HWND hwnd,  UINT uMsg,

WPARAMwParam,  LPARAM lParam  );

参数uMsg指定需要对哪些消息进行处理,应该设为WSAAsyncSelect( )中指定的消息。

参数wParam存放发生网络事件的套接字。

参数lParam的低两位字节存放发生的网络事件,高两位字节存放错误代码。

所以当网络事件消息抵达一个窗口例程后,应用程序首先应检查lParam的高字位,以判断是否在套接字上发生了一个网络错误。若没有产生任何错误,接着便读取lParam低字位的内容,以确定到底是哪种网络事件类型。

#defineWSAGETSELECTERROR(lParam)      HIWORD(lParam)

#defineWSAGETSELECTEVENT(lParam)             LOWORD(lParam)

1.3     WSAEventSelect模型

intWSAEventSelect(  SOCKET s,  WSAEVENT hEventObject,  long lNetworkEvents  );

 

事件通知模型首先要创建一个事件对象:

WSAEVENTWSACreateEvent(void);

WSACreateEvent( )返回一个创建好的事件对象句柄。WSACreateEvent( )创建的事件有两种状态:signaled和non-signaled,以及两种模式:manual-reset和auto-reset。刚创建好的事件对象处于non-signaled的状态,并用manual-reset模式。当注册过的网络事件放生后,事件对象的状态便会从non-signaled变成signaled。

 

BOOLWSAResetEvent(  WSAEVENT hEvent  );

WSAResetEvent( )将事件对象的状态从signaled变为non-signaled。

 

BOOLWSACloseEvent(  WSAEVENT hEvent  );

WSACloseEvent( )释放事件句柄占用的系统资源。

 

DWORDWSAWaitForMultipleEvents(  DWORDcEvents,  const WSAEVENT* lphEvents,

BOOLfWaitAll,  DWORD dwTimeout,

BOOLfAlertable  );

参数cEvents和lphEvents指定了一个事件对象数组,cEvents指定的是事件对象的数量,而lphEvents对应的是一个指针,用于直接引用该事件对象数组。

参数fWaitAll若设为TRUE,只有等lphEvents数组内的所有事件对象都进入signaled状态后,函数才会返回;若设为FALSE,任何一个事件对象进入signaled状态,函数就会返回,而且函数返回值指出了到底是哪个事件对象造成了函数的返回。

参数dwTimeout规定了等待的最长时间,以毫秒为单位。如设为0,函数会检查指定的事件对象的状态,并立即返回。假如没有signaled状态的事件对象,函数就会返回WSA_WAIT_TIMEOUT。如设为WSA_INFINITE,那么只有在一个事件对象的状态变为signaled后,函数才会返回。

参数fAlertable在WSAEventSelect模型的时候是可以忽略的,且应设为FALSE。

1.3.1   WSAEventSelect模型的实例

Index           =     WASWaitForMultipleEvents(……);

MyEvent     =     EventArray[Index];

知道了产生网络事件的套接字后,可以用WSAEnumNetworkEvents( )查询发生了哪种类型的网络事件:

intWSAEnumNetworkEvents(  SOCKET s,  WSAEVENT hEventObject,

LPWSANETWORKEVENTSlpNetworkEvents  );

参数hEventObject是可选的,指定的事件对象必须处于signaled状态,而且WSAEnumNetworkEvents( )会自动将该事件对象重新变为non-signaled状态。

参数lpNetworkEvents用于接收套接字上发生的网络事件类型以及出现错误代码。

typedefstruct _WSANETWORKEVENTS

{

long lNetworkEvents;

int iErrorCode[FD_MAX_EVENTS];

}WSANETWORKEVENTS, *LPWSANETWORKEVENTS;

iErrorCode是一个错误代码数组,对每个网络事件类型都存在着一个事件索引,索引名字是在网络事件名字后面添加一个“_BIT”,例如,FD_READ的iErrorCode数组的索引标识符便是FD_READ_BIT:

if( NetworkEvents.lNetworkEvents &FD_READ )

{

      if(NetworkEvents.iErrorCode[FD_READ_BIT ] != 0 )

      {

             printf("FD_READ failed with error %d\n ",NetworkEvents.iErrorCode[FD_READ_BIT] );

      }

}

1.4     重叠模型

要在一个套接字上使用重叠模型,必须使用WSA_FLAG_OVERLAPPED标志创建一个套接字。如下所示:

s= WSASocket( AF_NET, SOCK_STREAM,  0, NULL,0,  WSA_FLAG_OVERLAPPED );

创建套接字的时候,如果使用的是socket( ),那么会默认设置WSA_FLAG_OVERLAPPED标志。成功建好一个重叠模型的套接字后,可以调用下面的Winsock函数进行重叠I/O操作:

WSASend;WSASendTo;WSARecv;WSARecvFrom;WSAIoctl;AcceptEx

参数lpOverlapped指向WSAOVERLAPPED结构:

typedefstruct _WSAOVERLAPPED

{

DWORD Internal;

DWORD InternalHigh;

DWORD Offset;

DWORD OffsetHigh;

WSAEVENT hEvent;

}WSAOVERLAPPED, *LPWSAOVERLAPPED;

其中,Internal、InternalHigh、Offset和OffsetHigh均由系统在内部使用,不应由应用程序直接进行处理或使用。一个重叠操作完成之后,会将WSAOVERLAPPED结构中的hEvent状态从non-signaled变成signaled。通过WSAWaitForMultipleEvents( )发现一次重叠操作完成之后,可以调用WSAGetOverlappedResult( )判断重叠操作是否成功:

BOOLWSAGetOverlappedResult(  SOCKET s,  LPWSAOVERLAPPED lpOverlapped,

LPDWORDlpcbTransfer,  BOOL fWait,

LPDWORDlpdwFlags  );

参数lpcbTransfer负责接收一次重叠发送或接收操作实际传输的字节数。

参数fWait决定是否应该等待重叠操作完成。若设为TRUE,只有重叠操作完成后函数才会返回;若设为FALSE,而且重叠操作仍未完成,那么函数就会返回FALSE。

参数lpdwFlags负责接收标志,如果重叠调用WSARecv()或WSARecvFrom( )发出的。

WSAGetOverlappedResult( )成功返回TRUE,意味着重叠操作成功完成。

 

重叠模型也允许应用程序以一种重叠方式实现对连接的接受:

BOOLAcceptEx(  SOCKET sListenSocket,  SOCKET sAcceptSocket,

PVOIDlpOutputBuffer,  DWORDdwReceiveDataLength,

DWORDdwLocalAddressLength,  DWORDdwRemoteAddressLength,

LPDWORDlpdwBytesReceived,  LPOVERLAPPEDlpOverlapped  );

参数sListenSocket指定的是监听套接字。

参数sAcceptSocket指定的是接受连接请求的套接字。AcceptEx( )和accept( )的区别在于,我们必须提供接受连接请求的套接字,而不是让系统自动创建。

参数lpOutputBuffer指定的是一个缓冲区,负责三种数据的接收:服务器的本地地址,客户机的远程地址,以及在新建连接上发送的第一个数据块。

参数dwReceiveDataLength以字节为单位,指定在lpOutputBuffer中,保留多大的空间,用于数据的接收。如果设为0,表示在连接的接受过程中,不接收任何数据。

参数dwLocalAddressLength和dwRemoteAddressLength以字节为单位,指定在lpOutputBuffer中,保留多大的空间,在一个套接字被接受的时候,用于本地和远程地址信息的保存。要注意的是,和当前传送协议允许的最大地址长度比较起来,这里指定的缓冲区大小至少应多出16字节。

参数lpdwBytesReceived用于返回接收到的实际数据量,以字节为单位。

参数lpOverlapped对应的是一个OVERLAPPED结构。

 

GetAcceptExSockaddrs( )可以从lpOutputBuffer中解析出本地和远程地址元素:

voidGetAcceptExSockaddrs(  PVOIDlpOutputBuffer,  DWORDdwReceiveDataLength,

DWORDdwLocalAddressLength,

DWORDdwRemoteAddressLength,

LPSOCKADDR*LocalSockaddr,

LPINTLocalSockaddrLength,

LPSOCKADDR*RemoteSockaddr,

LPINTRemoteSockaddrLength  );

参数lpOutputBuffer应设为AcceptEx()返回的lpOutputBuffer。

dwReceiveDataLength、dwLocalAddressLength以及dwRemoteAddressLength参数应设为与AcceptEx( )的dwReceiveDataLength、dwLocalAddressLength以及dwRemoteAddressLength参数相同的值。

1.4.1   重叠模型的回调函数

完成重叠操作的Winsock函数中有一个常用的参数:lpCompletionROUTINE,该参数指定一个回调函数,在重叠操作完成后调用。

voidCALLBACK CompletionROUTINE(  DWORDdwError,                                           IN

DWORDcbTransferred,                                   IN

LPWSAOVERLAPPEDlpOverlapped,         IN

DWORDdwFlags                                              IN

);

参数dwError表明了重叠操作的完成状态。

参数cbTransferred表明了重叠操作实际传输的字节数。

参数lpOverlapped是Winsock函数中的WSAOVERLAPPED结构。

参数dwFlags目前尚未使用,应设为0。

 


原创粉丝点击