• [C++ 网络协议] 多种I/O函数


    1. Linux的send&recv函数

    1.1 send函数和recv函数

    1. #include
    2. ssize_t send(
    3. int sockfd, //套接字文件描述符
    4. const void* buf, //保存待传输数据的缓冲地址值
    5. size_t nbytes, //待传输的字节数
    6. int flags //传输数据时指定的可选项信息
    7. );
    8. 成功返回发送的字节数,失败返回-1
    1. #include
    2. ssize_t recv(
    3. int sockfd, //套接字文件描述符
    4. const void* buf, //保存接收数据的缓冲地址值
    5. size_t nbytes, //接收的字节数
    6. int flags //接收数据时指定的可选项信息
    7. );
    8. 成功返回发送的字节数,失败返回-1

    Linux的send和recv函数与windows相比,实际差别不大。

    1.2 flags参数(传输时指定的可选项信息)

    可选项含义sendrecv
    MSG_OOB用于传输带外数据(out-of-band data)
    MSG_PEEK验证输入缓冲中是否存在接收的数据
    MSG_DONTROUTE数据传输过程中不参照路由表,在本地网络中寻找目的地
    MSG_DONTWAIT调用I/O函数时不阻塞,用于非阻塞I/O
    MSG_WAITALL防止程序返回,直到接收全部请求的字节数

    可以用过"|"运算,同时设置。

    1.2.1 MSG_OOB(发送紧急消息)

    发送端使用MSG_OOB:

    send(sockfd,"123",strlen("123"),MSG_OOB);

    紧急消息的发送比较简单,直接指定MSG_OOB可选项即可。

    接收端使用MSG_OOB:

    1. void Message_Handle(int signo)
    2. {
    3. ......
    4. int recv_len=recv(clientfd,buff,sizeof(buff),MSG_OOB);
    5. ......
    6. }
    7. int main()
    8. {
    9. sigaction act;
    10. act.sa_handler=Message_Handle;
    11. sigemptyset(&act.sa_mask);
    12. act.sa_flags=0;
    13. ......
    14. fcntl(clientfd,F_SETOWN,getpid()); //(1)
    15. int state=sigaction(SIGURG,&act,0); //(2)
    16. ......
    17. }

    fcntl函数的意义:将文件描述符clientfd指向的套接字引发的SIGURG信号处理进程,变为将getpid函数返回值用作ID的进程。

    为什么要执行fcntl函数?

    因为:如果通过了fork函数创建了子进程,此时文件描述符也会一并复制一份给子进程,这样的话,当SIGURG信号触发时,那应该用哪个进程来处理信号呢?所以这里要使用fcntl函数,来指定处理信号的进程是当前主进程。

    综上,接收端使用MSG_OOB必须要有以下步骤:

    1. 执行fcntl函数
    2. 注册SIGURG信号
    3. 在处理函数中接收紧急消息。
    1. 发送端顺序:"123","4","567","890";
    2. 其中"4""890"是紧急消息
    3. 接收结果:"123","4","567","0","89";

    根据上述结果得知,MSG_OOB的作用,不像我们想象的那样,指定了紧急消息的信息会先到达。并且,我们发送的是一串紧急消息,但最终只读取了一个字节。

    实际上,MSG_OOB的作用是:督促数据接收对象尽快处理数据,而不是让信息传输最快。(就像病人送去急诊,MSG_OOB的作用不是让病人尽快到达医院,而是当病人到达医院后,通知医生,让医生来紧急治疗这个病人。这个医生就是程序员)

    这是因为,虽然OOB是需要通过单独的通信路径高速传输数据,但是TCP并不另外提供,TCP只是利用其紧急模式来进行传输。

    TCP紧急模式工作原理:

    当设置了MSG_OOB时,发送端的输出缓冲中的状态为如下形式:

    以这个数据缓冲的最左端为偏移量0,基于这个偏移量,这个数据缓冲的尾部之后的一个位置,如上图中的偏移量为3的位置,就存有紧急指针,紧急指针指向紧急消息的下一个位置。当传递时,会告知对方主机,在这个紧急指针的前面的部分都是紧急消息。实际上,只用一个字节来表示紧急消息的信息。即发送端接收到紧急消息只会接收到“0”,而余下的部分,将会由常用输入函数(read)读取。

    为什么收到的紧急消息不能直接读取0,90,890?

    答:因为这些并不重要,URG_OOB的作用只是督促消息处理,而非紧急传输形式受限的消息。

    1.2.2 MSG_PEEK和MSG_DONTWAIT

    MSG_PEEK和MSG_DONTWAIT一般是通过"|"同时设置的,用来验证输入缓冲中是否存在接收的数据。但注意,设置了MSG_PEEK选项的recv函数时,读取了输入缓冲里的数据后,并不会删除这个数据。在下一次执行没有设置MSG_PEEK的recv函数时,会再一次读取,并在此次才会删除。所以两者合用,用以调用以非阻塞形式方式验证待读数据存在与否

    1. 发送端:
    2. write(sockfd,"123",strlen("123"));
    3. 接收端:
    4. int rec_len=recv(sockfd,buff,sizeof(buff),MSG_PEEK|MSG_DONTWAIT);
    5. //这时如果rec_len>0那么就意味着有数据存在。
    6. //此时buff里存有数据123
    7. recv(sockfd,buff,sizeof(buff),0);
    8. //这时读取的buff里仍然是123,因为上一句使用了MSG_PEEK,没有将此数据从输入缓冲中删除。

    2. readv&writev函数

    1. #include
    2. ssize_t writev(
    3. int filedes, //表示数据传输对象的套接字文件描述符,
    4. //不仅仅可以是套接字文件描述符,
    5. //也可以传递文件或标准输出描述符
    6. const struct iovec* iov, //iovec结构体数组的地址值,结构体中包含待发送数据的位置和大小
    7. int iovcnt //向第二个参数传递的数组长度
    8. );
    1. #include
    2. ssize_t readv(
    3. int filedes, //表示数据接收对象的套接字文件描述符,
    4. //不仅仅可以是套接字文件描述符,
    5. //也可以传递文件或标准输出描述符
    6. const struct iovec* iov, //iovec结构体数组的地址值,结构体中包含数据保存的位置和大小
    7. int iovcnt //向第二个参数传递的数组长度
    8. );
    1. struct iovec
    2. {
    3. void* iov_base; //缓冲地址
    4. size_t iov_len; //缓冲大小
    5. }

    readv和writev函数的主要作用是:提高数据通信效率,适当的使用,可以减少I/O函数的调用次数提高性能。

    writev(1,ptr,2);

    第一个参数1,表示是向控制台输出数据,第二个参数ptr,保存有待发送数据信息的iovec数组指针,第三个参数2,表示从ptr指向的地址开始,读取2个iovec结构体变量,发送这些数据指向的缓冲数据。如图:

    如代码:

    1. 发送端:
    2. int main()
    3. {
    4. char buf1[]="ABCDEFG";
    5. char buf2[]="1234567";
    6. iovec vec[2];
    7. vec[0].iov_base=buf1;
    8. vec[0].iov_len=3;
    9. vec[1].iov_base=buf2;
    10. vec[1].iov_len=4;
    11. writev(1,vec,2);
    12. }
    13. 控制台输出为:ABC1234

    一次writev即可将两个缓冲buf里的数据分别按字节大小读取出来。

    3. Windows的不同

    3.1 MSG_OOB的读取与Linux不同

    在windows操作系统中,没有如Linux那般的信号处理机制。所以在使用MSG_OOB时,Windows得做特殊处理。即使用select函数在select函数中有个“发送异常的套接字”的参数,这里的异常指的是:不同寻常的程序执行流,所以收到的out-of-band数据也属于异常,所以利用这一特性,可以使用select函数来接收紧急消息。如代码:

    1. 发送端:
    2. int main()
    3. {
    4. ......
    5. send(hSocket,"4",1,MSG_OOB);
    6. ......
    7. }
    8. 接收端:
    9. int main()
    10. {
    11. ......
    12. int result=select(0,&readCopy,0,&exceptCopy,&timeout);
    13. if(result>0)
    14. {
    15. if(FD_ISSET(hRecvSock, &exceptCopy)
    16. {
    17. stelen=recv(hRecvSock,buf,sizeof(buf),MSG_OOB);
    18. ......
    19. }
    20. ......
    21. }
    22. ......
    23. }

     3.2 重叠I/O(实现Linux的writev和readv函数功能)

    可以看 [C++ 网络协议] 重叠I/O模型 章节里讲述的WSASend和WSARecv函数,里面的第二个和第三个参数,就具有Linux的writev和readv函数功能。

  • 相关阅读:
    【MATLAB源码-第86期】基于matlab的QC-LDPC码性能仿真,输出误码率曲线。
    crsctl stop crs -f 停止不成功 (SYSTEMDG in QUIESCING state)
    Kotlin okhttp3 HttpClient
    Python实践项目——LSB隐写术
    #微信小程序创建(获取onenet平台数据)
    一个快速切换浏览器搜索引擎的小技巧(在地址栏搜索B站、书签等的方法)
    “互动+消费”时代,借助华为云GaussDB重构新零售中消费逻辑
    使用Mybatis映射时间 DateTime ==> LocalDateTime
    IE浏览器设置兼容性、清除缓存,重置浏览器、Edge浏览器设置兼容性
    Java SimpleDateFormat linux时间字符串转时间轴的坑
  • 原文地址:https://blog.csdn.net/A_ns_wer_/article/details/132896649