SOCKET通信—如何设置成非阻塞模式、该模式下判断connect成功(失败)、判断recv/recvfrom成功(失败)、判断send/sendto成功(失败)

时间:2022-09-09 09:06:49

 http://blog.163.com/xychenbaihu@yeah/blog/static/13222965520117243917934/

SOCKET

       对于一个socket 是阻塞模式还是非阻塞模式的处理方法::

       方法:fcntl 设置;

                   即先用fcntl的F_GETFL获取flags,

                   用F_SETFL设置flags|O_NONBLOCK;        

                   (注意,取消非阻塞的方式是F_SETFL 设置flags&~O_NONBLOCK)

                   并在recv,send 时,将flag参数设置为MSG_DONTWAIT。

             

                                                              实现

 fcntl 函数可以将一个socket 句柄设置成非阻塞模式:
      flags = fcntl(sockfd, F_GETFL, 0);                       //获取文件的flags值。

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

      flags  = fcntl(sockfd,F_GETFL,0);

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

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

      即:recv, send 函数的最后有一个flag 参数可以设置成MSG_DONTWAIT

      recv(sockfd, buff, buff_size,MSG_DONTWAIT);     //非阻塞模式的消息发送

      send(scokfd, buff, buff_size, MSG_DONTWAIT);   //非阻塞模式的消息接受

 设置之后每次的对于sockfd 的操作都是非阻塞的。

(

        connect   当返回0时,表示立即创建了socket链接,
                        当返回-1时,需要判断errno是否是EINPROGRESS(表示当前进程正在处理),否则失败
                        (

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

select的例子:

    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);


         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的例子:

     (//待续)

                                   当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       当返回值为0时,表示对端已经关闭了这个链接,我们应该自己关闭这个链接,

                       即close(sockfd)。(因为异步操作会用select或epoll做事件触发,所以:)

                                   如果使用select,应该将sockfd清除掉,不再监听。

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

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

                       当返回值小于0,即等于-1时:

                                     如果   errno   为  EAGAINE  或 EWOULDBLOCK                                      

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

                                     如果   errno   为  EINTR

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

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

                      (

                        发生EAGAINE,EWOULDBLOCK,EINTR错误时,

                        表明socket没有问题,即不用close(sockfd);

                         产生的原因:

                                 EAGAINE    和   EWOULDBLOCK  可能是多进程读同一个sockfd,可能一个进程读

                         到数据,其他进程就读取不到数据,当然单个进程也可能出现这种情况。

                         对于这种错误,不需用close(sockfd)。

                                可以等待select或epoll的下一次触发,继续读。

                     )

                      参看自己的blog::   http://blog.163.com/xychenbaihu@yeah/blog/static/132229655201094104357111/

        (recvfrom的判断也是一样的)

       send      返回值是实际发送的字符数。

                     因为我们知道要发送的总长度,所以,如果没有发送完,我们可以继续发送。

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

                         如果errno为  EAGAINE   或 EWOULDBLOCK ,表示当前缓冲区写满,可以继续写,或者等待epoll或select的后续通知。  

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

                       (

                        发生EAGAINE,EWOULDBLOCK,EINTR错误时,

                         表明socket没有问题,即不用close(sockfd);

                         产生的原因:

                                 EAGAINE    和   EWOULDBLOCK  可能是多进程写同一个sockfd,可能一个进程写

                         了数据,其他进程就不能写数据,当然单个进程也可能出现这种情况。

                         对于这种错误,不需用close(sockfd)。

                                可以等待select或epoll的触发,下一次继续写。

                     )

                    参看自己的blog::   http://blog.163.com/xychenbaihu@yeah/blog/static/132229655201094104357111/

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

       (sendto的判断也是一样)

)             

1.设置调用closesocket()后,仍可继续重用该socket。调用closesocket()一般不会立即关闭socket,而经历TIME_WAIT的过程。
  BOOL bReuseaddr = FALSE;
  setsockopt( s, SOL_SOCKET, SO_REUSEADDR, ( const char* )&bReuseaddr, sizeof( BOOL ) );
  2. 如果要已经处于连接状态的soket在调用closesocket()后强制关闭,不经历TIME_WAIT的过程:
  BOOL bDontLinger = TRUE;
  setsockopt( s, SOL_SOCKET, SO_DONTLINGER, ( const char* )&bDontLinger, sizeof( BOOL ) );
  3.在send(),recv()过程中有时由于网络状况等原因,收发不能预期进行,可以设置收发时限:
  int nNetTimeout = 1000; //1秒
  //发送时限
  setsockopt( socket, SOL_SOCKET, SO_SNDTIMEO, ( char * )&nNetTimeout, sizeof( int ) );
  //接收时限
  setsockopt( socket, SOL_SOCKET, SO_RCVTIMEO, ( char * )&nNetTimeout, sizeof( int ) );
  4.在send()的时候,返回的是实际发送出去的字节(同步)或发送到socket缓冲区的字节(异步);系统默认的状态发送和接收一次为8688字节(约
  为8.5K);在实际的过程中如果发送或是接收的数据量比较大,可以设置socket缓冲区,避免send(),recv()不断的循环收发:
  // 接收缓冲区
  int nRecvBuf = 32 * 1024; //设置为32K
  setsockopt( s, SOL_SOCKET, SO_RCVBUF, ( const char* )&nRecvBuf, sizeof( int ) );
  //发送缓冲区
  int nSendBuf = 32*1024; //设置为32K
  setsockopt( s, SOL_SOCKET, SO_SNDBUF, ( const char* )&nSendBuf, sizeof( int ) );
  5.在发送数据的时,不执行由系统缓冲区到socket缓冲区的拷贝,以提高程序的性能:
  int nZero = 0;
  setsockopt( socket, SOL_SOCKET, SO_SNDBUF, ( char * )&nZero, sizeof( nZero ) );
  6.在接收数据时,不执行将socket缓冲区的内容拷贝到系统缓冲区:
  int nZero = 0;
  setsockopt( s, SOL_SOCKET, SO_RCVBUF, ( char * )&nZero, sizeof( int ) );
  7.一般在发送UDP数据报的时候,希望该socket发送的数据具有广播特性:
  BOOL bBroadcast = TRUE;
  setsockopt( s, SOL_SOCKET, SO_BROADCAST, ( const char* )&bBroadcast, sizeof( BOOL ) );
  8.在client连接服务器过程中,如果处于非阻塞模式下的socket在connect()的过程中可以设置connect()延时,直到accpet()被调用(此设置只
  有在非阻塞的过程中有显著的作用,在阻塞的函数调用中作用不大)
  BOOL bConditionalAccept = TRUE;
  setsockopt( s, SOL_SOCKET, SO_CONDITIONAL_ACCEPT, ( const char* )&bConditionalAccept, sizeof( BOOL ) );
  9.如果在发送数据的过程中send()没有完成,还有数据没发送,而调用了closesocket(),以前一般采取的措施是shutdown(s,SD_BOTH),但是数
  据将会丢失。
  某些具体程序要求待未发送完的数据发送出去后再关闭socket,可通过设置让程序满足要求:
  struct linger {
  u_short l_onoff;
  u_short l_linger;
  };
  linger m_sLinger;
  m_sLinger.l_onoff = 1; //在调用closesocket()时还有数据未发送完,允许等待
  // 若m_sLinger.l_onoff=0;则调用closesocket()后强制关闭
  m_sLinger.l_linger = 5; //设置等待时间为5秒
  setsockopt( s, SOL_SOCKET, SO_LINGER, ( const char* )&m_sLinger, sizeof( linger ) );