C++ 非阻塞套接字的使用 (3)

  异步非阻塞套接字避免了死循环的接收问题,但是软件用起来体验还是很差。究其原因,软件在指令的发送、接收上,

采取了一种不合理的方式:在指令的发送后,立刻调用接收函数,等待回令。

  若是采用同步阻塞套接字,那么如果连接出现问题没有回令,那么软件进程会始终等待锁死,这样显然是不合理。于是

采用同步非阻塞的接收方式,timeout被设置为NULL。这时会出现发令与回令的错位问题。这是由于,相应回令没有及时到

达,而接收函数已经返回,第一条发令被当做无回令处理;而此时回令被堆积在了缓冲区,被第二条发令接收到,造成指令错位。

  实际上,这种同步的接收方式是不合理的。如果软件有长连接中接收消息的可能性,那么显然单独设置接受线程是更为合理,

这种主动的接受方式优于被动的调用接收函数的方式。在软件的设计中,通信需要下一番功夫,这是个教训。

  总之,软件的设计已经无法更改,我们需要缓解这个问题,所以只好采用同步非阻塞的结束方式,使用select()函数实现。

  以下内容引用自http://blog.csdn.net/inspiron_110/article/details/4764847

  select模型(选择模型)

  先看一下下面的这句代码:

  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函数

  int select(

    int nfds,

    fd_set FAR *readfds,

    fd_set FAR *writefds,

    fd_set FAR *exceptfds,

    const struct timeval FAR *timeout

  );

  第一个参数不要管,会被系统忽略的。第二个参数是用来检查套接字可读性,也就说检查套接字上是否有数据可读,同样,第三个参数

用来检查数据是否可以发出。最后一个是检查是否有带外数据可读取。

  参数详细的意思请去看MSDN,这里限于篇幅不详细解释了。

  最后一个参数是用来设置select等待多久的,是个结构:

  struct timeval {

    long tv_sec; // seconds

    long tv_usec; // and microseconds

  };

  如果将这个结构设置为(0,0),那么select函数会马上返回。

  说了这么久,select的作用到底是什么?

  他的作用就是:防止在在阻塞模式的套接字里被锁死,避免在非阻塞套接字里重复检查WSAEWOULDBLOCK错误。

  他的工作流程如下:

  1:用FD_ZERO宏来初始化我们感兴趣的fd_set,也就是select函数的第二三四个参数。

  2:用FD_SET宏来将套接字句柄分配给相应的fd_set。

  3:调用select函数。

  4:用FD_ISSET对套接字句柄进行检查,如果我们所关注的那个套接字句柄仍然在开始分配的那个fd_set里,那么

说明马上可以进行相应的IO操作。比如一个分配给select第一个参数的套接字句柄在select返回后仍然在select第一个参数的

fd_set里,那么说明当前数据已经来了,马上可以读取成功而不会被阻塞。

最后附上一段示例代码:

    WSADATA wsa;
    WSAStartup(MAKEWORD(2,2), &wsa);
    
    SOCKET fdServer = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(InternetAddr);
    server.sin_port = htons(iPort);
    
    int ret = bind(fdServer, (sockaddr*)&server, sizeof(server));
    ret = listen(fdServer, 4);

    SOCKET AcceptSocket; 
    fd_set fdread;
    timeval tv;
    int nSize; 

    while(1)
    {
        FD_ZERO(&fdread);//初始化fd_set
        FD_SET(fdServer, &fdread);//分配套接字句柄到相应的fd_set
        
        //这里我们打算让select等待两秒后返回,避免被锁死
        tv.tv_sec = 2;
        tv.tv_usec = 0;
        
        select(0, &fdread, NULL, NULL, &tv);
        
        nSize = sizeof(server);

        //如果套接字句柄还在fd_set里,说明客户端已经有
          connect的请求发过来了,马上可以accept成功
        if (FD_ISSET(fdServer, &fdread))
        {
            AcceptSocket = accept(fdServer,
                ( sockaddr*) &server, &nSize);
    break;
   }
    }

    char buffer[128];
    ZeroMemory(buffer, 128);

    //这里同样可以用select,用法和上面一样
    ret = recv(AcceptSocket,buffer,128,0);

    closesocket(AcceptSocket);
    WSACleanup();
    return;
posted on 2015-01-22 16:39  libcan  阅读(1596)  评论(0编辑  收藏  举报