- /* Read N bytes into BUF from socket FD.
- Returns the number read or -1 for errors.
- This function is a cancellation point and therefore not marked with
- __THROW. */
- extern ssize_t recv (int __fd, void *__buf, size_t __n, int __flags);
sys/socket.h
- /* The error code set by various library functions. */
- extern int *__errno_location (void) __THROW __attribute_const__;
- # define errno (*__errno_location ())
errno返回的错误及含义如下所示:
注意:返回值<0并且(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的情况下认为连接是正常的,继续接收。EWOULDBLOCK与EAGAIN是一回事,只是不同系统的宏名称不同而已。
当应用程序调用recv函数时,recv先等待s的发送缓冲中的数据被协议传送完毕,如果协议在传送s的发送缓冲中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR,如果s的发送缓冲中没有数据或者数据被协议成功发送完毕后,recv先检查套接字s的接收缓冲区,如果s接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,只到协议把数据接收完毕。当协议把数据接收完毕,recv函数就把s的接收缓冲中的数据copy到buf中(注意协议接收到的数据可能大于buf的长度,所以在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的),recv函数返回其实际copy的字节数。如果recv在copy时出错,那么它返回SOCKET_ERROR;
如果recv函数在等待协议接收数据时网络中断了,那么它返回0。注意:在Unix系统下,如果recv函数在等待协议接收数据时网络断开了,那么调用recv的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。
tcp协议本身是可靠的,并不等于应用程序用tcp发送数据就一定是可靠的.不管是否阻塞,send发送的大小,并不代表对端recv到多少的数据.
在阻塞模式下, send函数的过程是将应用程序请求发送的数据拷贝到发送缓存中发送并得到确认后再返回.但由于发送缓存的存在,表现为:如果发送缓存大小比请求发送的大小要大,那么send函数立即返回,同时向网络中发送数据;否则,send向网络发送缓存中不能容纳的那部分数据,并等待对端确认后再返回(接收端只要将数据收到接收缓存中,就会确认,并不一定要等待应用程序调用recv);
在非阻塞模式下,send函数的过程仅仅是将数据拷贝到协议栈的缓存区而已,如果缓存区可用空间不够,则尽能力的拷贝,返回成功拷贝的大小;如缓存区可用空间为0,则返回-1,同时设置errno为EAGAIN.
- /* Send N bytes of BUF to socket FD. Returns the number sent or -1.
- This function is a cancellation point and therefore not marked with
- __THROW. */
- extern ssize_t send (int __fd, const void *__buf, size_t __n, int __flags);
sys/socket.h
| 标志位 | 解释 |
| MSG_CONFIRM | 只在SOCK_DGRAM和SOCK_RAW类型的socket有效,该标志位用于通知链路层收到了成功回复,以避免链路层定期发送ARP协议,减少流量。 |
| MSG_DONTROUTE | 不使用网关发送数据包,只能发送到直接连接的网络上的主机,通常用于网络诊断的路由系列协议。 |
| MSG_DONTWAIT | 设置本次调用为非阻塞,与O_NONBLOCK flag类似,但是该标志为作用域仅限于本次send调用,而后者则是对文件描述符操作。 |
| MSG_MORE | 此标志指定会有更多的数据发送,与 TCP_CORK 套接字设置效果相同,此时内核将保留这些数据,仅在下一次调用未指定该标志时进行传输。 |
| MSG_NOSIGNAL | 此标志指定调用send(),当对端的socket已经关闭时,不会产生SIGPIPE信号,但仍会返回EPIPE错误,作用范围为本次调用。这与sigaction忽略 SIGPIPE信号效果相同,但是后者会影响整个线程。 |
| MSG_OOB | 此标志指定在正常数据流的接受中不会接收带外数据。某些协议将加急数据放在正常数据队列的头部,因此此标志不能与此类协议一起使用。 |
当调用该函数时,send先比较待发送数据的长度len和套接字s的发送缓冲的长度, 如果len大于s的发送缓冲区的长度,该函数返回SOCKET_ERROR;如果len小于或者等于s的发送缓冲区的长度,那么send先检查协议是否正在发送s的发送缓冲中的数据,如果是就等待协议把数据发送完,如果协议还没有开始发送s的发送缓冲中的数据或者s的发送缓冲中没有数据,那么send就比较s的发送缓冲区的剩余空间和len,如果len大于剩余空间大小send就一直等待协议把s的发送缓冲中的数据发送完,如果len小于剩余空间大小send就仅仅把buf中的数据copy到剩余空间里(注意并不是send把s的发送缓冲中的数据传到连接的另一端的,而是协议的,send仅仅是把buf中的数据copy到s的发送缓冲区的剩余空间里)。
如果send函数copy数据成功,就返回实际copy的字节数,如果send在copy数据时出现错误,那么send就返回SOCKET_ERROR;如果send在等待协议传送数据时网络断开的话,那么send函数也返回SOCKET_ERROR。
要注意send函数把buf中的数据成功copy到s的发送缓冲的剩余空间里后它就返回了,但是此时这些数据并不一定马上被传到连接的另一端。如果协议在后续的传送过程中出现网络错误的话,那么下一个Socket函数就会返回SOCKET_ERROR。(每一个除send外的Socket函数在执行的最开始总要先等待套接字的发送缓冲中的数据被协议传送完毕才能继续,如果在等待时出现网络错误,那么该Socket函数就返回SOCKET_ERROR)。
注意:在Unix系统下,如果send在等待协议传送数据时网络断开的话,调用send的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。
- /* Read NBYTES into BUF from FD. Return the
- number read, -1 for errors or 0 for EOF.
- This function is a cancellation point and therefore not marked with
- __THROW. */
- extern ssize_t read (int __fd, void *__buf, size_t __nbytes) __wur;
unistd.h
read函数与recv函数功能类似,不同点是read函数相对于recv函数缺少了一个参数,至于这个参数的含义,可以参考recv的参数介绍章节。
与recv函数类似,不再赘述。
函数原型
- /* Write N bytes of BUF to FD. Return the number written, or -1.
- This function is a cancellation point and therefore not marked with
- __THROW. */
- extern ssize_t write (int __fd, const void *__buf, size_t __n) __wur;
unistd.h
write与send函数类似,不同的同样是缺少一个参数。对比他们的声明即可。若send函数最后一个参数为0,则他们是一致的。
- /* Send a message described MESSAGE on socket FD.
- Returns the number of bytes sent, or -1 for errors.
- This function is a cancellation point and therefore not marked with
- __THROW. */
- extern ssize_t sendmsg (int __fd, const struct msghdr *__message,
- int __flags);
sys/socket.h
第二个参数介绍
struct msghdr {
void *msg_name; /* 套接字地址 */
socklen_t msg_namelen; /* 地址长度 */
struct iovec *msg_iov; /* 散布/聚焦数组 */
size_t msg_iovlen; /* msg_iov 的元素个数 */
void *msg_control; /* 辅助数据 */
size_t msg_controllen; /* 辅助数据大小 */
int msg_flags; /* 接收数据的标志,仅用于 recvmsg */
}
数据缓冲数据结构:
#include
struct iovec {
void *iov_base; /* starting address of buffer */
size_t iov_len; /* size of buffer */
}
控制数据数据结构
#include
struct cmsghdr {
socklen_t cmsg_len; /* length in bytes, including this structure */
int cmsg_level; /* originating protocol */
int cmsg_type; /* protocol-specific type */
/* followed by unsigned char cmsg_data[] */
}
这两个成员用于套接字未连接的场合(如未连接 UDP 套接字)。它们类似 recvfrom 和 sendto 的第五个和第六个参数:
这两个成员指定输入或输出缓冲区数组(即iovec结构数组),类似 readv 或 writev 的第二个和第三个参数。
这两个成员指定可选的辅助数据的位置和大小。msg_controllen 对于 recvmsg 是一个值-结果参数。
对于 recvmsg 和 sendmsg,必须区别它们的两个标志变量:
只有 recvmsg 使用 msg_flags 成员。recvmsg 被调用时,flags 参数被复制到 msg_flags 成员,并由内核使用其值驱动接收处理过程。内核还依据 recvmsg 的结果更新 msg_flags 成员的值。
sendmsg 则忽略 msg_flags 成员,因为它直接使用 flags 参数驱动发送处理过程。这一点意味着如果想在某个 sendmsg 调用中设置 MSG_DONTWAIT 标志,那就把 flags 参数设置为该值,把 msg_flags 成员设置为该值不起作用。
recvmsg 返回的 7 个标志如下:
- #include "stdio.h"
- #include "stdlib.h"
- #include "string.h"
- #include "unistd.h"
- #include "sys/wait.h"
- #include "sys/select.h"
- #include "sys/poll.h"
- #include
- #include
- #include
- #include
- #include
-
- using namespace std;
-
- #define MAXSIZE 100
-
- int main(int argc, char ** argv)
- {
- int sockfd;
- struct sockaddr_in serv_socket;
- struct msghdr msg;
- struct iovec io[2];
- struct sockaddr_in * client_socket = (struct sockaddr_in *) malloc (sizeof(struct sockaddr_in));
- char buf[MAXSIZE + 1]={0};
- char ip[16];
- char buf1[MAXSIZE + 1]={0};
- //char ip1[16];
-
- sockfd = socket(AF_INET, SOCK_DGRAM, 0);//UDP
- bzero(&serv_socket, sizeof(serv_socket));
- serv_socket.sin_family = AF_INET; //IPV4
- serv_socket.sin_addr.s_addr = htonl(INADDR_ANY);//本地任意ip
- serv_socket.sin_port = htons(9888);
-
- bind(sockfd, (struct sockaddr *)&serv_socket, sizeof(serv_socket));
-
- msg.msg_name = client_socket;
- msg.msg_namelen = sizeof(struct sockaddr_in);
- io[0].iov_base = buf;
- io[0].iov_len = MAXSIZE;
- io[1].iov_base = buf1;
- io[1].iov_len = MAXSIZE;
- msg.msg_iov = io;
- msg.msg_iovlen = 2;
-
- ssize_t len = recvmsg(sockfd, &msg, 0);//server 在recvmsg阻塞等待客户端数据
- cout<<"recvmsg len= "<
- //解析数据
- client_socket = (struct sockaddr_in *)msg.msg_name;
- inet_ntop(AF_INET, &(client_socket->sin_addr), ip, sizeof(ip));
- int port = ntohs(client_socket->sin_port);
- char * temp = (char*)(msg.msg_iov[0].iov_base);
- //temp[len] = '\0';
- char * temp1 = (char*)(msg.msg_iov[1].iov_base);
- //temp1[len] = '\0';
- // cout<<"iov_base1 len = "<
printf("get message from %s[%d]: %s,%s,%d\n", ip, port,temp,temp+12, msg.msg_iov[0].iov_len);-
- close(sockfd);
-
- return 0;
- }
sendmsg客户端用例
- #include "stdio.h"
- #include "stdlib.h"
- #include "string.h"
- #include
- #include
- #include
- #include
- #include
-
- using namespace std;
- #define MAXSIZE 100
-
- int main(int argc, char ** argv)
- {
- int sockfd;
- struct sockaddr_in serv_socket;
- struct msghdr msg;
- struct iovec io[2];
- char send[] = "hello world";
- char send1[] = "nihaod dlsdjfsddjsjdfjsdjfjsjdfjsjddfjsjdjfsdjkfkdks";
- cout<<"bufsize = "<< strlen(send)+strlen(send1)<
-
- sockfd = socket(AF_INET, SOCK_DGRAM, 0);//UDP
- bzero(&serv_socket, sizeof(serv_socket));
- serv_socket.sin_family = AF_INET;//ipv4
- serv_socket.sin_port = htons(9888);
- inet_pton(AF_INET, "192.168.0.149", &serv_socket.sin_addr);//服务器IP
-
- msg.msg_name = &serv_socket;
- msg.msg_namelen = sizeof(struct sockaddr_in);
- io[0].iov_base = send;//"hello world"
- io[0].iov_len = sizeof(send);
- io[1].iov_base = send1;
- io[1].iov_len = sizeof(send1);
- msg.msg_iov = io;
- msg.msg_iovlen = 2;
- msg.msg_control = NULL;
- msg.msg_controllen = 0;
- msg.msg_flags = 0;
-
- ssize_t send_size = sendmsg(sockfd, &msg, 0);//发送数据到服务器
- cout<<"send_size = "<
" "<< sockfd << " errno = "< - close(sockfd);
-
- return 0;
- }
send客户端用例
- #include "stdio.h"
- #include "stdlib.h"
- #include "string.h"
- #include
- #include
- #include
- #include
- #include
-
- using namespace std;
- int main(int argc, char *argv[])
- {
- int sockfd;
- struct sockaddr_in serv_socket;
- char send1[] = "djsdjjsdfjsldjfjsdjfjsdjfjccjkjdsiejiocuaishfsaudfashdfhahsddifskdhvakjshdfhskdjfhkshdjfhkjasdhfhkjhakjhkfjhdfjhashdf";
-
- cout<<"send1 size = "<<sizeof(send1)<
- sockfd = socket(AF_INET, SOCK_DGRAM, 0);//UDP
- bzero(&serv_socket, sizeof(serv_socket));
- serv_socket.sin_family = AF_INET;//ipv4
- serv_socket.sin_port = htons(9888);
- inet_pton(AF_INET, "192.168.0.149", &serv_socket.sin_addr);//服务器IP
-
- connect(sockfd, (struct sockaddr*)&serv_socket, sizeof(serv_socket));
- ssize_t send_size = send(sockfd, send1, sizeof(send1),0);//发送数据到服务器
- cout<<"send_size = "<
" "<< sockfd << " errno = "< -
- close(sockfd);
-
- return 0;
- }
通过上面的三个用例进行测试得到如下结论:
- 发送方调用send接口发送一个缓冲区的数据,接收方调用recvmsg接口也可以接收,并不是recvmsg只能对应sendmsg接口。但是需要注意的是假设recvmsg接口设置了三个缓冲区接收,第一个缓冲区长度为40,第二个缓冲区长度为40,第三个缓冲区的长度为40,那么发送方调用send接口发送长度为100字节的数据,接收方通过调用recvmsg接收到数据后首先将第一个缓冲区的40字节缓冲区占满,第一个缓冲区被占满后,继续填写第二个缓冲区,第二个缓冲区也占满后,总共收到80个字节,所以剩余的20个字节会填写到第三个缓冲区,故第三个缓冲区剩余20个字节;如果发送方调用send接口进行传输,传输的数据长度小于接收方第一个缓冲区的长度,则只占用第一个缓冲区;如果send发送的数据长度,并且是采用udp协议无连接的方式发送超过接收方的三个缓冲区的长度,该会如何呢?
- 如果发送方调用sendmsg发送数据,假设发送方有两个缓冲区,第一个缓冲区的长度为12,第二个缓冲区的长度为54,而接收到调用recvmsg接口接收数据,同样设置两个缓冲区,假设两个缓冲区的长度都是100,那么这里思考是发送方的两个缓冲区的数据是否会填写到接收方对应的缓冲区呢?答案是否定的,接收规则是这样的,发送方发送的第一个缓冲区的12字节的数据收到存放到接收方第一个缓冲区的前12个字节,然后接收方第一个缓冲区的长度剩余88字节,发送方第二个缓冲区的长度为54个字节,则会继续在一个缓冲区的第13个字节开始填写发送发送第二个缓冲区的数据。
sendmsg的优势
可以一次性发送多个缓冲区数据,减少系统调用,提供发送效率,如果使用send接口发送数据,一次只能发送一个缓冲区的数据,而系统调用会触发用户态到内核态的切换,这个是耗时的,因为要从用户内存拷贝到对应的内核缓冲区,从这个角度看可以减少系统调用,从而减少用户态和内核态的切换,达到提供效率的目的。
readv/sendv
待补充
-
相关阅读:
前端面试题大汇总
RT-Thread 内核移植(学习)
mysql8其它新特性
考研数据结构大题整合_组一(ZYL组)
RedisObject
专用短程通讯(DSRC)技术介绍
【MPU6050_DMP】dmp初始化校准设置,取消上电零度
module.exports和exports,应该用哪个
提分必练!中创教育PMP全真模拟题分享来喽
uniapp通过url或base64打开pdf文件
-
原文地址:https://blog.csdn.net/iqanchao/article/details/133683615