评论

收藏

[Linux] linux下非阻塞模式网络通讯模型示例分享

服务系统 服务系统 发布于:2021-09-23 21:36 | 阅读数:511 | 评论:0

这篇文章主要介绍了linux下非阻塞模式网络通讯模型示例,需要的朋友可以参考下
复制代码 代码如下:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <sysexits.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>
 
#ifdef __ENABLED_DEBUG_INFO_OUTPUT__
  #define DEBUG_OUTPUT(format) printf( "\nFile: %s : Line: %d ->Function: %s\n"format"\n", __BASE_FILE__, __LINE__, __FUNCTION__ )
  #define DEBUG_OUTPUT_PARA(format,...) printf( "\nFile: %s : Line: %d ->Function: %s\n"format"\n", __BASE_FILE__, __LINE__, __FUNCTION__, __VA_ARGS__ )
#else
  #define DEBUG_OUTPUT(format)
  #define DEBUG_OUTPUT_PARA(format,...)
#endif
// @brief 非阻塞等待套接字是否可读/写
// @param[in] sockfd 套接字描述符
// @param[in] bWhichSet true - 可读集; false - 可写集;
// @param[in] uiTimeOutMS 超时时长(单位:微秒);
// @pre scokfd 有效套接字描述符,即大于等于零(>=0)
// @return 此函数执行结果
// @return  0 - 可以读/写;
//     -1 - 参数不合法;
//     -2 - 检测已超时;
// @note uiTimeOutMS 超时时长,设为零(0),则不等待超时
static inline int
wait_rw_able( int      sockfd,
        bool     bWhichSet,
        unsigned int uiTimeOutMS )
{
  // 默认为检测已超时
  int iReturnValue = -2;
  // 可读描述符集
  fd_set rset;
  // 可写描述符集
  fd_set wset;
  // select 将等待的时间
  timeval tv;
  do // 非循环,只是为了保证函数只有一个返回点
  {
    // 参数不合法
    if ( 0 > sockfd )
    {
      iReturnValue = -1;
      break;
    }
    // 注:每次调用 select 之前都要重设一次!
    tv.tv_sec  = 0;
    tv.tv_usec = uiTimeOutMS;
    // 检测是否可读
    if ( true == bWhichSet )
    {
      // 清除其所有位
      FD_ZERO( &rset );
      // 设置关心的描述符
      FD_SET( sockfd, &rset );
      // 大于零(0) - 有套接字可读,零(0) - 没有,负数 - 出错
      if ( 0 < select( sockfd + 1, // 从描述符零(0)开始搜索,故此要对套接字描述符加壹(1)
               &rset,    // 可读描述符集
               NULL,     // 可写描述符集
               NULL,     // 异常描述符集
               &tv ) )   // 等待时间
      {
        // 可读描述符是我们的套接字
        if ( FD_ISSET( sockfd, &rset ) )
        {
          iReturnValue = 0;
          break;
        }
      }
    }
    // 检测是否可写
    else
    {
      // 清除其所有位
      FD_ZERO( &wset );
      // 设置关心的描述符
      FD_SET( sockfd, &wset );
      // 大于零(0) - 有套接字可读,零(0) - 没有,负数 - 出错
      if ( 0 < select( sockfd + 1, // 从描述符零(0)开始搜索,故此要对套接字描述符加壹(1)
               NULL,     // 可读描述符集
               &wset,    // 可写描述符集
               NULL,     // 异常描述符集
               &tv ) )   // 等待时间
      {
        // 可读描述符是我们的套接字
        if ( FD_ISSET( sockfd,
                 &wset ) )
        {
          iReturnValue = 0;
          break;
        }
      }
    }
  }while( 0 );
  return iReturnValue;
}
// @brief 发送且接收通讯协议
// @param[int][out] pucSRBuffer 发送且接收协议字符缓冲区指针
// @param[int] usBufferLen 发送且接收协议字符缓冲区大小
// @pre pucSRBuffer 有效的协议字符缓冲区指针,且字符串长度大于零(0)
// @return 此函数执行结果
// @retval   0 成功
// @retval  -1 参数不合法
// @retval  -2 创建连接服务端的套接字失败
// @retval  -3 设置连接服务端的套接字为非阻塞模式失败
// @retval  -4 套按字非阻塞模式下也不可写
// @retval  -5 调用 getsockopt 函数失败
// @retval  -6 调用 connect 函数失败
// @retval  -7 设置连接服务端的套接字为阻塞模式失败
// @retval  -8 发送协议数据失败
// @retval  -9 等待服务端返回数据超时
// @retval -10 调用 recv 函数出错
// @retval -11 pucSRBuffer 指向的缓冲区空间不足
int
send_receive_data( unsigned char* const pucSRBuffer,
           const unsigned short usBufferLen )
{
  // 本函数执行结果返回值
  int     iResult = 0; // 默认为零(0) 表示成功
  // 连接服务端的 TCP 套接字
  int     iServerSocket = -1;
  // 服务端IP与端口
  sockaddr_in sServerAddr;
  // I/O 状态标识值
  int iValue = 1;
  // 获取套接字错误状态码
  int     iSo_Error = 0;
  socklen_t So_Error_len = sizeof( iSo_Error );
  // 接收到的通讯协议数据长度
  unsigned short usRealReceivedData = 0;
  do // 非循环,只是为了减少分支缩进和保证进出口唯一
  {
    // 1.检查参数是否合法
    if ( ( NULL == pucSRBuffer ) ||
       (  0 >= usBufferLen ) ||
       (  0 == pucSRBuffer[0] ) )
    {
      DEBUG_OUTPUT( "Invalid parameter" );
      iResult = -1;
      break;
    }
    // 2.创建连接服务端的套接字
    iServerSocket = socket( AF_INET,   // IPv4 协议
                SOCK_STREAM, // TCP  套接字协议类型
                0 );     // 默认协议,通常设置为零(0)
    if ( 0 > iServerSocket )
    {
      DEBUG_OUTPUT( "Create socket is failed" );
      iResult = -2;
      break;
    }
    // 3.为了调用 connect 函数不阻塞,设置连接服务端的套接字为非阻塞模式
    iValue = 1; //
    iResult = ioctl( iServerSocket, // 服务端收发套接字
             FIONBIO,     // 设置或清除非阻塞I/O标志
             &iValue );   // 零(0) - 清除,非零(0) - 设置
    if ( 0 > iResult )
    {
      DEBUG_OUTPUT_PARA( "Call ioctl to set I/O asynchronization is failed, return %d",
                 iResult );
      iResult = -3;
      break;
    }
    sServerAddr.sin_family = AF_INET;
    inet_pton( AF_INET,
           m_caServerIP,
           &sServerAddr.sin_addr );
    sServerAddr.sin_port = htons( m_usServerPort );
    // 4.连接服务端
    iResult = connect( iServerSocket,
               (sockaddr*)&sServerAddr,
               sizeof( sServerAddr ) );
    // 调用 connect 函数,正常情况下,因为 TCP 三次握手需要一些时间,
    // 而非阻塞调用只要不能立即完成就会返回错误,所以这里会返回 EINPROGRESS ,
    // 表示在建立连接但还没有完成。
    if ( 0 != iResult ) // 成功则返回零(0)
    {
      // 内核中对 connect 有超时限制是 75 秒,为了加快反应速度此处设为750毫秒。
      // 注:无论连接与否,都会返回可写,除非有错误发生,这里仅是缩短等待连接的时间而已。
      iResult = wait_rw_able( iServerSocket,
                  false,   // 是否可写
                  750000  ); // 750毫秒
      if ( 0 != iResult )
      {
        DEBUG_OUTPUT( "Can't write in asynchronization" );
        iResult = -4;
        break;
      }
      if ( 0 > getsockopt( iServerSocket,
                 SOL_SOCKET,
                 SO_ERROR,
                 &iSo_Error,
                 &So_Error_len ) )
      {
        DEBUG_OUTPUT( "Call getsockopt is failed" );
        iResult = -5;
        break;
      }
      // 为零(0)才说明连接成功
      if ( 0 != iSo_Error )
      {
        DEBUG_OUTPUT( "Call connect is failed" );
        iResult = -6;
        break;
      }
    }
    // 5.调用 connect 函数连接服务端成功,再设置套接字为阻塞模式(便于管理)
    iValue = 0;
    iResult = ioctl( iServerSocket, // 服务端收发套接字
             FIONBIO,     // 设置或清除非阻塞I/O标志
             &iValue );   // 零(0) - 清除,非零(0) - 设置
    if ( 0 > iResult )
    {
      DEBUG_OUTPUT_PARA( "Call ioctl to set I/O synchronization is failed, return %d",
                 iResult );
      iResult = -7;
      break;
    }
    // 6.发送协议数据
    iResult = send( iServerSocket,
            (const char*)pucSRBuffer,
            strlen( (const char*)pucSRBuffer ),
            0 );
    // 发送异常则停止收发
    if ( iResult != (int)strlen( (const char*)pucSRBuffer ) )
    {
      DEBUG_OUTPUT( "Call send is failed" );
      iResult = -8;
      break;
    }
    // 7.判断是否可读 - 即服务端是否返回数据
    iResult = wait_rw_able( iServerSocket, // 服务端收发套接字
                true,      // 是否可读
                750000  );   // 750毫秒
    if ( 0 != iResult )
    {
      DEBUG_OUTPUT( "Waitting for recevie data has time out" );
      iResult = -9;
      break;
    }
    // 清零(0),方便调用者计算收到的通讯协议数据长度
    memset( pucSRBuffer, 0, usBufferLen );
    do
    {
      // 8.从客户端接收数据
      iResult = recv( iServerSocket,            // 服务端收发套接字
              pucSRBuffer + usRealReceivedData,   // 存放数据的缓冲区地址
              usBufferLen - usRealReceivedData - 1, // 每次读出的字节
              0 );                  // 默认为零(0),无特殊要求
      // 返回负数为出错了,直接跳出不再等待尝试接收新数据
      if ( 0 > iResult )
      {
        DEBUG_OUTPUT_PARA( "Call recv is failed, return %d", iResult );
        iResult = -10;
        break;
      }
        // 接收数据时网络中断就会返回零(0)
        if ( 0 == iResult )
        {
          break;
        }
      usRealReceivedData += iResult;
      // 传出参数所指缓冲区空间不足矣放下全部应签数据
      if ( usBufferLen <= usRealReceivedData )
      {
        DEBUG_OUTPUT( "pucSRBuffer is not superfluous space" );
        iResult = -11;
        break;
      }
    }while( 0 == wait_rw_able( iServerSocket,
                   true,    // 是否可读
                   750000  ) ); // 750毫秒
    // 收数据时出错了,则直接跳出返回
    if ( 0 > iResult )
    {
      break;
    }
    // 执行至此发收通讯数据完毕
    iResult = 0;
    break;
  }while( 0 );
  // 套接字创建成功,则要释放资源
  if ( -1 != iServerSocket )
  {
    close( iServerSocket );
  }
  return iResult;
}
关注下面的标签,发现更多相似文章