使用Winsock的简单服务器端程序

本文详细介绍了Windows环境下Winsock的I/O模型之一——select模型的工作原理与应用。通过具体的代码示例,展示了如何利用select函数同时监测多个套接口的状态变化,实现高效地网络通信处理。

在正式开始之前先来先来看一下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模型的服务器以后再慢慢研究,呵呵,先把这个好好消化一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值