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

(0)

相关推荐

  • Linux/window下怎样查看某个端口被哪个程序/进程占用

    Windows: C:/Users/ewanbao>netstat -aon|findstr "123" TCP 127.0.0.1:55123 0.0.0.0:0 LISTENING 5092 TCP 127.0.0.1:55123 127.0.0.1:55124 ESTABLISHED 5092 TCP 127.0.0.1:55124 127.0.0.1:55123 ESTABLISHED 5092 UDP 0.0.0.0:123 *:* 1416 UDP [::]:123

  • 如何编写Linux设备驱动程序

    Linux是Unix操作系统的一种变种,在Linux下编写驱动程序的原理和思想完全类似于其他的Unix系统,但它dos或window环境下的驱动程序有很大的区别.在Linux环境下设计驱动程序,思想简洁,操作方便,功能也很强大,但是支持函数少,只能依赖kernel中的函数,有些常用的操作要自己来编写,而且调试也不方便.本人这几周来为实验室自行研制的一块多媒体卡编制了驱动程序,获得了一些经验,愿与Linux fans共享,有不当之处,请予指正. 以下的一些文字主要来源于khg,johnsonm的W

  • Linux网络编程之socket文件传输示例

    本文所述示例程序是基于Linux平台的socket网络编程,实现文件传输功能.该示例是基于TCP流协议实现的socket网络文件传输程序.采用C语言编写.最终能够实现传输任何格式文件的文件传输程序. 具体实现代码如下: Server端代码如下: /************************************************************************* > File Name: Server.c > Author: SongLee ***********

  • Perl实现的Linux下socket代理服务器

    大家提供了许多linux开代理的方法,一般用到python等语言,一些服务器可能不会安装,然而perl可以说是linux标配的语言,给大家一款Perl语言的socket代理,代码非常少,而且还支持密码,效果还是不错,感觉很稳定. #!/usr/bin/perl $auth_enabled = 0; $auth_login = "hidden"; $auth_pass = "hidden"; $port = 44269; use IO::Socket::INET; $

  • linux下通过脚本实现自动重启程序

    无论什么程序都不可能完美无缺,理论上,任何程序都有Core Dump的一天,正式运营的程序,尤其是服务器程序,一旦Core Dump,后果不堪设想,有过服务器开发经验的朋友,一定都经历过深夜美梦中,被电话惊醒的惨痛经历,手忙脚乱把服务器重新启动,第二天上班还要被老板一顿狠批.所以,程序发生错误时自动重启变得很重要.这里集中讨论linux实现自动重启程序的方法. linux下实现程序的自动重启有很多方法,这里我们介绍的是通过自己写脚本来实现, 自动重启脚本 假定需要实现重启的程序名为 test ,

  • linux网络编程用到的网络函数详解用和使用示例

    一.概念介绍网络程序分为服务端程序和客户端程序.服务端即提供服务的一方,客户端为请求服务的一方.但实际情况是有些程序的客户端.服务器端角色不是这么明显,即互为客户端和服务端.我们编写网络程序时,一般是基于TCP协议或者UDP协议进行网络通信的.TCP:(Transfer Control Protocol)传输控制协议是一种面向连接的协议, 当我们的网络程序使用这个协议的时候,网络可以保证我们的客户端和服务端之间的传输是可靠的.UDP:(User Datagram Protocol)用户数据报协议

  • linux下实现的2048游戏示例分享

    my2048.c 复制代码 代码如下: #include"my_getch.h"#include"math.h"#include"time.h" #define SPACE() printf("    ")#define RED_NUM(n) printf("\033[;31m%4d",(n))#define GREEN_NUM(n) printf("\033[;32m%4d",(n))

  • linux c程序中获取shell脚本输出的实现方法

    1. 前言Unix界有一句名言:"一行shell脚本胜过万行C程序",虽然这句话有些夸张,但不可否认的是,借助脚本确实能够极大的简化一些编程工作.比如实现一个ping程序来测试网络的连通性,实现ping函数需要写上200~300行代码,为什么不能直接调用系统的ping命令呢?通常在程序中通过 system函数来调用shell命令.但是,system函数仅返回命令是否执行成功,而我们可能需要获得shell命令在控制台上输出的结果.例如,执行外部命令ping后,如果执行失败,我们希望得到p

  • linux中查询dns示例

    dns.c 复制代码 代码如下: /* * DNS Query Program on Linux * * Author : ismdeep@live.com * * *///Header Files#include<stdio.h> //printf#include<string.h> //strlen#include<stdlib.h> //malloc#include<sys/socket.h> //you know what this is for#i

  • 编写Linux实用程序的艺术

    Linux 和其他类 UNIX 系统总是附带了大量的工具,它们执行从显而易见的到不可思议的广泛功能.类 UNIX 编程环境的成功很大程度上归功于工具的高品质和选择,以及这些工具之间相互衔接的简易性. 作为开发人员,您可能会发现现有实用程序并不总是能够解决问题.虽然能够通过结合使用现有实用程序来容易地解决许多问题,然而解决其他问题却至少需要一些实 际的编程工作.这些后面的任务通常是创建新实用程序的候选任务,结合现有实用程序来创建新实用程序可以通过做最少的工作来解决问题.本文考察优秀实用程序所具有的

  • linux c 获得当前进程的进程名和执行路径(示例)

    复制代码 代码如下: [sam@hzhsan test]$ more test_processname.cpp #include <limits.h>#include <stdio.h>#include <string.h>#include <unistd.h> size_t get_executable_path( char* processdir,char* processname, size_t len){        char* path_end;

随机推荐