linux下读取socket相关的系统调用总结

recv

函数原型

cpp 复制代码
/* 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

参数介绍

  1. 第一个参数指定接收端套接字描述符;
  2. 第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
  3. 第三个参数指明buf的长度;
  4. 第四个参数一般置0或者以下组合:
  • MSG_DONTROUTE:不查找路由表,是send函数使用的标志.这个标志告诉IP协议.目的主机在本地网络上面,没有必要查找路由表.这个标志一般用网络诊断和路由程式里面
  • MSG_OOB:接受或发送带外数据,表示能够接收和发送带外的数据.
  • MSG_PEEK:查看数据,并不从系统缓冲区移走数据,是recv函数的使用标志,表示只是从系统缓冲区中读取内容,而不清除系统缓冲区的内容。这样下次读的时候,仍然是相同的内容。一般在有多个进程读写数据时能够使用这个标志
  • MSG_WAITALL :等待任何数据

返回值介绍

  1. 成功执行时,返回接收到的字节数。
  2. 对端已关闭,返回0,此时可关闭本地连接。
  3. 失败返回-1,errno被设为以下的某个值 ,注意errno是一个宏,声明如下:
cpp 复制代码
/* 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是一回事,只是不同系统的宏名称不同而已。

流程介绍

当应用程序调用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

函数原型

cpp 复制代码
/* 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

参数介绍

  1. 第一个参数指定发送端套接字描述符;
  2. 第二个参数指明一个存放应用程序要发送数据的缓冲区;
  3. 第三个参数指明实际要发送的数据的字节数;
  4. 第四个参数一般置0或者其它组合。

|---------------|-------------------------------------------------------------------------------------------------------------|
| 标志位 | 解释 |
| 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 | 此标志指定在正常数据流的接受中不会接收带外数据。某些协议将加急数据放在正常数据队列的头部,因此此标志不能与此类协议一起使用。 |
[flag参数介绍]

流程介绍

当调用该函数时,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

函数原型

cpp 复制代码
/* 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

函数原型

cpp 复制代码
/* 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

函数原型

cpp 复制代码
/* 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 <sys/uio.h>

struct iovec {

void *iov_base; /* starting address of buffer */

size_t iov_len; /* size of buffer */

}

控制数据数据结构

#include <sys/socket.h>

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[] */

}

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服务端用例

cpp 复制代码
#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客户端用例

cpp 复制代码
#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客户端用例

cpp 复制代码
#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

待补充

相关推荐
麻辣韭菜24 分钟前
网络基础 【HTTP】
网络·c++·http
Deryck_德瑞克2 小时前
Java网络通信—TCP
java·网络·tcp/ip
GodK7772 小时前
IP 数据包分包组包
服务器·网络·tcp/ip
梁诚斌2 小时前
VSOMEIP代码阅读整理(1) - 网卡状态监听
运维·服务器·网络
ZachOn1y3 小时前
计算机网络:计算机网络概述 —— 描述计算机网络的参数
网络·tcp/ip·计算机网络·考研必备
我命由我123453 小时前
SSL 协议(HTTPS 协议的关键)
网络·经验分享·笔记·学习·https·ssl·学习方法
两点王爷4 小时前
使用WebClient 快速发起请求(不使用WebClientUtils工具类)
java·网络
wusam4 小时前
螺蛳壳里做道场:老破机搭建的私人数据中心---Centos下Docker学习03(网络及IP规划)
运维·服务器·网络·docker·容器
什么鬼昵称6 小时前
Pikachu-xxe-xxe漏洞
网络·安全·xxe