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

1. Linux的send&recv函数

1.1 send函数和recv函数

cpp 复制代码
#include <sys/socket.h>
ssize_t send(
int sockfd,              //套接字文件描述符
const void* buf,         //保存待传输数据的缓冲地址值
size_t nbytes,           //待传输的字节数
int flags                //传输数据时指定的可选项信息
);
成功返回发送的字节数,失败返回-1
cpp 复制代码
#include <sys/socket.h>
ssize_t recv(
int sockfd,              //套接字文件描述符
const void* buf,         //保存接收数据的缓冲地址值
size_t nbytes,           //接收的字节数
int flags                //接收数据时指定的可选项信息
);
成功返回发送的字节数,失败返回-1

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

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

|---------------|----------------------------|------|------|
| 可选项 | 含义 | send | recv |
| 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:

cpp 复制代码
send(sockfd,"123",strlen("123"),MSG_OOB);

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

接收端使用MSG_OOB:

cpp 复制代码
void Message_Handle(int signo)
{
    ......
    int recv_len=recv(clientfd,buff,sizeof(buff),MSG_OOB);
    ......
}

int main()
{
    sigaction act;
    act.sa_handler=Message_Handle;
    sigemptyset(&act.sa_mask);
    act.sa_flags=0;
    ......
    fcntl(clientfd,F_SETOWN,getpid());    //(1)
    int state=sigaction(SIGURG,&act,0);   //(2)
    ......
}

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

为什么要执行fcntl函数?

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

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

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

接收结果:"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函数时,会再一次读取,并在此次才会删除。所以两者合用,用以调用以非阻塞形式方式验证待读数据存在与否

cpp 复制代码
发送端:
write(sockfd,"123",strlen("123"));

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

2. readv&writev函数

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

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

cpp 复制代码
writev(1,ptr,2);

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

如代码:

cpp 复制代码
发送端:
int main()
{
    char buf1[]="ABCDEFG";
    char buf2[]="1234567";
    iovec vec[2];
    vec[0].iov_base=buf1;
    vec[0].iov_len=3;
    vec[1].iov_base=buf2;
    vec[1].iov_len=4;
    writev(1,vec,2);
}
控制台输出为:ABC1234

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

3. Windows的不同

3.1 MSG_OOB的读取与Linux不同

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

cpp 复制代码
发送端:
int main()
{
    ......
    send(hSocket,"4",1,MSG_OOB);
    ......
}
接收端:
int main()
{
    ......
    int result=select(0,&readCopy,0,&exceptCopy,&timeout);
    if(result>0)
    {
        if(FD_ISSET(hRecvSock, &exceptCopy)
        {
            stelen=recv(hRecvSock,buf,sizeof(buf),MSG_OOB);
            ......
        }
        ......
    }
    ......
}

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

相关推荐
怀澈12244 分钟前
高性能服务器模型之Reactor(单线程版本)
linux·服务器·网络·c++
chnming19871 小时前
STL关联式容器之set
开发语言·c++
威桑1 小时前
MinGW 与 MSVC 的区别与联系及相关特性分析
c++·mingw·msvc
熬夜学编程的小王1 小时前
【C++篇】深度解析 C++ List 容器:底层设计与实现揭秘
开发语言·数据结构·c++·stl·list
yigan_Eins1 小时前
【数论】莫比乌斯函数及其反演
c++·经验分享·算法
Mr.132 小时前
什么是 C++ 中的初始化列表?它的作用是什么?初始化列表和在构造函数体内赋值有什么区别?
开发语言·c++
阿史大杯茶2 小时前
AtCoder Beginner Contest 381(ABCDEF 题)视频讲解
数据结构·c++·算法
C++忠实粉丝2 小时前
计算机网络socket编程(3)_UDP网络编程实现简单聊天室
linux·网络·c++·网络协议·计算机网络·udp
我们的五年2 小时前
【Linux课程学习】:进程描述---PCB(Process Control Block)
linux·运维·c++
程序猿阿伟2 小时前
《C++ 实现区块链:区块时间戳的存储与验证机制解析》
开发语言·c++·区块链