Socket缓冲区

您所在的位置:网站首页 tcp发送缓冲区满了会怎样 Socket缓冲区

Socket缓冲区

2023-09-11 08:24| 来源: 网络整理| 查看: 265

一、什么是 socket 缓冲区

编程的时候,如果要跟某个IP建立连接,我们需要调用操作系统提供的 socket API。

socket 在操作系统层面,可以理解为一个文件。我们可以对这个文件进行一些方法操作。

用listen方法,可以让程序作为服务器监听其他客户端的连接。用connect,可以作为客户端连接服务器。用send或write可以发送数据,recv或read可以接收数据。

在建立好连接之后,这个 socket 文件就像是远端机器的 "代理人" 一样。比如,如果我们想给远端服务发点什么东西,那就只需要对这个文件执行写操作就行了。

 

那写到了这个文件之后,剩下的发送工作自然就是由操作系统内核来完成了。

既然是写给操作系统,那操作系统就需要提供一个地方给用户写。同理,接收消息也是一样。这个地方就是 socket 缓冲区。

用户发送消息的时候写给 send buffer(发送缓冲区)用户接收消息的时候写给 recv buffer(接收缓冲区)

也就是说一个socket ,会带有两个缓冲区,一个用于发送,一个用于接收。因为这是个先进先出的结构,有时候也叫它们发送、接收队列。

 

二、怎么观察 socket 缓冲区

如果想要查看 socket 缓冲区,可以在linux环境下执行 netstat -nt 命令。

# netstat -nt Active Internet connections (w/o servers) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 60 172.22.66.69:22 122.14.220.252:59889 ESTABLISHED

这上面表明了,这里有一个协议(Proto)类型为 TCP 的连接,同时还有本地(Local Address)和远端(Foreign Address)的IP信息,状态(State)是已连接。

Send-Q 是发送缓冲区,下面的数字60是指,当前还有60 Byte在发送缓冲区中未发送。 Recv-Q 代表接收缓冲区, 此时是空的,数据都被应用进程接收干净了。 三、TCP部分

我们在使用TCP建立连接之后,一般会使用 send 发送数据。

