先上这篇文章的目录。

目录

代码执行send成功后,数据就发出去了吗?

回答这个问题之前,需要了解什么是Socket 缓冲区

Socket 缓冲区

什么是 socket 缓冲区

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

socket 在操作系统层面,可以理解为一个文件

我们可以对这个文件进行一些方法操作

listen方法,可以让程序作为服务器监听其他客户端的连接。

connect,可以作为客户端连接服务器。

sendwrite可以发送数据,recvread可以接收数据。

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

socket_api

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

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

这个地方就是 socket 缓冲区

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

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

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

一个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[])
{// 创建socketsockfd=socket(AF_INET,SOCK_STREAM, 0))// 建立连接  connect(sockfd, 服务器ip信息, sizeof(server))  // 执行 send 发送消息send(sockfd,str,sizeof(str),0))  // 关闭 socketclose(sockfd);return 0;
}

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

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

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

tcp_sendmsg 逻辑

在用户进程中,程序通过操作 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 中, 核心工作就是将待发送的数据组织按照先后顺序放入到发送缓冲区中, 然后根据实际情况(比如拥塞窗口等)判断是否要发数据。如果不发送数据,那么此时直接返回。

如果缓冲区满了会怎么办

前面提到的情况里是,发送缓冲区有足够的空间,可以用于拷贝待发送数据。

如果发送缓冲区空间不足,或者满了,执行发送,会怎么样?

这里分两种情况。

首先,socket在创建的时候,是可以设置是阻塞的还是非阻塞的。

int s = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);

比如通过上面的代码,就可以将 socket 设置为非阻塞SOCK_NONBLOCK)。

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

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

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

send非阻塞

我们可以简单看下源码是怎么实现的。还是回到刚才的 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) {// 非阻塞模式时,会等到超时返回 EAGAINif (等待超时))return -EAGAIN;     // 阻塞等待时,会等到发送缓冲区有足够的空间了,才跳出if (sk_stream_memory_free(sk) && !vm_wait)break;}return err;
}

如果接收缓冲区为空,执行 recv 会怎么样?

接收缓冲区也是类似的情况。

当接收缓冲区为空,如果还向socket执行 recv

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

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

recv非阻塞

下面用一张图汇总一下,方便大家保存面试的时候用哈哈哈。

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

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

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

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

那么正常情况下,如果 socket 缓冲区为空,执行 close。就会触发四次挥手。

TCP四次挥手

这个也是面试老八股文内容了,这里我们只需要关注第一次挥手,发的是 FIN 就够了

如果接收缓冲区有数据时,执行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) {// 如果接收缓冲区的数据被清空了,发 RSTtcp_send_active_reset(sk, sk->sk_allocation);} else if (tcp_close_state(sk)) {// 正常四次挥手, 发 FINtcp_send_fin(sk);}// 等待关闭sk_stream_wait_close(sk, timeout);
}

recvbuf非空

如果发送缓冲区有数据时,执行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() 。而只有在进入了这个方法之后,我们才有可能考虑发送缓冲区是否为空的场景。

sendbuf非空

UDP部分

UDP也有缓冲区吗

说完TCP了,我们聊聊UDP。这对好基友,同时都是传输层里的重要协议。既然前面提到TCP有发送、接收缓冲区,那UDP有吗?

以前我以为:

"每个UDP socket都有一个接收缓冲区,没有发送缓冲区,从概念上来说就是只要有数据就发,不管对方是否可以正确接收,所以不缓冲,不需要发送缓冲区。"

后来我发现我错了。

UDP socket 也是 socket,一个socket 就是会有收和发两个缓冲区,跟用什么协议关系不大。

有没有是一回事,用不用又是一回事。

UDP不用发送缓冲区?

事实上,UDP不仅有发送缓冲区,也用发送缓冲区。

一般正常情况下,会把数据直接拷到发送缓冲区后直接发送。

还有一种情况,是在发送数据的时候,设置一个 MSG_MORE 的标记。

ssize_t send(int sock, const void *buf, size_t len, int flags); // flag 置为 MSG_MORE

大概的意思是告诉内核,待会还有其他更多消息要一起发,先别着急发出去。此时内核就会把这份数据先用发送缓冲区缓存起来,待会应用层说ok了,再一起发。

我们可以看下源码。

