Winsock分别提供了“套接字模式”和“套接字I/O模型”,可对一个套接字上的I/O行为加以控制。注意:“套接字模式”和“套接字I/O模型”是无关的,套接字模型的出现是为了解决套接字模式存在的某些限制。
Winsock提供两种套接字模式:锁定和非锁定。
套接字I/O模型:Select,WSAAsyncSelect,WSAEventSelect,Overlapped I/O,Completion Port(完成端口)等
前一段时间已经对Winsock的一些基本函数进行了学习,估计和我一样的初学者已经都看过了这些函数,只是在应用上还存在一些问题,今天我只是在这里介绍一下Select模型,我所写内容也是借鉴别人的经验和别人的一些知识,有错误的地方还需各位指正。
Select模型:
这个模型的目的就是为了,防止应用程序在套接字处于锁定模式中时,在一次I/O绑定调用过程中,被迫进入锁定状态,同时又是防止在套接字处于非锁定状态时,产生WSAEWOULDBLOCK错误。
select函数:
int select(int nfds,fb_set FAR * readfds, fb_set FAR * writefds, fb_set FAR * exceptfds, const struct timeval FAR * timeout);
其中,第一个参数nfds会被忽略。之所以仍然要提供这个参数,只是为了保持与早期的Berkeley套接字应用程序的兼容。大家可注意到三个fd _ set参数:一个用于检查可读性(readfds),一个用于检查可写性(writefds),另一个用于例外数据(exceptfds)。从根本上说,fb_set数据类型代表着一系列特定套接字的集合。
其中,readfds集合包括符合下述任何一个条件的套接字:
1.有数据可以读入。
2.连接已经关闭、重设或中止。
3.假如已调用了listen,而且一个连接正在建立,那么accept函数调用会成功。
writefds集合包括符合下述任何一个条件的套接字:
1.有数据可以发出。
2.如果已完成了对一个非锁定连接调用的处理,连接就会成功。
最后,exceptfds集合包括符合下述任何一个条件的套接字:
1.假如已完成了对一个非锁定连接调用的处理,连接尝试就会失败。
2.有带外(out-of-band,OOB)数据可供读取。
例如,假定我们想测试一个套接字是否“可读”,必须将自己的套接字增添到readfds集合,再等待select函数完成。select完成之后,必须判断自己的套接字是否仍为readfds集合的一部分。若答案是肯定的,便表明该套接字“可读”,可立即着手从它上面读取数据。在三个参数中(readfds、writefds和exceptfds),任何两个都可以是空值( NULL);但是,至少有一个不能为空值!在任何不为空的集合中,必须包含至少一个套接字句柄;否则, select函数便没有任何东西可以等待。最后一个参数timeout对应的是一个指针,它指向一个timeval结构,用于决定select最多等待I / O操作完成多久的时间。如tieout是一个空指针,那么select调用会无限期地“锁定”或停顿下去,直到至少有一个描述符符合指定的条件后结束。对timeval结构的定义如下:
Struct timeval
{
Long tv_sec;
Long tv_usec;
}
其中,tv_sec字段以秒为单位指定等待时间; tv_usec字段则以毫秒为单位指定等待时间。若将超时值设置为(0 ,0),表明select会立即返回,允许应用程序对select操作进行“轮询”。出于对性能方面的考虑,应避免这样的设置。select成功完成后,会在fd_set结构中,返回刚好有未完成的I/O操作的所有套接字句柄的总量。若超过timeval设定的时间,便会返回0。不管由于什么原因,假如select调用失败,都会返回socket_error。用select对套接字进行监视之前,在自己的应用程序中,必须将套接字句柄分配给一个集合,设置好一个或全部读、写以及例外FD_SET结构.
主要涉及到几个宏操作:
1. FD_CLR(s, *set):从s e t中删除套接字s。
2. FD_ISSET(s, *set):检查s是否s e t集合的一名成员;如答案是肯定的是,则返回T R U E。
3.FD_SET(s, *set):将套接字s加入集合s e t。
4.FD_ZERO ( * set ):将s e t初始化成空集合。
在书中提供了简单的一个socket的程序代码,在这里我写了多个终端的简单小程序可供参考:
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 )
{
return;
}
if ( LOBYTE( wsaData.wVersion ) != 1 ||
HIBYTE( wsaData.wVersion ) != 1 )
{
WSACleanup( );
return;
}
char hostname[256];
gethostname(hostname,sizeof(hostname)); //这一代码是为
HOSTENT*hos=gethostbyname(hostname); //了实现自动获取安
CString CS=inet_ntoa(*(struct in_addr*)hos->h_addr_list[0]);// 装程序的主机代码
SOCKET socksrv = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in saServer;
//saServer.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//宏INADDR_ANY定义的是0
saServer.sin_addr.S_un.S_addr = inet_addr(CS);//宏INADDR_ANY定义的是0
saServer.sin_family = AF_INET;
saServer.sin_port = htons(6000);
bind(socksrv, (SOCKADDR*)&saServer, sizeof(SOCKADDR));
SOCKET socketclientarray[FD_SETSIZE - 1];//定义套接字数组
listen(socksrv, 5);
for (int i=0; i<FD_SETSIZE; i++)
{
socketclientarray[i] = INVALID_SOCKET;
}
int ret, nRet;
char buff[100];
memset(&buff, 0, sizeof(buff));
SOCKET clientsocket;
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(socksrv, &readfds);
timeval tv; //无限等待
tv.tv_sec = IFNINITE;
tv.tv_usec = 0;
for (;;)
{
ret = select(0, &readfds, NULL, NULL, &tv);
//不要判断ret是否大于0了,因为这是无限等待,返回时一定会有时间发生。
if(FD_ISSET(socksrv, &readfds))
{
clientsocket = accept(socksrv, NULL, NULL);
if(!insertsocket(socketclientarray, clientsocket))
{
closesocket(clientsocket);
continue;
}
}
//处理待决的套接字
for(int j=0; j<FD_SETSIZE; j++)
{
if(FD_ISSET(socketclientarray[j], &readfds))
{
nRet = recv(socketclientarray[j], buff, sizeof(buff), 0);
if(nRet == 0 || nRet == INVALID_SOCKET)
{
closesocket(socketclientarray[j]);
socketclientarray[j] = INVALID_SOCKET;
continue;
}
//对数据进行处理{.......}
MessageBox(buff);
}
}
FD_ZERO(&readfds);
FD_SET(socksrv, &readfds);
//将所有有效的套接字放入套接字句柄数组
for(int idex=0; idex<FD_SETSIZE; idex++)
{
if(socketclientarray[idex] != INVALID_SOCKET)
FD_SET(socketclientarray[idex], &readfds);
}
}
}
BOOL insertsocket(SOCKET *psockarray, SOCKET sock)
{
for(int idex=0; idex<FD_SETSIZE; idex++)
{
if(psockarray[idex] == INVALID_SOCKET)
{
psockarray[idex] = sock;
break;
}
if(idex == FD_SETSIZE - 1)
return FALSE;
}
return TRUE;
}
上面只是给简要的代码,有的辅助函数也没有给出。用select支持多Client是比较方便的.
本文转自:http://herocao.bokee.com/3593715.html