int main(int argc, char *argv[]) { // 创建socket sockfd=socket(AF_INET,SOCK_STREAM, 0)) // 建立连接 connect(sockfd, 服务器ip信息, sizeof(server)) // 执行 send 发送消息 send(sockfd,str,sizeof(str),0)) // 关闭 socket close(sockfd); return 0; }

上面是一段伪代码,仅用于展示大概逻辑,我们在建立好连接后,一般会在代码中执行 send 方法。那么此时,消息就会被立刻发到对端机器吗?

执行 send 发送的字节,会立马发送吗?

答案是不确定!执行 send 之后,数据只是拷贝到了socket 缓冲区。至 什么时候会发数据,发多少数据,全听操作系统安排。

 在用户进程中,程序通过操作 socket 会从用户态进入内核态,而 send方法会将数据一路传到传输层。在识别到是 TCP协议后,会调用 tcp_sendmsg 方法。

// net/ipv4/tcp.c // 以下省略了大量逻辑 int tcp_sendmsg() { // 如果还有可以放数据的空间 if (skb_availroom(skb) > 0) { // 尝试拷贝待发送数据到发送缓冲区 err = skb_add_data_nocache(sk, skb, from, copy); } // 下面是尝试发送的逻辑代码,先省略 }

在 tcp_sendmsg 中, 核心工作 就是将待发送的数据组织按照先后顺序放入到发送缓冲区中, 然后根据实际情况(比如拥塞窗口等)判断是否要发数据。如果不发送数据,那么此时直接返回。

如果缓冲区满了,执行 send 会怎么办?

前面提到的情况里是,发送缓冲区有足够的空间,可以用于拷贝待发送数据。如果发送缓冲区空间不足,或者满了,执行发送,会怎么样?这里分两种情况。

// 1、socket在创建的时候,是可以设置是阻塞的还是非阻塞的。 // 2、比如通过下面的代码,就可以将 socket 设置为非阻塞 (SOCK_NONBLOCK)。 int s = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);

当发送缓冲区满了,如果还向socket执行send:

如果此时 socket 是阻塞的,那么程序会在那干等、死等,直到释放出新的缓存空间,就继续把数据拷进去,然后返回。

图片

 

如果此时 socket 是非阻塞的,程序就会立刻返回一个 EAGAIN 错误信息,意思是  Try again , 现在缓冲区满了,你也别等了,待会再试一次。

图片

 我们可以简单看下源码是怎么实现的。还是回到刚才的 tcp_sendmsg 发送方法中。

int tcp_sendmsg() { if (skb_availroom(skb) > 0) { // ..如果有足够缓冲区就执行balabla } else { // 如果发送缓冲区没空间了,那就等到有空间,至于等的方式,分阻塞和非阻塞 if ((err = sk_stream_wait_memory(sk, &timeo)) != 0) goto do_error; } }

上面提到的  sk_stream_wait_memory 会根据socket是否阻塞来决定是一直等等一会就返回。

int sk_stream_wait_memory(struct sock *sk, long *timeo_p) { while (1) { // 非阻塞模式时,会等到超时返回 EAGAIN if (等待超时)) return -EAGAIN; // 阻塞等待时,会等到发送缓冲区有足够的空间了,才跳出 if (sk_stream_memory_free(sk) && !vm_wait) break; } return err; } 如果接收缓冲区为空,执行 recv 会怎么样?

接收缓冲区也是类似的情况。当接收缓冲区为空,如果还向socket执行 recv

如果此时 socket 是阻塞的,那么程序会在那干等,直到接收缓冲区有数据,就会把数据从接收缓冲区拷贝到用户缓冲区,然后返回。

图片

 如果此时 socket 是非阻塞的,程序就会立刻返回一个 EAGAIN 错误信息。

 

如果socket缓冲区还有数据,执行close了,会怎么样?

首先我们要知道,一般正常情况下,发送缓冲区和接收缓冲区 都应该是空的。

如果发送、接收缓冲区长时间非空,说明有数据堆积,这往往是由于一些网络问题或用户应用层问题,导致数据没有正常处理。

1、正常情况下,如果 socket 缓冲区为空,执行 close。

就会触发四次挥手

 2、如果发送缓冲区有数据时,执行close了,会怎么样?

以前以为,这种情况下,内核会把发送缓冲区数据清空,然后四次挥手。但是发现源码并不是这样的。

void tcp_send_fin(struct sock *sk) { // 获得发送缓冲区的最后一块数据 struct sk_buff *skb, *tskb = tcp_write_queue_tail(sk); struct tcp_sock *tp = tcp_sk(sk); // 如果发送缓冲区还有数据 if (tskb && (tcp_send_head(sk) || sk_under_memory_pressure(sk))) { TCP_SKB_CB(tskb)->tcp_flags |= TCPHDR_FIN; // 把最后一块数据值为 FIN TCP_SKB_CB(tskb)->end_seq++; tp->write_seq++; } else { // 发送缓冲区没有数据,就造一个FIN包 } // 发送数据 __tcp_push_pending_frames(sk, tcp_current_mss(sk), TCP_NAGLE_OFF); }

此时,还有些数据没发出去,内核会把发送缓冲区最后一个数据块拿出来。然后置为 FIN。

socket 缓冲区是个先进先出的队列,这种情况是指内核会等待TCP层安静把发送缓冲区数据都发完,最后再执行 四次挥手的第一次挥手(FIN包)。

有一点需要注意的是,只有在接收缓冲区为空的前提下,我们才有可能走到 tcp_send_fin() 。而只有在进入了这个方法之后,我们才有可能考虑发送缓冲区是否为空的场景。

 

3、如果接收缓冲区有数据时,执行close了,会怎么样?

socket close 时,主要的逻辑在 tcp_close() 里实现。先说结论,关闭过程主要有两种情况:

如果接收缓冲区还有数据未读,会先把接收缓冲区的数据清空,然后给对端发一个RST。

如果接收缓冲区是空的,那么就调用 tcp_send_fin() 开始进行四次挥手过程的第一次挥手。

void tcp_close(struct sock *sk, long timeout) { // 如果接收缓冲区有数据,那么清空数据 while ((skb = __skb_dequeue(&sk->sk_receive_queue)) != NULL) { u32 len = TCP_SKB_CB(skb)->end_seq - TCP_SKB_CB(skb)->seq - tcp_hdr(skb)->fin; data_was_unread += len; __kfree_skb(skb); } if (data_was_unread) { // 如果接收缓冲区的数据被清空了,发 RST tcp_send_active_reset(sk, sk->sk_allocation); } else if (tcp_close_state(sk)) { // 正常四次挥手, 发 FIN tcp_send_fin(sk); } // 等待关闭 sk_stream_wait_close(sk, timeout); }

 

socket读写缓冲区满了的情况汇总

 



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3