socket IO读写原理

1.IO读写原理

https://blog.csdn.net/qq_31815507/article/details/115673210

用户程序进行IO读写,依赖于操作系统底层的IO读写,基本上会用到底层的read&write两大系统调用。read系统调用,并不是直接从物理设备把数据读取到内存中,write系统调用,也不是把数据直接写入到物理设备。

上层应用无论是调用操作系统的read,还是调用操作系统的write,都会涉及缓冲区。具体来说,调用操作系统的read,是把数据从内核缓冲区复制到进程缓冲区;而write系统调用,是把数据从进程缓冲区复制到内核缓冲区

 

 上图显示了块数据如何从外部源(例如硬盘)移动到正在运行的进程(例如RAM)内部的存储区的简化“逻辑”图。

  • 首先,该进程通过进行read()系统调用来填充其缓冲区。(首先是进程的读数据的I/O系统调用)
  • read读取调用会导致内核向磁盘控制器硬件发出命令以从磁盘获取数据。
  • 磁盘控制器通过DMA将数据直接写入内核内存缓冲区。(DMA,直接存储器存取(direct memory access))
  • 磁盘控制器完成缓冲区的填充后,内核将数据从内核空间中的临时缓冲区复制到进程指定的缓冲区中。之后内核会向应用程序发出信号、或者调用回调函数、或者是不阻塞了。

所以,用户程序的IO读写程序,在大多数情况下,并没有进行实际的IO操作,而是在进程缓冲区和内核缓冲区之间直接进行数据的交换。

底层操作会对内核缓冲区进行监控,等待缓冲区达到一定数量的时候,再进行IO设备的中断处理,集中执行物理设备的实际IO操作,这种机制提升了系统的性能。至于什么时候中断(读中断、写中断),由操作系统的内核来决定,用户程序则不需要关心。

2.send和recv函数只复制数据

https://blog.csdn.net/stpeace/article/details/43719449

 

 

 假设A(客户端)向B(服务端)发送数据:

  •  在应用程序Program A中, 我们定义一个数组char szBuf[100] = "tcp"; 那么这个szBuf就是应用程序缓冲区(对应上图的橙色Program A中的某块内存)。
  • send函数对应上面蓝色的Socket API, 内核缓冲区对应上面的黄色部分。 
  • send函数的作用是把应用程序缓冲区中的数据拷贝到内核缓冲区, 仅此而已,只要进入了应用缓冲区就进入了滑动窗口内。所以,从写一个TCP套接口的write调用成功返回仅仅表示我们可以重新使用应用进程缓冲区,它并不是告诉我们对方收到数据
  • 然后,内核缓冲区中的数据经过网卡, 经历网络传到B端的网卡(TCP协议), 然后进入B的内核缓冲区, 然后由recv函数剪切/复制到Program B的应用程序缓冲区。

数据流总结:

整个数据的流程中,首先网卡接收到的数据存放到内核缓冲区内,然后内核缓冲区存放的数据根据TCP信息将数据移动到具体的某一个TCP连接上的接收缓冲区内,也就是接收滑动窗口内,然后应用程序从TCP的接受缓冲区内读取数据,如果应用程序一直不读取,那么滑动窗口就会变小,直至为0.

 3.TCP socket接收和发送缓冲区

https://blog.csdn.net/ldw662523/article/details/79540507

https://www.cnblogs.com/edver/p/9347227.html

https://www.cnblogs.com/lsx1993/p/4841471.html

每个TCP Socket在内核中都有一个发送缓冲区和一个接收缓冲区, TCP的全双工工作模式以及TCP的滑动窗口就是依赖这两个独立的buffer以及buffer的填充状态

注意:  TCP的发送/接受缓冲区(也就是发送/接受滑动窗口),是针对某一个具体的TCP连接来说的,每一个TCP连接都会有相应的滑动窗口,但是内核的发送/接受缓冲区是针对整个系统的,里面存放着整个系统的所有TCP连接的接收/发送的数据

 

 

 问题分析:

当发送方发送包过快之后,接收方缓冲区满,此时TCP的窗口大小填写0返回告知发送方已经没有空间,发送缓冲区数据无法发出导致堆积满发送缓冲区,从而导致send无法将数据拷贝进发送缓冲区(内核发送缓冲区已满),进而形成send函数无法返回,程序阻塞无法运行。

为什么send无法返回?

  • send操作只是将应用缓冲区的数据拷贝到发送缓冲区。
  • 但是内核发送缓冲区的数据并没有完全得到接收端的ACK回应,所以暂时不能将内核发送缓冲区中的数据丢弃,导致发送缓冲区的被填满,这样应用层中的数据也就不能拷贝到内核发送缓冲区内,也就会一直阻塞在这里,直到可以继续将应用层的数据拷贝到发送缓冲区中。
  • 何时触发这个操作呢?等到发送端回应win大于0时才有这样的操作。【有疑问,难道不应该是收到了ACK之后吗?】

那么在接收端经历的过程:接收端处于慢启动状态,滑动窗口值越来越大,但是由于接收端不处理接收缓冲区内的数据【也就是说不返回ACK?】,其滑动窗口越来越小(因为接收端回应发送端中的win大小表示接受端还能够接受多少数据,发送端下次发送的数据大小不能超过回应中win的大小),最后发送端回应给接受端的ACK中显示的win大小为0,表示接收端不能够再接受数据。

//但是我有一点不明白,它能收到zero window这个tcp数据报,难道这个没有携带ACK?如果带了那不就说明接收方收到了?如果没带,那为什么不带呢?明明接收到了,只不过是还没处理啊。我认为是有个时差的问题,返回的数据包写了zero window但是同时也有ACK=1,seq=x+1,但是有很多后来的还没有发送ACK。

//可以简单地理解为:只要收到zero window的肯定就不能发送数据了,那么既然内核发送缓冲区不动,那么send迟早写满,导致阻塞。

4.数据结构

https://www.cnblogs.com/lsx1993/p/4841471.html

  • 应用程序可通过调用send(write, sendmsg等)利用tcp socket向网络发送应用数据;
  • 而tcp/ip协议栈再通过网络设备接口把已经组织成struct sk_buff的应用数据(tcp数据报)真正发送到网络上;
  • 由于应用程序调用send的速度跟网络介质发送数据的速度存在差异,所以,一部分应用数据被组织成tcp数据报之后,会缓存在tcp socket的发送缓存队列中,等待网络空闲时再发送出去。

tcp socket的发送缓冲区实际上是一个结构体struct sk_buff的队列。

posted @ 2021-05-24 18:34  lypbendlf  阅读(323)  评论(0编辑  收藏  举报