recv
函数原型
/* 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
参数介绍
- 第一个参数指定接收端套接字描述符;
- 第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
- 第三个参数指明buf的长度;
- 第四个参数一般置0或者以下组合:
- MSG_DONTROUTE:不查找路由表,是send函数使用的标志.这个标志告诉IP协议.目的主机在本地网络上面,没有必要查找路由表.这个标志一般用网络诊断和路由程式里面
- MSG_OOB:接受或发送带外数据,表示能够接收和发送带外的数据.
- MSG_PEEK:查看数据,并不从系统缓冲区移走数据,是recv函数的使用标志,表示只是从系统缓冲区中读取内容,而不清除系统缓冲区的内容。这样下次读的时候,仍然是相同的内容。一般在有多个进程读写数据时能够使用这个标志
- MSG_WAITALL :等待任何数据
返回值介绍
- 成功执行时,返回接收到的字节数。
- 对端已关闭,返回0,此时可关闭本地连接。
- 失败返回-1,errno被设为以下的某个值 ,注意errno是一个宏,声明如下:
/* The error code set by various library functions. */
extern int *__errno_location (void) __THROW __attribute_const__;
# define errno (*__errno_location ())
errno返回的错误及含义如下所示:
- EAGAIN:套接字已标记为非阻塞,而接收操作被阻塞或者接收超时 。从字面上来看,是提示再试一次。这个错误经常出现在当应用程序进行一些非阻塞(non-blocking)操作(对文件或socket)的时候。例如,以O_NONBLOCK的标志打开file/socket/FIFO,如果你连续做read操作而没有数据可读。此时程序不会阻塞起来等待数据准备就绪返回,read或者recv函数会返回一个错误EAGAIN,提示你的应用程序现在没有数据可读请稍后再试。在VxWorks和Windows上,EAGAIN的名字叫做EWOULDBLOCK。该错误码使我们经常使用的错误码。
- EBADF:sock不是有效的描述词
- ECONNREFUSE:远程主机阻绝网络连接
- EFAULT:内存空间访问出错
- EINTR:操作被信号中断,当出现该信号时并不是表示有问题,一般情况下我们会选择重新做报错的操作,例如read或者recv,该错误码是我们经常使用到的一个错误码。
- EINVAL:参数无效
- ENOMEM:内存不足
- ENOTCONN:与面向连接关联的套接字尚未被连接上
- ENOTSOCK:sock索引的不是套接字 当返回值是0时,为正常关闭连接;
注意:返回值<0并且(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的情况下认为连接是正常的,继续接收。EWOULDBLOCK与EAGAIN是一回事,只是不同系统的宏名称不同而已。
流程介绍
send
函数原型
/* 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
参数介绍
- 第一个参数指定发送端套接字描述符;
- 第二个参数指明一个存放应用程序要发送数据的缓冲区;
- 第三个参数指明实际要发送的数据的字节数;
- 第四个参数一般置0或者其它组合。
流程介绍
read
函数原型
/* 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
函数原型
/* 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,则他们是一致的。
sendmsg/recvmsg
函数原型
/* 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
参数介绍
第二个参数介绍
数据缓冲数据结构:
控制数据数据结构
msg_name 和 msg_namelen
这两个成员用于套接字未连接的场合(如未连接 UDP 套接字)。它们类似 recvfrom 和 sendto 的第五个和第六个参数:
- msg_name 指向一个套接字地址结构,调用者在其中存放接收者(对于 sendmsg 调用)或发送者(对于recvmsg调用)的协议地址。如果无需指明协议地址(如对于 TCP 套接字或已连接 UDP 套接字),msg_name 应置为空指针。
- msg_namelen 对于 sendmsg 是一个值参数,对于 recvmsg 却是一个值-结果参数。
msg_iov 和 msg_iovlen
这两个成员指定输入或输出缓冲区数组(即iovec结构数组),类似 readv 或 writev 的第二个和第三个参数。
msg_control 和 msg_controllen
这两个成员指定可选的辅助数据的位置和大小。msg_controllen 对于 recvmsg 是一个值-结果参数。
flags
对于 recvmsg 和 sendmsg,必须区别它们的两个标志变量:
- 一个是传递值的 flags 参数;
- 另一个是所传递 msghdr 结构的 msg_flags 成员,它传递的是引用,因为传递给函数的是该结构的地址。
只有 recvmsg 使用 msg_flags 成员。recvmsg 被调用时,flags 参数被复制到 msg_flags 成员,并由内核使用其值驱动接收处理过程。内核还依据 recvmsg 的结果更新 msg_flags 成员的值。
sendmsg 则忽略 msg_flags 成员,因为它直接使用 flags 参数驱动发送处理过程。这一点意味着如果想在某个 sendmsg 调用中设置 MSG_DONTWAIT 标志,那就把 flags 参数设置为该值,把 msg_flags 成员设置为该值不起作用。
recvmsg 返回的 7 个标志如下:
- MSG_BCAST:本标志随 BSD/OS 引入,相对较新。它的返回条件是本数据包作为链路层广播收取或者其目的 IP 地址是一个广播地址。与 IP_RECVD-STADDR 套接字选项相比,本标志是用于判定一个 UPD 数据包是否发往某个广播地址的更好方法。
- MSG_MCAST:本标志随 BSD/OS 引入,相对较新。它的返回条件是本数据报作为链路层多播收取。
- MSG_TRUNC:本标志的返回条件是本数据报被截断,也就是说,内核预备返回的数据超过进程事先分配的空间(所有 iov_len 成员之和)。
- MSG_CTRUNC:本标志的返回条件是本数据报的辅助数据被截断,也就是说,内核预备返回的辅助数据超过进程事先分配的空间(msg_controllen)。
- MSG_EOR:本标志的返回条件是返回数据结束一个逻辑记录。TCP 不使用本标志,因为它是一个字节流协议。
- MSG_OOB:本标志绝不为 TCP 带外数据返回。它用于其他协议族(如 OSI 协议族)。
- MSG_NOTIFICATION:本标志由 SCTP 接收者返回,指示读入的消息是一个事先通知,而不是数据消息。
rcvmsg服务端用例
#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 <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
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= "<<len<<endl;
//解析数据
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 = "<<msg.msg_iov[0].iov_len<<" base2 = "<<temp1[0]<<endl;
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 <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
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)<<endl;
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 = "<<send_size<<" "<< sockfd << " errno = "<<errno<<endl;
close(sockfd);
return 0;
}
send客户端用例
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
int sockfd;
struct sockaddr_in serv_socket;
char send1[] = "djsdjjsdfjsldjfjsdjfjsdjfjccjkjdsiejiocuaishfsaudfashdfhahsddifskdhvakjshdfhskdjfhkshdjfhkjasdhfhkjhakjhkfjhdfjhashdf";
cout<<"send1 size = "<<sizeof(send1)<<endl;
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 = "<<send_size<<" "<< sockfd << " errno = "<<errno<<endl;
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
待补充