send与recv函数详解

#include <sys/socket.h>

ssize_t recv(int sockfd, void *buff, size_t nbytes, int flags);

ssize_t send(int sockfd, const void *buff, size_t nbytes, int flags);

send和recv的前3个参数等同于read和write;

flags参数值为0或:

flags说明recvsend
MSG_DONTROUTE绕过路由表查找
MSG_DONTWAIT仅本操作非阻塞
MSG_OOB发送或接收带外数据
MSG_PEEK窥看外来消息
MSG_WAITALL等待所有数据

2. send解析

ssize_t send(int sockfd, const void *buff, size_t nbytes, int flags);

sockfd:指定发送端套接字描述符。

buff: 存放要发送数据的缓冲区

nbytes: 实际要改善的数据的字节数

flags: 一般设置为0

1) send先比较发送数据的长度nbytes和套接字sockfd的发送缓冲区的长度,如果nbytes > 套接字sockfd的发送缓冲区的长度, 该函数返回SOCKET_ERROR。

2) 如果nbtyes <= 套接字sockfd的发送缓冲区的长度,那么send先检查协议是否正在发送sockfd的发送缓冲区中的数据,如果是就等待协议把数据发送完,如果协议还没有开始发送sockfd的发送缓冲区中的数据或者sockfd的发送缓冲区中没有数据,那么send就比较sockfd的发送缓冲区的剩余空间和nbytes。

3) 如果 nbytes > 套接字sockfd的发送缓冲区剩余空间的长度,send就一起等待协议把套接字sockfd的发送缓冲区中的数据发送完。

4) 如果 nbytes < 套接字sockfd的发送缓冲区剩余空间大小,send就仅仅把buf中的数据copy到剩余空间里(注意:并不是send把套接字sockfd的发送缓冲区中的数据传到连接的另一端的,而是协议传送的。send仅仅是把buf中的数据copy到套接字sockfd的发送缓冲区的剩余空间里)。

5) 如果send函数copy成功,就返回实际copy的字节数,如果send在copy数据时出现错误,那么send就返回SOCKET_ERROR;如果在等待协议传送数据时网络断开,send函数也返回SOCKET_ERROR。

6) send函数把buff中的数据成功copy到sockfd的发送缓冲区的剩余空间后它就返回了,但是此时这些数据并不一定马上被传到连接的另一端。如果协议在后续的传送过程中出现网络错误的话,那么下一个socket函数就会返回SOCKET_ERROR(每一个除send的socket函数在执行的最开始总要先等待套接字的发送缓冲区中的数据被协议传递完毕才能继续,如果在等待时出现网络错误那么该socket函数就返回SOCKET_ERROR)。

7) 在unix系统下,如果send在等待协议传送数据时网络断开,调用send的进程会接收到一个SIGPIPE信号,进程对该信号的处理是进程终止。

3. recv解析

sockfd: 接收端套接字描述符

buff: 用来存放recv函数接收到的数据的缓冲区

nbytes: 指明buff的长度

flags: 一般置为0

1)recv先等待sockfd的发送缓冲区的数据被协议传送完毕,如果协议在传送sock的发送缓冲区中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR。

2)如果套接字sockfd的发送缓冲区中没有数据或者数据被协议成功发送完毕后,recv先检查套接字sockfd的接收缓冲区,如果sockfd的接收缓冲区中没有数据或者协议正在接收数据,那么recv就一起等待,直到把数据接收完毕。当协议把数据接收完毕,recv函数就把sockfd的接收缓冲区中的数据copy到buff中(注意:协议接收到的数据可能大于buff的长度,所以在这种情况下要调用几次recv函数才能把sockfd的接收缓冲区中的数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的)。

3)recv函数返回其实际copy的字节数,如果recv在copy时出错,那么它返回SOCKET_ERROR。如果recv函数在等待协议接收数据时网络中断了,那么它返回0。

4)在unix系统下,如果recv函数在等待协议接收数据时网络断开了,那么调用recv的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。


一.函数原型

recv函数用于socket通信中接收消息,接口定义如下:

ssize_t recv(int socket, void *buf, size_t len, int flags)

参数一:指定接收端套接字描述符;

参数二:指向一个缓冲区,该缓冲区用来存放recv函数接收到的数据;

参数三:指明buf的长度;

参数四:一般置为0;

返回值:失败时,返回值小于0;超时或对端主动关闭,返回值等于0;成功时,返回值是返回接收数据的长度。

send函数用于socket通信中发送消息,接口定义如下:

ssize_t send(int socket, const void *buf, size_t len, int flags);

参数一:指定发送端套接字描述符;

参数二:指明一个存放应用程序要发送数据的缓冲区;

参数三:指明实际要发送的数据的字节数;