int udp_sendmsg()
{// corkreq 为 true 表示是 MSG_MORE 的方式,仅仅组织报文,不发送;int corkreq = up->corkflag || msg->msg_flags&MSG_MORE;//  将要发送的数据,按照MTU大小分割,每个片段一个skb;并且这些//  skb会放入到套接字的发送缓冲区中;该函数只是组织数据包,并不执行发送动作。err = ip_append_data(sk, fl4, getfrag, msg->msg_iov, ulen,sizeof(struct udphdr), &ipc, &rt,corkreq ? msg->msg_flags|MSG_MORE : msg->msg_flags);// 没有启用 MSG_MORE 特性,那么直接将发送队列中的数据发送给IP。 if (!corkreq)err = udp_push_pending_frames(sk);}

因此,不管是不是 MSG_MORE, IP都会先把数据放到发送队列中,然后根据实际情况再考虑是不是立刻发送。

而我们大部分情况下,都不会用  MSG_MORE,也就是来一个数据包就直接发一个数据包。从这个行为上来说,虽然UDP用上了发送缓冲区,但实际上并没有起到"缓冲"的作用。

最后

这篇文章,我也就写了20个小时吧。画图也就画吐了而已,每天早上7点钟爬起来写一个多小时再去上班。

欢迎点赞、在看、关注【小白debug】

动画图解 socket 缓冲区的那些事儿相关推荐

  1. socket缓冲区以及阻塞模式详解

    在<socket数据的接收和发送>一节中讲到,可以使用 write()/send() 函数发送数据,使用 read()/recv() 函数接收数据,本节就来看看数据是如何传递的. sock ...

  2. socket缓冲区以及阻塞模式

    socket缓冲区 每个 socket 被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区. write()/send() 并不立即向网络中传输数据,而是先将数据写入缓冲区中,再由TCP协议将数据从 ...

  3. 查看socket缓冲区数据_什么是socket缓冲区?

    Socket 就是发送和接收网络数据,Socket 有发送缓冲也有接收缓冲,这些缓冲区有什么作用? 1.什么是Socket缓冲区? 熟悉 Socket 的读者都知道,Socket 的发送和接收,就是调 ...

  4. Java Socket缓冲区

    Java网络编程–Socket编程(1)–Socket缓冲区探讨 socket收发缓冲区 socket套接字及缓冲区详解

  5. 汽车转向前后轮轨迹matlab程序,车前进后退方向的口诀,动画图解车前后轮转弯轨迹...

    我们在学习驾驶前,最好先掌握一些车辆行驶原理,比方说汽车转弯轨迹.前进和后退打方向的关系等,只有具备这些基础后,驾驶才得心应手,下面提供汽车前轮和后轮转向轨迹动画图解. 车前进后退方向的口诀 口诀一: ...

  6. 人体常用穴位按摩保健方法(动画图解)

    http://user.qzone.qq.com/592112686/blog/1265247247?ptlang=2052 [转] 人体常用穴位按摩保健方法(动画图解)   人体常用穴位按摩保健方法 ...

  7. 动画图解一般看不见的机械原理

    动画图解一般看不见的机械原理 动画图解一般看不见的机械原理--更新多款发动机,手枪--现代生活离不开各种机械,无数复杂的机械走进了我们寻常百姓的生活中,小到我们家里客厅墙上的挂钟,大到出门上班用以代步 ...

  8. socket缓冲区/sk_buffer/滑动窗口关系

    之前看极客时间对内核缓冲区,socket缓冲区.sk_buffer,着实有些乱,整理了一下午,把这些概念理清,真是顺畅,通透啊! 首先,还是感谢极客时间大佬.关于网络协议方面的讲解,还有一些优秀博客( ...

  9. 动画图解:Excel字符格式设置

    动画图解:Excel字符格式设置 1.设置字体:选中需要设置字体的单元格(区域),鼠标按"格式"工具栏的"字体"框右侧的下拉按钮,在随后弹出的下拉列表中,选择需 ...

最新文章

  1. ASP.NET2.0轻松搞定统计图表【月儿原创】
  2. 各大知名企业的Research展示
  3. 真人拳皇项目第九次Scrum总结——史经浩
  4. 【控制】《复杂运动体系统的分布式协同控制与优化》-方浩老师-第3章-局部指数稳定的多欧拉-拉格朗日系统协同控制
  5. python创建空文本文件_Python干货:「文件处理整合」 创建、打开、写入和删除...
  6. easyUi创建临时Dialog
  7. oracle unpivot 索引_oracle 11g 行列转换之unpivot、pivot
  8. jenkins maven没有使用全局设置文件地址_Jenkins手把手图文教程「基于Jenkins 2.164.1」...
  9. CSE:阿里在线应用如何演进成Serverless架构
  10. java特定用户登录_求教!!!用 jsp+servlet 怎样控制指定用户名登录页面啊!
  11. FastAdmin composer json 版本说明
  12. 三大技术要素为互联网金融2.0保驾护航
  13. 利用MATLAB实现人脸识别GUI程序设计
  14. 关于ACM竞赛的题型分析
  15. 有道单词本修改背景颜色
  16. 5.8Gwifi信道
  17. 《大连金州没有眼泪》
  18. 【python】 实现排列组合公式
  19. 【VS问题已解决】警告被视为错误 - 没有生成“object”文件
  20. 星巴克创始人第三次重出江湖

热门文章

  1. 结合webpack配置_前端 Webpack 工程化的最佳实践
  2. FreeSql与SqlSugar性能测试对比
  3. 你知道我们平时在CSS中写的%都是相对于谁吗?
  4. 《构建之法》阅读第四章、第十七章收获
  5. Powercli的一些使用案例
  6. sed 学习笔记(未完成)
  7. 用css3制作旋转加载动画的几种方法
  8. Openfiler的安装和配置
  9. 汽车发动机参数指标含义
  10. 如何实现文章中文字的打字效果