在正式开始之前先来先来看一下Winsock的I/O模型。与I/O模式不同,I/O模型讨论的是在软件系统层面上对套接口上的I/O进行管理及处理的方式。常用的Winsock I/O模型有5种:select,WSAAsyncSelect,WSAEventSelect,重叠I/O以及I/O完成端口。
1.I/O复用-select
Windows的select函数由Berkeley Socket继承而来,当调用select函数时,系统会阻塞在该函数上直到超时或者预设定的某个I/O条件(如套接口上有数据可读)得到满足,此时可进行相应的I/O操作(如读数据)并能立即得到结果。与普通的阻塞模式相比,该模型的优势主要体现在它能同时判断多个套接口(套接口集合,fd_set)的多种I/O状态(read,write,exception)。一次成功的select返回,表明至少有一个套接口满足了一个I/O状态要求。具体是哪些套接口,哪些I/O条件得到了满足需要进行检测,这也是称select为I/O复用的原因。
int select(int nfds,fd_set FAR *readfds,fd_set FAR *writefds,fd_set FAR *exceptfds,const struct timeval FAR *timeout);
fd_set结构在很多Winsock函数中都会用到,该结构体定义如下:
typedef struct fd_set{
u_int fd_count;
SOCKET fd_array[FD_SETSIZE]
} fd_set;
我们一般不会直接对fd_set进行操作,套接口API中提供了一整套FD_XXX宏定义来完成各种操作任务,这些宏定义如下:
FD_ZERO(*set)
该宏通过将set的fd_count域置为0来完成对该fd_set的初始化。
FD_SET(s,*set)
将套接口描述字s加入集合set。
FD_ISSET(s,*set)
检查套接字s是否在set中,如果在返回非0值,否则返回0。
FD_CLR(s,*set)
从集合set中去掉套接字s。
select函数的第5个参数timeout为输入参数[IN],该参数以TIMEVAL结构的形式告诉select函数需要等待的时间。其中,tv_sec字段是以秒为单位的时间值。如果该参数值设为NULL,那么select将以阻塞模式工作;如果设置为(0,0),那么select会立即返回,由此可以对套接口状态进行“轮询”,但出于对性能方面的考虑,一般不会这么做。TIMEVAL结构的定义如下:
struct timeval{
long tv_sec; // seconds
long tv_usec; // microseconds
};
select函数的返回值有3种情况。
SOCKET_ERROR:表明select函数调用出错,这时可以使用WSAGetLastError函数来获得错误码。
0:表明select等待超时,如果第5个参数为NULL,那么不会出现该返回值的情况。
其余均为正常返回,说明下列情况中至少有一种发生了,并且返回值表示满足I/O操作的套接口数。
(1)readfds
调用了listen函数,并且一个TCP连接正等待接受(三次握手已完成),这时调用accept将成功。
有数据可读(包括带外数据,如果开启了SO_OOBINLINE选项)。
TCP连接被关闭,复位或中止。
(2)writefds
数据可以被发出。
内核正处理一个非阻塞的connect,并且连接成功。
(3)exceptfds
内核正处理一个非阻塞的connect,但是连接失败。
有带外数据可读,但是未开启SO_OOBINLINE选项。
有了这些知识,我们就可以开始编写第一种I/O模型的服务器了。下面给出代码:
#pragma comment(lib,"ws2_32.lib")
#include <STDIO.H>
#include <WINSOCK2.H>
int main(int argc,char *argv[])
{
WSADATA wsaData;
WSAStartup(WINSOCK_VERSION,&wsaData); // Initialize winsock enviorment
SOCKET sockListen=socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in addr;
int len=sizeof(addr);
memset(&addr,0,len);
addr.sin_addr.S_un.S_addr=INADDR_ANY;
addr.sin_family=AF_INET;
addr.sin_port=htons(9999);
// bind sockListen to local port 9999
if(bind(sockListen,(struct sockaddr *)&addr,len)==SOCKET_ERROR)
{
printf("bind: %d/n",WSAGetLastError());
goto FINISH;
}
// listen at the port 9999,wait for client's connect
if(listen(sockListen,1)==SOCKET_ERROR)
{
printf("Listen: %d/n",WSAGetLastError());
goto FINISH;
}
SOCKET sockSvr; // declare the server socket
int ret;
char buf[100];
fd_set readSet;
timeval tv; // time
memset(&tv,0,sizeof(tv));
tv.tv_sec=16; // set the time
// the loop structure
while(1)
{
// make the server socket accept the listen socket's connect request
sockSvr=accept(sockListen,NULL,NULL);
if(sockSvr==INVALID_SOCKET)
break;
// select operation
FD_ZERO(&readSet); // set the readSet's domain fd_count to 0
FD_SET(sockSvr,&readSet); // add sockSvr into the set readSet
ret=select(0,&readSet,NULL,NULL,&tv);
// CASE 1: select error
if(ret==SOCKET_ERROR)
{
printf("select: %d/n",WSAGetLastError());
closesocket(sockSvr);
break;
}
// CASE 2: select timeout
if(ret==0)
{
closesocket(sockSvr);
continue;
}
// CASE 3: select OK
if(FD_ISSET(sockSvr,&readSet)) // whether sockSvr is in the readSet
{
while(1) // serve the client
{
memset(buf,0,100);
ret=recv(sockSvr,buf,100,0);
if(ret==SOCKET_ERROR)
{
printf("recv: %d/n",WSAGetLastError());
closesocket(sockSvr);
continue;
}
printf("received: %s length: %d/n",buf,ret);
if(!strcmp(buf,"exit"))
{
printf("the client leaves./n");
break;
}
send(sockSvr,buf,ret,0);
}
closesocket(sockSvr);
}
}
FINISH:
closesocket(sockListen);
WSACleanup();
return 0;
}
剩下2中I/O模型的服务器以后再慢慢研究,呵呵,先把这个好好消化一下。