Winsock 套接字模式:锁定和非锁定
1.锁定模式:
在I/O操作完成前,执行操作的Winsock函数(例send和recv)会一直等待下去,不会立即返回程序
应用程序需要读取(或写入)指定数量的字节,然后以它为基础执行一些运算,
假如没有数据处于"待决"状态,那么函数可能永远都无法返回,只有从系统的输入缓冲区中读回点东西
才允许返回!
2.非锁定模式:
函数无论如何都会立即返回,
把套接字置为非锁定:
int ioctlsocket(s,FIOBIO,(unsigned long*)&ul);
3.锁定模式的多线程示例:
CRITICAL_SECTION data;
HANDLE hEvent;
TCHAR buff[MAX_BUFFER_SIZE];
int nbytes;
//读线程
void ReadThread(void)
{
int nTotal =0 ;
nRead =0;
nLeft =0;
nBytes =0;
while(!done)
{
nTotal =0;
nLeft=NUM_BYTES_REQUIRED;
while(nTotal != NUM_BYTES_REQUIRED)
{
EnterCriticalSection(&data);
nRead = recv(sock,&(buff[MAX_BUFFER_SIZE-nBytes]),nLeft);
if(nRead==-1)
{
//Error;
ExitThread();
}
nTotal+=nRead;
nLeft-=nRead;
nBytes+=nRead;
LeaveCriticalSection(&data);
}
SetEvent(hEvent);
}
}
//计算线程
void ProcessThread(void)
{
WaitForSingleObject(hEvent);
EnterCriticalSection(&data);
DoSomeComputionOnData(buff);
nBytes-=NUM_BYTES_REQUIRED;
LeaveCriticalSection(&data);
}
4.1 select 模型
a>利于select函数,判断套接字上是否可以读取或写入数据,
int select ( int nfds, //忽略
fd_set FAR* readfds, //检察可读性
fd_set FAR* writefds, //检察可写性
fd_set FAR* exceptfds, //异常数据
const struct timeval FAR* timeout
);
说明:fd_set类型,代表着特定套接字的集合,
readfds包括任何一个下述的套接字:
i>有数据可以读入
ii>连接己经关闭,重设或中止
iii>假如己经调用了listen,而且一个连接正在建立,那么accept函数调用会成功
writefds包括任何一个下述的套接字
i>有数据可以发出
ii>如果己完成了对一个非锁定连接调用的处理,连接就会成功
exceptfds包括任何一个下述的套接字
i>假如己完成了对一个非锁定连接调用的处理,连接尝试就会失败
ii>有带外(out-of-band,OOB)数据可供读取
select 函数完成后,可读取或可写入数据的套接字,保留在fds_set集合中,否则会被
删除掉,
三个fds_set集合参数,至少有一个不能为空值,
将一个套接字分配给任何一个集合后,再调用select,便可知一个套接字是否发生I/O活动
b>FD_CLR(s,*set):从set中删除套接字
c>FD_ISSET(s,*set):检查s是否set集合的一名成员
d>FD_SET(s,*set):将套接字s加入集合set
e>FD_ZERO(*set):将set初始化成空集合
f>下述步骤可以完成select操作一个或多个套接字句柄的全过程:
1)使用FD_ZERO宏,初始化自己感兴趣的每一个fd_set
2)使用F_SET宏,将套接字句柄分配给自己感兴趣的每个fd_set
3)调用select函数,然后等待在指定的fd_set集合中,I/O活动设置好一个或多个套接字句柄.
select完成后,会返回在所有fd_set集合中设置的套接字句柄总数,并对每个集合进行相
应的更新
4)根据select的返回值,应用程序便可判断出哪些套接字可以读取或写入(使用FD_ISSET),
对每个fd_set集合进行检查
5)知道了每个集合中"待决"的I/O操作后,对I/O进行处理,然后返回步骤1),继续进行select
处理.
select返回后,会修改每个fd_set结构,删除不存在待决I/O操作的套接字句柄
g>例子为一个(只有一个)套接字设置select模型所需的一系列基本步骤.
(若想在这个应用程序中添加更多的套接字,只需为额外的套接字维护它们的一个列表或数组)
SOCKET s;
fd_set fdread;
int ret;
//Creat a socket and accept a connection
while(True)
{
// Always clear the read set before calling select()
FD_ZERO(&fdread);
//Add socket s to the read set
FD_SET(s,&fdread);
if (ret=select(0,&fdread,NULL,NULL,NULL)==SOCKET_ERROR)
{
//Error
}
if (ret>0)
{
//For this simple case,select() should return the value 1,
//An application dealing with more than one socket could get a value
//greater than 1,At this point,your application should check to see whether
//the socket is part of a set
if (FI_ISSET(s,&fdread))
{ //A read event has occurred on socket s}
}
}
4.2 WSAAsyncSelect (异步I/O模型)
应用程序可在一个套接字上,接收以Windows消息为基础的网络事件通知.
在应用程序中,必须用CreatWindow函数,创建一个窗口(若在MFC的VC中,指定窗口就行了),
再为该窗口提供一个窗口例程函数(即回调函数)WinProc.
int WSAAyncSelect( SOCKET s,
HWND hWnd, //窗口句柄,要接收网络事件消息的窗口
unsigned int wMsg, //准备接收的消息
long lEvent //位掩码,网络事件的组合
)
说明:网络事件:FD_READ,FD_WRITE,FD_ACCEPT,FD_CONNECT,FD_CLOSE.
对套接字s调用此函数,则此套接字自动从"锁定" 自动变成"非锁定"
窗口例程:
LRESULT CALLBACK WindowProc(HWND hWnd, //指向窗口的句柄,调用此例程的窗口
UINT uMsg, //需果处理的消息
WPARAM wParam, //指向一个或多个发生网络事件的套接字
LPARAM lParam //低位字己经发生的网络事件,高位字错误码
)
网络事件消息到达窗口例程后,程序首选检察lParam的高字位,断定套接字上是否发生了错误
宏WSAGETSELECTERROR返回高字位包含的错误消息,若无错,确定网络事件类型,
宏WSAGETSELECTEVENT返回低字位包含的网络事件类型
例.WSAAsyncSelect服务器示范代码
#define WM_SOCKET WM_USER+1
#include <windows.h>
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,
int nCmdShow)
{
SOCKET Listen;
HWND Window;
//Create a window and assign the ServerWinpro below to it
Window = CreateWindow();
//Start Winsock and create a socket
WSAStartup(…);
Listen = Socket();
//Bind the socket to port 5150 and begin listening for connections
InternetAddr.sin_family = AF_INET;
InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
InternetAddr.sin_port = htons(5150);
bind(Listen,(PSOCKADDR)&InternetAddr,sizeof(InternetAddr));
//Set up window message notiffication on the new socket using the WM_SOCKET define
// above
WSAAsyncSelect(Listen,Window,WM_SOCKET,FD_ACCEPT|FD_CLOSE);
listen(Listen,5);
//Translate and dispatch window messages until the application terminates
}
//窗口函数(例程,回调函数)
BOOL CALLBACK ServerWinproc(HWND hDlg,WORD wMsg,WORD wParam,DWORD lParam)
{
SOCKET Accept;
switch(wMsg)
{
case WM_PAINT:
// Process window paint message
break;
case WM_SOCKET:
//Determine whether an error occurred on the socket by using the
//WSAGETSELECTERROR() macro
if(WSAGETSELECTERROR(lParam))
{
//Errror
closesocket(wParam);
break;
}
//Determine what event occurred on the socket
switch(WSAGETSELECTEVENT(lParam))
{
case FD_ACCEPT:
//Accept an incoming connection
Accept = accept(wParam,NULL,NULL);
//Prepare accepted socket for read,
//Write and close notification
WSAAsyncSelect(Accept,hwnd,WM_SOCKET,FD_READ|FD_WRITE|FD_CLOse);
break;
case FD_READ:
//Receive data from the socket in wParam
break;
case FD_WRITE:
//the socket in wParam is ready for sending data
break;
case FD_CLOSE:
//the connection is now closed
closesocket(wParam);
break;
}
break;
}
return TRUE;
}
只有在三种条件下,才会发出FD_WRITE通知,
i>使用connect或WSAConnect,一个套接字首次建立了连接
ii>使用accept或WSAAccept,套接字被接受以后
iii>若send,WSASend,sendto或WSASendTO操作失败,返回了WSAEWOULDBLOCK错误,而且缓冲区空间
变得可用
因此,应用程序自收到首条FD_WRITE消息开始,便应认为自己必然能在一个套接字上发出数据,直至一
个send ,WSASend,sendto或WSASendTo返回套接字错误WSAEWOULDBLOCK.经过了这样的失败后,
要再用另一条FD_WRITE通知程序再次发送数据.
4.3 WSAEventSelect
该模型把网络事件投递至一个事件对象句柄,而不是一个窗口函数
4.3.1事件通知:
应用程序针对打算使用的套接字,首先创建一个事件对象
WSAEVENT WSACreateEvent( void );
将返回事件句柄对象与套接字关联在一起,同时注册感兴趣的网络事件
int WSAEventSelect( SOCKET s,
WSAEVENT hEventObject, //事件句柄对象
long lNetworkEvents
);
为WSAEventSelect创建的事件有两种状态以及两种工作模式:
工作状态:己传信和未传信
工作模式:人工重设和自动重设
WSACreateEvent最开始用未传信的工作状态和人工重设模式创建事件句柄,随着网络事件触发
事件对象,工作状态便会从"未传信"转变成"己传信",由于事件对象是人工模式创建的,所在
程序在完成了一个I/O处理后,应把事件对象从"己传信"更改为"未传信",用WSAResetEvent函数
BOOL WSAResetEvent( WSAEVENT hEvent);
程序完成对一个事件对象的处理后,应调用WSACloseEvent函数,释放资源
BOOL WSACloseEvent (WSAEVENT hEvent);
4.3.2
套接字与事件对象句柄关联后,程序就应该I/O处理:方法是等待网络事件触发事件对象句柄的
工作状态,WSAWaitForMulipleEvents函数,便是用来等待一个或多个事件对象句柄,并在事先指
定的一个或所有句柄进入"己传信"状态后或超时后,立即返回.
DWORD WSAWaitForMultipleEvents
(DWORD cEvents, //WSAEVENT对象构成的数组里的事件对象数量
const WSAEVENT FAR * lphEvents, //指向事件对象数组
BOOL fWaitAll, //True,所有事件对象变成"己传信"时,返回,否则有一个变就返回
DWORD dwTimeout, //最多一个网络事件发生时,函数的等待时间
BOOL fAlertable //FALSE
);返回造成函数返回的事件对象的索引,从而知道发生在哪个套接字上
例:
Index = WSAWaitForMultipleEvents(....);
MyEvent = EvnetArray[Index-WSA_WAIT_EVENT_0];
接下来,调查发生网络事件的类型(WSAEnumNetworkEvents):
int WSAEnumNetworkEvent(
SOCKET s, //对应产生网络事件的套接字
WSAEVENT hEventObject, //准备置成"未传信"状态的事件句柄
LPWSANETWORKEVENTS lpNetworkEvents //指向 WSANETWORKEVENTS结构,如下:
);
typedef struct _WSANETWORKEVENTS
{
long lNetworkEvents; //对应套接字上发生的所有网络事件类型(可能多个)
int iErrorCode[FD_MAX_EVENTS]; //错误代码数组,同lNetworkEVents关联
//每个错误索引是在事件类型后加'_BIT'
} WSANETWORKEVENTS,FAR* LPWSANETWORKEVENTS;
例:
// Process FD_READ notification
if (NetworkEvents.lNetworkEvents & FD_READ)
{
if(NetworkEvents.iErrorCode[FD_READ_BIT] !=0)
{
//Error
}
}
例:采用WSAEventSelect I/O模型的示范服务器源代码
SOCKET Socket[WSA_MAXIMUM_WAIT_EVENTS];
WSAEVENT Event[WSA_MAXINUM_WAIT_EVENTS];
SOCKET Accept,
Listen;
DWORD EventTotal = 0;
DWORD Index;
//Set up a TCP socket for listening on port 5150
Listen = socket(PF_INET,SOCK_STREAM,0);
InternetAddr.sin_family = AF_INET;
InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
InternetAddr.sin_port = htons(5150);
bind(Listen,(PSOCKADDR) &InternetAddr,sizeof(InternetAddr));
NewEvent = WSACreateEvent();
WSAEventSelect(Listen,NewEvnet,FD_ACCEPT|FD_CLOSE);
listen(Listen,5);
Socket[EventTotal] = Listen;
Event[EventTotal] = NewEvent;
EventTotal++;
while (TRUE)
{
//Wait for network events on all sockets
Index = WSAWaitForMultipleEvents(EventTotal,EventArray,FALSE,WSA_INFINITE,FALSE);
WSAEnumNewWorkEvents(SocketArray[Index-WSA_WAIT_EVENT_0],
EventArray[Index-WSA_WAIT_EVENT_0],
&NetworkEvents);
//Check for FD_ACCEPT messages
if (NetworkEvents.lNetworkEvents & FD_ACCEPT)
{
if (NetworkEvents.iErrorCode[FD_ACCEPT_BIT] !=0)
{
//Error
break;
}
//Accept a new connection and add it to the socket and event lists
Accept = accept(SocketArray[Index-WSA_WAIT_EVENT_0],NULL,NULL);
//We cannot process more than WSA_MAXIMUM_WAIT_EVENTS sockets ,
//so close the accepted socket
if (EventTotal > WSA_MAXIMUM_WAIT_EVENTS)
{
printf("........");
closesocket (Accept);
break;
}
NewEvent = WSACreateEvent();
WSAEventSelect(Accept,NewEvent,FD_READ|FD_WRITE|FD_CLOSE);
Event[EventTotal] = NewEvent;
Socket[EventTotal]= Accept;
EventTotal++;
prinrt("Socket %d connect/n",Accept);
}
//Process FD_READ notification
if (NetworkEvents.lNetworkEvents & FD_READ)
{
if (NetworkEvents.iErrorCode[FD_READ_BIT !=0])
{
//Error
break;
}
//Read data from the socket
recv(Socket[Index-WSA_WAIT_EVENT_0],buffer,sizeof(buffer),0);
}
//process FD_WRITE notitication
if (NetworkEvents.lNetworkEvents & FD_WRITE)
{
if (NetworkEvents.iErrorCode[FD_WRITE_BIT] !=0)
{
//Error
break;
}
send(Socket[Index-WSA_WAIT_EVENT_0],buffer,sizeof(buffer),0);
}
if (NetworkEvents.lNetworkEvents & FD_CLOSE)
{
if(NetworkEvents.iErrorCode[FD_CLOSE_BIT] !=0)
{
//Error
break;
}
closesocket (Socket[Index-WSA_WAIT_EVENT_0]);
//Remove socket and associated event from the Socket and Event arrays and
//decrement eventTotal
CompressArrays(Event,Socket,& EventTotal);
}
}