Linux socket里的send和recv,阻塞与非阻塞socket、TCP与UDP的区别

1. send函数

#include <sys/types.h>
#include <sys/socket.h>

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

参数:sockfd是socket()的返回值,文件描述符;buf是待发送数据所在的数据区的指针;len是发送数据的长度;flags标志位,默认为0。

返回值:(阻塞与非阻塞没有区别)>0表示成功将数据复制到缓冲区中,返回的值表示发送的字节数;=0表示对方主动关闭了连接过程;<0表示出错,会返回SOCKET_ERROR。

send发送数据实际上是将数据(应用层buf中的数据)拷贝到套接字sockfd的缓冲区(内核中的sockfd对应的发送缓冲区)中,内核中的发送缓存中的数据由协议(TCP,UDP没有发送缓冲区)传输。send函数将buf中的数据成功拷贝到发送缓冲区后就返回了,如果协议后续发送数据到接收端出现网络错误的话,那么下一个socket函数就会返回SOCKET_ERROR。

send发送数据时,首先比较待发送的数据长度len和套接字sockfd的发送缓冲区的长度,

  (1)如果待发送数据的长度len大于sockfd发送缓冲区的长度,则返回SOCKET_ERROR;

  (2)如果待发送数据的长度len小于等于sockfd发送缓冲区的长度,则再检查协议是否正在发送sockfd的发送缓冲区的数据, 

      【1】如果正在发送,则等协议把发送缓冲区中的数据发送完毕后(这里应该有阻塞和非阻塞的区别,阻塞的话等待数据的发送完毕,非阻塞的话立即返回,并将errno置为EAGAIN),将待发送的数据拷贝到sockfd的发送缓冲区中;

      【2】如果没有在发送数据,或发送缓冲区中没有数据,则比较sockfd的发送缓冲区的剩余空间和待发送数据的长度len:

扫描二维码关注公众号,回复: 13164600 查看本文章

        A. 如果剩余空间大于待发送数据的长度len,则将buf里的数据拷贝到剩余空间里;

        B. 如果剩余空间小于待发送数据的长度len,则等待协议把sockfd的发送缓冲区中的数据发送腾出空间(收到接收方的确认)(阻塞与非阻塞的区别,见ps的1),再将待发送数据拷贝到发送缓冲区中。

PS:1. 如果剩余空间小于待发送数据的长度len,阻塞socket会等待协议将发送缓冲区中的数据发送(缓冲区应该要收到接收方的确认之后,才能腾出空间),再拷贝待发送的数据并返回;非阻塞socket会尽力拷贝(能拷多少拷多少),返回已拷贝字节的大小,如果缓冲区可用空间为0,则返回-1,并置errno为EAGAIN((不确定)。

2. TCP有发送缓冲区,数据发送是协议发送发送缓冲区的内容;UDP没有发送缓冲区,UDP有数据要发送时,直接发送到网络上,不会缓存。

3. 接收端的sockfd缓冲区(内核的缓冲区)收到数据包后,就会返回ACK,不会等待recv到用户空间再返回。

2. recv函数

#include <sys/types.h>

 #include <sys/socket.h>

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

参数:sockfd是socket()的返回值,文件描述符;buf是接受数据的缓存区的指针;len是发送数据的长度;flags标志位,默认为0。

返回值:(阻塞与非阻塞没有区别)>0表示成功,此时的值是接收到的数据大小;=0表示对方调用了close API来关闭连接;<0表示出错,<0且(errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN)的情况下认为连接是正常的,继续接收,此时,errno被设为下面的某个值:

  EAGAIN:套接字已标记为非阻塞,而接收操作被阻塞或者接收超时,连接正常,继续接收

  EBADF:sock不是有效的描述词

  ECONNREFUSE:远程主机阻绝网络连接

  EFAULT:内存空间访问出错

  EINTR:操作被信号中断,连接正常,继续接收

  EINVAL:参数无效

  ENOMEM:内存不足

  ENOTCONN:与面向连接关联的套接字尚未被连接上

  ENOTSOCK:sock索引的不是套接字

recv仅仅将接收到的数据(存储在内核的接受缓冲区)拷贝到buf(应用层用户的缓存区)中,真正的接受数据是由协议(TCP/UDP,UDP调用的函数名称不同)完成的。

recv先检查sockfd的发送缓冲区中有没有数据:

  (1)如果发送缓冲区中有数据,先等待sockfd的发送缓冲区的数据被协议发送完毕;如果协议在传送发送缓冲区的数据时出现网络错误,则返回SOCKET_ERROR;

  (2)如果sockfd发送缓冲区中的数据发送完毕或者发送缓冲区中没有数据,则检查sockfd的接收缓冲区,如果接受缓冲区中没有数据或者协议正在接收数据,那么recv一直等待(阻塞socket将等待,非阻塞socket直接返回-1,errno置为EWOULDBLOCK),直到协议将数据接受完毕;当协议把数据接收完毕,recv函数就把sockfd的接受缓冲区中的数据拷贝到buf中,然后返回拷贝的字节数。(注:协议接收到的数据的长度可能大于buf的长度,此时要调用几次recv函数才能把sockfd接受缓冲区中的数据拷贝完。)

 3. 阻塞与非阻塞的区别

发送时:在发送缓冲区的空间大于待发送数据的长度的条件下,阻塞socket一直等到有足够的空间放待发送的数据,将数据拷贝到发送缓冲区中才返回;非阻塞socket在没有足够空间时,会拷贝部分,并返回已拷贝的字节数,置errno为EWOULDBLOCK(不确定置为啥)。

接收时:如果sockfd发送缓冲区中有数据,或接受缓冲区中无数据,或协议正在接受数据,阻塞socket都将等待,直到有数据可以拷贝到用户程序中;非阻塞socket会返回-1,置errno为EWOULDBLOCK,表示“没有数据,回头来看”。

4. TCP与UDP的区别,TCO如何使用缓冲区实现流量控制

TCP有发送缓冲区和接收缓冲区;UDP只有接受缓冲区,UDP发送时不缓存,直接发送出去。对于接收缓冲区,TCP和UDP的recv操作相同,分为阻塞与非阻塞socket。

TCP的sockfd的接收缓冲区如果满了之后,接收端通知发送端,接收窗口关闭(win=0),保证了TCP套接口接收缓冲区不溢出,从而实现可靠传输;如果发送方无视窗口大小,仍然发送,则接收方TCP丢失收到的包。这就是TCP的流量控制(流量控制是点对点的)

UDP的接受缓冲区满了之后,对方并不知道,新来的数据报无法进入缓冲区,直接被丢弃,所以UDP没有流量控制,快的发送者可以将慢的接收方淹没,导致接收方丢弃数据包。

5. send和recv图示

猜你喜欢

转载自blog.csdn.net/u013318019/article/details/119713864