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的队列。