winsock i/o的select模型

本文部分截取他人博客,自己总结完成,附带各自链接:

http://blog.csdn.net/yunboy4/archive/2009/09/18/4566124.aspx

http://dev.csdn.net/article/82132.shtm

http://uestczly.blog.163.com/blog/static/111354231200924105116788/

http://m.cnblogs.com/32508/1089271.html

 

先看一下下面的这句代码:
int iResult = recv(s, buffer,1024);
这是用来接收数据的,在默认的阻塞模式下的套接字里,recv会阻塞在那里,直到套接字连接上有数据可读,把数据读到buffer里后recv函数才会返回,不然就会一直阻塞在那里。在单线程的程序里出现这种情况会导致主线程(单线程程序里只有一个默认的主线程)被阻塞,这样整个程序被锁死在这里,如果永远没数据发送过来,那么程序就会被永远锁死。这个问题可以用多线程解决,但是在有多个套接字连接的情况下,这不是一个好的选择,扩展性很差(套接字很多的话,线程也必然很多,线程切换浪费很多时间)。Select模型就是为了解决这个问题而出现的。
再看代码:

int iResult = ioctlsocket(s, FIOBIO, (unsigned long *)&ul);
iResult = recv(s, buffer,1024);

这一次recv的调用不管套接字连接上有没有数据可以接收都会马上返回。原因就在于我们用ioctlsocket把套接字设置为非阻塞模式了。不过你跟踪一下就会发现,在没有数据的情况下,recv确实是马上返回了,但是也返回了一个错误:WSAEWOULDBLOCK,意思就是请求的操作没有成功完成。看到这里很多人可能会说,那么就重复调用recv并检查返回值,直到成功为止,但是这样做效率很成问题,开销太大。

select模型就是用来解决上述问题而设计的。

 

select(选择)模型是winsock中常见的I/O模型。之所以称其为“select模型”,是由于它的“中心思想”是利用select函数,实现对I/O的管理!最初设计该模型时,主要面向的是某些使用Unix操作系统的计算机,它们采用的是Berkeley套接字方案。select模型已经集成到Winsock1.1中。

 

该函数定义如下:

      int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,

                     struct timeval *timeout);

            n:该参数忽略,之所以保留是因为和早期的berkeley函数兼容

            readfds: 该参数为用于检查是否有数据需要读入(接收)的集合

            writefds:该参数为用于检查是否有数据需要写出(发送)的集合

            exceptfds:该参数为用于检查是否有"例外数据"需要读取的集合.

            timeout:设置select函数检查的超时时间,如果设为NULL,则无限制等待直到上述集合有改变

            为止,如果设置为0,则select函数立即返回。

      需要注意的是,上述三个集合之中,至少一个不为NULL,不为NULL的集合里,至少一个不为空,否则函数就没有等待的目标而导致该函数失去作用。

 

其中fd_set为WINSOCK提供的一个文件集描述符(简单的说就是一个集合而已),里面能够容纳数个SOCKET句柄。这就不得不提到select模型的另外几个核心的函数.

     FD_SET(Socket s,fd_set *SET)   //将s句柄放入集合SET

     FD_ZERO(fd_set *SET)    //清空SET

     FD_CLR(Socket s,fd_set *SET)  //  从SET中清除s

     FD_ISSET(Socket s,fd_set *SET)   //检查s是否在集合SET内,是返回TRUE,否返回FALSE

 

select函数有个很重要的特性,就是会自动删除相应集合里不符合要求的SOCKET句柄,例如,SOCKET S作为需要检查的对象,用FD_SET放入readfds(当然readfds是自己申明的),然后用select函数进行检查,设置timeout为10s,那么如果10s内S没有需要读入的数据,select函数返回的时候会自动将S从readfds中删除,那么在用FD_ISSET检查的时候便可以分辨哪些是可读可写的。

      一般程序大致走向如下。

     1. SOCKET相关操作(create,bind,listen,accept……)

     2.  建立readfds,writefds等集合

     3.  FD_ZERO初始化集合

     4.  FD_SET将SOCKET句柄放入感兴趣的集合内

     5.  适时调用select

     6.  FD_ISSET检查感兴趣集合

     7.  返回 3(一般都是有这一步)

 

如果select函数每次会自动删除不符合要求的S,那如果采取顺序的执行方式肯定不行了,为什么?因为S如果此时没有数据读入,则从readfds中删除了,那么如果下次仍然需要检查,又必须要调用FD_SET函数才行,这样很自然的就形成了一个循环。虽然MICROSOFT的教科书上出于对程序性能、效率的考虑不建议使用轮询的方式(即循环)进行检查,但是如果该应用对实时有一定要求,而且会多次发送数据,却避免不了,而且存在一个严重的问题,如果在一个线程里实现的话,FD_ISSET必须要得按顺序检查集合,这就导致了程序可能必须得先读,然后再写或者完全相反(这样也就不符合逻辑了)。

 

最后讨论select模型的优缺点,从一个可以使用的应用程序来看,select模型几乎没有什么价值,唯一的优点便是模型十分简单,容易理解,可以一次提供对多个套接字的服务(虽然不太适合),从某种程度上避免了多线程(其实还不如使用阻塞函数的多线程效果好(摘抄--还需要研究,不是很理解)),过程十分清晰,其最直接的一点便是避免了程序因为同步的问题卡死。缺点的话用一句概括就是几乎没有实用价值,有时为了达到实时不可避免的要使用轮询来支持实时通信。

        总之,select模型作为异步IO模型的一份子,还是有它的意义,凡事从易到难,select模型应该是个不错的开始。

 

