zoukankan      html  css  js  c++  java
  • 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;
  • 相关阅读:
    软件设计原则
    UML 类图
    Lambda 四大内置核心函数式接口
    Lambda 表达式简介
    vuex源码解析及简单实现
    websocket
    module.export / require 和 export / import
    关于form表单提交时required属性失效的问题
    更改mysql引擎后无法建立外键(navicat)
    关于Android studio SDK的安装与配置
  • 原文地址:https://www.cnblogs.com/libcan/p/4242100.html
Copyright © 2011-2022 走看看