• socket异步通信-如何设置成非阻塞模式、非阻塞模式下判断connect成功(失败)、判断recv/recvfrom成功(失败)、判断send/sendto


    原文:
    将一个socket 设置成阻塞模式和非阻塞模式,使用fcntl方法,即:

    设置成非阻塞模式:

    先用fcntl的F_GETFL获取flags,用F_SETFL设置flags|O_NONBLOCK;        

    即:

          flags = fcntl(sockfd, F_GETFL, 0);                        //获取文件的flags值。

          fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);   //设置成非阻塞模式;

    同时在接收和发送数据时,需要使用MSG_DONTWAIT标志

    即:

          在recv,recvfrom和send,sendto数据时,将flag设置为MSG_DONTWAIT。

    设置成阻塞模式:

    先用fcntl的F_GETFL获取flags,用F_SETFL设置flags&~O_NONBLOCK;      

    即:

         flags  = fcntl(sockfd,F_GETFL,0);                          //获取文件的flags值。

         fcntl(sockfd,F_SETFL,flags&~O_NONBLOCK);    //设置成阻塞模式;            

    同时在接收和发送数据时,需要使用阻塞标志

    即:

            在recv,recvfrom和send,sendto数据时,将flag设置为0,默认是阻塞。       

    在将socket设置成非阻塞模式后,每次的对于sockfd 的操作都是非阻塞的;

    非阻塞模式下:

    connect   

           =0   当返回0时,表示立即创建了socket链接,

           <0   当返回-1时,需要判断errno是否是EINPROGRESS(表示当前进程正在处理),否则失败。

           例如:下面会有select或epoll监听fd是否建立链接,

            select监听connect是否成功的例子,注意getsockopt验证,因为三次握手的第三个ACK有可能会丢失,但是客户端认为链接已经建立:

    int ret = ::connect(_socket_fd, add.addr(), add.length());
    if(ret == 0)
    {
                //建立链接成功
    }
    else if(ret < 0 && errno == EINPROGRESS)          //errno == EINPROGRESS表示正在建立链接
    {
         // 等待连接完成,errno == EINPROGRESS表示正在建立链接
         fd_set set;
         FD_ZERO(&set);
         FD_SET(_socket_fd,&set);  //相反的是FD_CLR(_sock_fd,&set)


         time_t = 10;          //(超时时间设置为10毫秒)
         struct timeval timeo;
         timeo.tv_sec = timeout / 1000; 
         timeo.tv_usec = (timeout % 1000) * 1000;

         int retval = select(_socket_fd + 1, NULL, &set, NULL, &timeo);           //事件监听
         if(retval < 0)   
         {
                //建立链接错误close(_socket_fd)
         }
         else if(retval == 0) // 超时
         {
                //超时链接没有建立close(_socket_fd)
         }

         //将检测到_socket_fd读事件或写时间,并不能说明connect成功
         if(FD_ISSET(_socket_fd,&set))
         {
               int error = 0;
               socklen_t len = sizeof(error);
               if(getsockopt(_socket_fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
               {
                      //建立简介失败close(_socket_fd)
               }
               if(error != 0) // 失败
               {
                      //建立链接失败close(_socket_fd)
               }
               else
                {
                      //建立链接成功
                }
         }
    }
    else
    {
          //出现错误 close(_sock_fd)
    }

    注意:这里主要是想强调当epoll或select监听到sockfd上有EPOLL_IN或EPOLL_OUT时,即读写事件时,并不能说明链接已经建立,如上面的代码。

    /*

    int error = 0;
    socklen_t ilen = sizeof(error);
    ret = getsockopt(fd,SOL_SOCKET,SO_ERROR,&error,&ilen);
    if(ret < 0)
    {
          //说明链接建立失败,close(fd);
    }
    else if(error != 0 )
    {

         //说明链接建立失败,close(fd);
    }

    else

    {

           //说明链接建立成功。即可以向fd上写数据。

    }

     */                      

     recv 和 recvfrom

           =0  当返回值为0时,表示对端已经关闭了这个链接,我们应该自己关闭这个链接,即close(sockfd)。另外因为异步操作会用select或epoll做事件触发,所以:

           1、如果使用select,应该使用FD_CLR(sockfd,fd_set)将sockfd清除掉,不再监听。

           2、如果使用epoll,系统会自己将sockfd清除掉,不再进行监听。

           >当返回值大于0 且 小于sizeof(buffer)时,表示数据肯定读完。(如果等于sizeof(buffer),可能有数据还没读,应该继续读,不可能有大于)

           <0 当返回值小于0,即等于-1时,分情况判断:

            1、如果   errno   为  EAGAINE  或 EWOULDBLOCK                                      

                    表示暂时无数据可读,可以继续读,或者等待epoll或select的后续通知。(EAGAINE,EWOULDBLOCK产生的

             原因:可能是多进程读同一个sockfd,可能一个进程读到数据,其他进程就读取不到数据(类似惊群效应),当然

             单个进程也可能出现这种情况。对于这种错误,不需用close(sockfd)。可以等待select或epoll的下一次触发,

             继续读。)

             2、如果   errno   为  EINTR

                    表示被中断了,可以继续读,或者等待epoll或select后续的通知。

                    否则,真的是读取数据失败。(此时应该close(sockfd))

    send和sendto      

            返回值是实际发送的字符数,因为我们知道要发送的总长度,所以,如果没有发送完,我们可以继续发送。

              <0 当返回值为 -1   时, 我们需要判断  errno:

                    1、如果errno为  EAGAINE   或 EWOULDBLOCK ,表示当前缓冲区写满,可以继续写,

                          或者等待epoll或select的后续通知,一旦有缓冲区,就会触发写操作,这个也是经常利用的一个特性。  

                     2、如果errno为EINTR  ,表示被中断了,可以继续写,或者等待epoll或select的后续通知。

                           否则真的出错了,即errno不为EAGAINE或EWOULDBLOCK或EINTR,此时应该close(sockfd)

              >=0 >=0且不等于要求发送的长度,应该继续send,如果等于要求发送的长度,发送完毕。

  • 相关阅读:
    JAVA基础:JAVA代码编写的30条建议
    Oracle学习之三 程序控制结构
    ExtJs 常用代码片段(7.30更新)
    ExtJs4 之数据模型
    .NET 大杂烩
    .NET 导出Excel
    Javascript实现IE直接打印
    C# Timer用法及实例
    AjaxPro实现无刷新多级联动实例
    VB6.0输入文本写进UTF8格式的文本文件
  • 原文地址:https://www.cnblogs.com/liyulong1982/p/5072057.html
Copyright © 2020-2023  润新知