select中限制了连接socket的数目,不过可以通过手工改动,改变socket的数目,最好不要大于1024

//winsock2.h中的定义
#ifndef FD_SETSIZE
#define FD_SETSIZE      64
#endif /* FD_SETSIZE */

typedef struct fd_set {
        u_int fd_count;              /* how many are SET? */
        SOCKET  fd_array[FD_SETSIZE];  /* an array of SOCKETs */
} fd_set;

 

示例代码:

int main(int argc, char* argv[])
{
 
WSADATA wsaData;
WORD sockVersion=MAKEWORD(2,0);
WSAStartup(sockVersion,&wsaData);

SOCKET clientsockarray[FD_SETSIZE - 1];
SOCKET s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(s==INVALID_SOCKET)
{
  printf(" Failed socket()/n");
  WSACleanup();
  return 0;
}

sockaddr_in sin;
sin.sin_family=AF_INET;
sin.sin_port=htons(8888);
sin.sin_addr.S_un.S_addr=INADDR_ANY;

if(bind(s,(LPSOCKADDR)&sin,sizeof(sin))==SOCKET_ERROR)
{
  printf(" Failed bind() %d/n",GetLastError());
  WSACleanup();
  return 0;
}

if(listen(s,63)==SOCKET_ERROR)
{
  printf(" Failed listen()/n");
  WSACleanup();
  return 0;
}

for (int i=0;i < 64; i++)
{
  clientsockarray[i] = INVALID_SOCKET;
}


sockaddr_in remoteAddr;
int nAddrLen=sizeof(remoteAddr);

//char szText[]="Hello World!";
FD_SET fd;
FD_ZERO(&fd);
TIMEVAL tv={30,0};
FD_SET(s,&fd);

printf("server start:%d/n",GetLastError());
int num=0;
while(TRUE)
{
 int nResult;
    nResult = select(0,&fd, NULL,NULL,&tv);
 if (nResult == SOCKET_ERROR)
 {
  printf("select failed /n");
  continue;
 }
 /*
 if (nResult > 0)
 {
  printf("dddddddddd/n");
 }
 */

 if(FD_ISSET(s,&fd))
 {
   SOCKET client;
   client=accept(s,(SOCKADDR*)&remoteAddr,&nAddrLen);
   if (client == INVALID_SOCKET)
   {
      printf("client connect fail %d",GetLastError());
     continue;
   }
   if(!InsertSock(clientsockarray,client))
   {
      printf("³¬¹ý×î´óSOCKET/n");
      closesocket(client);
      continue;
     }
    FD_SET(client,&fd);
 }

nResult = select(0,&fd, NULL,NULL,&tv);
if (nResult == SOCKET_ERROR)
{
     printf("select failed /n");
     continue;
}

for (int nIndex  =  0;  nIndex  <=  FD_SETSIZE - 1;  nIndex++)
{
   f(FD_ISSET(clientsockarray[nIndex], &fd))
   {   
       //printf("aaaa/n");
       char buffer[256];
       int nRet  =  recv(clientsockarray[nIndex],  buffer,  sizeof(buffer),  0);
       if  (nRet  ==  0  ||  nRet  ==  SOCKET_ERROR)
       {
            closesocket(clientsockarray[nIndex]);
            clientsockarray[nIndex] = INVALID_SOCKET;
            continue; 
       }

        printf("has recv data from socket %d : %s",i, buffer);
       printf(" num:%d/n",++num);
  }
}


}

    closesocket(s);
    WSACleanup();
    return 0;

}

 

 

示例代码2:read、write集都有

 

while(1)
    {
        FD_ZERO(&ReadSockFd);
        FD_ZERO(&WriteSockFd);
        ReadSockFd = AllSockFd;
        WriteSockFd = AllSockFd;

        int nRet = select(0, &ReadSockFd, &WriteSockFd, NULL, NULL);   
        if(SOCKET_ERROR == nRet)
        {
            continue;
        }
        //有请求事件发生
        if (FD_ISSET(ListenSock, &ReadSockFd))
        {
            //接受请求
            SOCKET ClientSock;
            u_short Port;
            bool nRe = (*(Pam.pListenSock)).Accept(&ClientSock, 0, &Port);
            if(nRe)
            {
                FD_SET(ClientSock, Pam.pClientSockFd);
                //设置套接字发送缓冲区80K
                int nBuf = SOCKET_BUFF;
                int nBufLen = sizeof(nBuf);
                int nRe = setsockopt(ClientSock, SOL_SOCKET, SO_SNDBUF, (char*)&nBuf, nBufLen);
                if(SOCKET_ERROR == nRe)
                    AfxMessageBox("setsockopt error!");   
                //检查缓冲区是否设置成功
                nRe = getsockopt(ClientSock, SOL_SOCKET, SO_SNDBUF, (char*)&nBuf, &nBufLen);
                if(SOCKET_BUFF != nBuf)
                    AfxMessageBox("检查缓冲区:setsockopt error!");
                else
                    AfxMessageBox("已连接客户端!");
            }
        }
        //判断是否可读或可写
        for(u_int n = 0;n < ClientSockFd.fd_count;n++)
        {
                if(FD_ISSET(ClientSockFd.fd_array[n], &ReadSockFd))        //发现可读
                {       
                        //接收数据
                        //如果失败 删除此元素       
                }
                if(FD_ISSET(ClientSockFd.fd_array[n], &WriteSockFd))    //发现可写 
                {
                        //发送缓冲区未满可以发送
                        //如果失败 删除此元素
                }
        }
    }

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值