参数四:一般置0;

返回值:失败时,返回值小于0;超时或对端主动关闭,返回值等于0;成功时,返回值是返回发送数据的长度。

二、TCP socket中的buffer

每个TCP socket在内核中都有一个发送缓冲区和一个接受缓冲区,TCP的全双工工作模式以及TCP的流量和拥塞控制便依赖于这两个独立的buffer以及buffer的填充状态。


接受缓冲区把数据缓存入内核,如果没有调用read()系统调用的话,数据会一直缓存在socket的接受缓冲区内。不管进程是否调用recv()读取socket,对等端发来的数据都会经由内核接受并且缓存到socket的内核接受缓冲区之中。recv()所做的工作,就是把内核缓冲区中的数据拷贝到应用层用户的buffer里面,并返回拷贝的字节数。(注意:是拷贝,不是像read那样读取之后,清空接受缓冲区内的数据。)

进程调用send()发送数据的时候,将数据拷贝到socket的内核发送缓冲区之中,然后返回拷贝的字节数。send()返回之时,数据不一定会发送到对等端去,send()仅仅是把应用层buffer的数据拷贝到socket的内核发送缓冲区中,发送是TCP的事情。(注意:这里也是拷贝,不是像write那样发送之后,清空发送缓冲区内的数据。)

接受缓冲区被TCP用来缓存网络上接收到的数据,一直保存到应用进程读走为止。如果应用进程一直没有读取,接受缓冲区满了以后,发生的动作是:接收端通知发送端,接收窗口关闭(win=0)。这个便是滑动窗口上的实现。保证TCP套接口接受缓冲区不会溢出,从而保证了TCP是可靠传输。因为对方不允许发出超过所通告窗口大小的数据。这就是TCP的流量控制,如果对方无视窗口大小而发出了超过窗口大小的数据,则接收方TCP将丢弃它。

三、send()的工作原理

send()函数只负责将数据提交给协议层。当调用该函数时,send()先比较待发送数据的长度和套接字的发送缓冲区的长度:

·当待拷贝数据的长度大于发送缓冲区的长度时,该函数返回SOCKET_ERROR;

·当待拷贝数据的长度小于或等于发送缓冲区的长度时,那么send先检查协议是否正在发送发送套接字的发送缓冲区中的数据:

如果是就等待协议把数据发送完,再进行拷贝;

如果协议还没有开始发送套接字的发送缓冲区中的数据或者该发送缓冲区中没有数据,那么send就比较该发送缓冲区中的剩余空间和待拷贝数据的长度:

如果待拷贝数据的长度大于剩余空间的大小,send就一直等待协议把该发送缓冲区中的数据发完;

如果待拷贝数据的长度小于剩余空间大小,send就仅仅把buf中的数据拷贝到剩余空间中。(注意:并不是send把该套接字的发送缓冲区中数据传到连接的另一端,而是协议传的,send仅仅是把数据拷贝到该发送缓冲区的剩余空间里面。)

如果send函数拷贝成功,就返回实际拷贝的字节数;如果拷贝的过程中出现错误,send就返回SOCKET_ERROR;如果send在等待协议传送数据时网络断开的话,那么send函数也返回SOCKET_ERROR。

要注意,send函数把buffer中的数据成功拷贝到套接字的发送缓冲区中的剩余空间里面后,它就返回了,但是此时这些数据并不一定马上被传到连接的另一端。如果协议在后续的传输过程中出现网络错误的话,那么下一个socket函数就会返回SOCKET_ERROR。(每一个除send外的socket函数在执行的最开始总要先等待套接字的发送缓冲区的数据被协议传送完毕才能继续,如果在等待时出现网络错误,那么该socket函数就返回SOCKET_ERROR。)

四、recv()的工作原理

recv先检查套接字的接收缓冲区,如果该接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,直到协议把数据接收完毕。当协议把数据接收完毕,recv函数就把套接字的接收缓冲区中的数据拷贝到用户层的buffer中,(注意:协议接收到的数据可能大于buffer的长度,所以在这种情况下,要调用几次recv函数才能把套接字接收缓冲区中的数据拷贝完。)recv函数仅仅是拷贝数据,真正的接收数据是协议来完成的。

recv函数返回其实际拷贝的字节数。如果recv在拷贝时出错,那么就返回SOCKET_ERROR;如果recv函数在等待协议接收数据时网络中断了,那么它返回0。对方优雅的关闭socket并不影响本地recv的正常接收数据,如果协议缓冲区内没有数据,recv返回0,指示对方关闭;如果协议缓冲区有数据,则返回对应数据(可能需要多次recv),在最后一次recv时,返回0,指示对方关闭。

发布于 2021-10-13 18:53