【Linux】TCP协议【下三】{面向字节流/粘包问题/TCP异常情况/文件和Socket}

时间:2024-07-07 09:14:49

文章目录

  • 7.面向字节流
    • TCP(传输控制协议)和UDP(用户数据报协议)
  • 8.粘包问题
  • 9.TCP异常情况
  • 10.再谈文件和socket的关系

7.面向字节流

创建一个TCP的socket, 同时在内核中创建一个 发送缓冲区 和一个 接收缓冲区;一个链接一对发收缓冲区。
调用write时, 数据会先写入发送缓冲区中;
如果发送的字节数太长, 会被拆分成多个TCP的数据包发出;
如果发送的字节数太短, 就会先在缓冲区里等待, 等到缓冲区长度差不多了, 或者其他合适的时机发送出
去;
接收数据的时候, 数据也是从网卡驱动程序到达内核的接收缓冲区;
然后应用程序可以调用read从接收缓冲区拿数据;
另一方面, TCP的一个连接, 既有发送缓冲区, 也有接收缓冲区, 那么对于这一个连接, 既可以读数据, 也可
以写数据. 这个概念叫做 全双工
由于缓冲区的存在, TCP程序的读和写不需要一一匹配, 例如:
写100个字节数据时, 可以调用一次write写100个字节, 也可以调用100次write, 每次写一个字节;
读100个字节数据时, 也完全不需要考虑写的时候是怎么写的, 既可以一次read 100个字节, 也可以一次
read一个字节, 重复100次

TCP(传输控制协议)和UDP(用户数据报协议)

是两种在计算机网络中广泛使用的传输层协议,它们在数据传输方式上有显著的区别。以下是TCP面向字节流和UDP面向数据报的具体区别:

  1. 面向字节流与面向数据报

TCP面向字节流:TCP将应用程序的数据视为无结构的字节流。这意味着TCP不关心数据是如何被组织成报文的,它只关注如何可靠地、按顺序地传输这些字节。TCP会在发送端将字节流拆分成多个(segment),并在接收端重新组合这些段以恢复原始的数据流。这种机制使得TCP能够处理任意大小的数据,同时也可能导致“粘包”问题,即多个数据块被合并成一个数据块传输。用户层认为我将4个http请求发给了对方,经过tcp层时tcp只认为只是一段二进制字节流。对方接收缓冲区收到数据也只有字节的概念,不关心是什么协议,是什么报文格式,取多少数据由用户层自定义,显然,用户层需要对接收到的数据进行控制以便能读到完整的报文而不是半个报文(例如之前编写过的网络版本计算器)。比如,发送想发送helloworld,可能hello先被接收方收到了,此时接收方用户层会识别到这不是一个完整的报文,他会继续读接收缓冲区直到把world也读了。

UDP面向数据报:UDP则不同,它将每个数据块视为一个独立的数据报(datagram)。每个数据报都包含完整的源地址和目的地址信息,以及一个可选的校验和。UDP不对数据报进行拆分或合并,而是直接将其发送到网络上。如果数据报太大而无法通过某个网络链路,那么IP层会对其进行分片(fragmentation),但这与UDP协议本身无关。在接收端,UDP会按照数据报的边界来接收数据,即每个read调用都会接收到一个完整的数据报(如果缓冲区足够大的话)。

  1. 可靠性

TCP可靠传输:TCP是一种可靠的传输协议,它通过确认应答、超时重传、流量控制等机制来确保数据的可靠传输。TCP会要求接收方对每个接收到的数据段进行确认,如果发送方在一定时间内没有收到确认,就会重新发送该数据段。此外,TCP还使用滑动窗口机制来进行流量控制,以防止发送方发送过多的数据导致接收方无法处理。
UDP不可靠传输:UDP则是一种不可靠的传输协议,它不提供任何形式的确认应答、超时重传或流量控制机制。UDP发送的数据报可能会丢失、乱序或重复到达接收方。因此,在使用UDP时,需要由应用层来负责数据的可靠性控制

  1. 连接管理

TCP面向连接:TCP是一种面向连接的协议,它在数据传输之前需要先建立连接。连接建立过程包括三次握手(SYN-SYNACK-ACK),以确保双方都准备好进行数据传输。在数据传输过程中,TCP会保持这个连接直到数据传输完成,并通过四次挥手(FIN-ACK-FIN-ACK)来关闭连接。
UDP无连接:UDP则是一种无连接的协议,它在发送数据之前不需要建立连接。UDP发送方只需要知道接收方的IP地址和端口号,就可以直接发送数据报。这种无连接的特性使得UDP在需要快速响应或广播通信的场景中非常有用。

  1. 性能与资源消耗

TCP性能与资源消耗:TCP由于需要建立连接、进行确认应答和流量控制等操作,因此相对于UDP来说会有更大的性能开销和资源消耗。但是,这种开销换来了更高的数据可靠性和有序性。
UDP性能与资源消耗:UDP则具有较低的性能开销和资源消耗,因为它不需要建立连接、进行确认应答和流量控制等操作。这使得UDP在需要高速传输或实时性要求较高的场景中更加适用。但是,UDP的可靠性和有序性较差,需要由应用层来负责数据的可靠性控制。
综上所述,TCP面向字节流和UDP面向数据报在数据传输方式、可靠性、连接管理和性能与资源消耗等方面都存在显著的区别。在选择使用哪种协议时,需要根据具体的应用场景和需求来进行权衡和选择。

8.粘包问题

AI问答

TCP(传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。TCP 粘包问题是 TCP 编程中常见的一个问题,它主要发生在 TCP 数据传输过程中,由于 TCP 本身是一个“流”协议,而不是一个“包”协议,TCP 不保证发送的数据包边界与接收的数据包边界一致,这就可能导致粘包和拆包问题。

  • 粘包问题简述

粘包 是指发送方发送的多个数据包在接收方被合并成一个数据包接收,或者发送方的一个数据包在接收方被拆分成多个数据包接收。粘包和拆包是 TCP 协议本身不保证消息边界所引起的问题,对于上层应用来说,这可能会导致数据解析错误

  • 粘包问题产生的原因
  1. TCP 缓冲区机制:TCP 传输数据时,会将数据放入缓冲区,然后按照 TCP 窗口大小进行发送。接收方也是将数据放入缓冲区,然后按照 TCP 窗口大小进行接收。如果发送方连续发送数据,且发送的数据小于 TCP 缓冲区的大小,这些数据包就有可能被 TCP 合并为一个大的数据包发送,接收方接收时就可能面临粘包问题。
  2. 网络延迟:在网络条件不佳的情况下,TCP 可能会将多个小的数据包合并成一个大的数据包发送,以减少网络传输的开销,这也可能引发粘包问题。
  3. TCP 的 Nagle 算法:为了减少网络中的小数据包的数量,TCP 使用了一种名为 Nagle 的算法。该算法会延迟小数据包的发送,直到有更大的数据包需要发送或者接收方对前一个数据包的确认到达。这种延迟机制也可能导致粘包。
  • 解决方案【明确边界】
  1. 【定长报文】固定长度消息:发送方和接收方约定每个消息的长度,接收方按照约定的长度来读取数据。
  2. 【使用特殊字符】特殊分隔符:在消息之间添加特定的分隔符(如换行符、回车符等),接收方通过识别分隔符来分割消息。
  3. 【自描述字段+定长报头/特殊字符】消息头部携带长度信息:在每条消息的头部添加表示消息长度的字段,接收方先读取头部信息,然后根据长度信息来读取整个消息。
  4. 应用层协议:设计一套应用层协议,该协议明确规定了消息的格式和解析方式,从而避免粘包和拆包问题。应用层要想获取一个完整的报文,就要对字节流根据某种协议分成一个一个报文。之前的网络版计算器encode和decode就是在解决这个问题!
    通过这些方法,可以在应用层解决 TCP 粘包问题,确保数据的正确传输和解析。成功获取到完整的报文之后,采用序列反序列思想解析数据。【再回头看udp/tcp报头的设计,在底层也是考虑到了这个情况==》读到一个报文,怎么获取报文的数据】

粘包问题中的 “包” , 是指的应用层的数据包。在TCP的协议头中, 没有如同UDP一样的 “报文长度” 这样的字段, 但是有一个序号这样的字段。站在传输层的角度, TCP是一个一个段过来的. 按照序号排好序放在缓冲区中.
站在应用层的角度, 看到的只是一串连续的字节数据.
那么应用程序看到了这么一连串的字节数据, 就不知道从哪个部分开始到哪个部分, 是一个完整的应用层数据包.应用层取数据时可能取到了1.5个包,这样本次分析数据有影响,之后的也有影响。

那么如何避免粘包问题呢? 归根结底就是一句话, 明确两个包之间的边界.

对于定长的包, 保证每次都按固定大小读取即可; 例如Request结构, 是固定大小的, 那么就从缓冲区从头开始按sizeof(Request)依次读取即可;
对于变长的包, 可以在包头的位置, 约定一个包总长度的字段, 从而就知道了包的结束位置;
对于变长的包, 还可以在包和包之间使用明确的分隔符(应用层协议, 是程序猿自己来定的, 只要保证分隔
符不和正文冲突即可);

思考: 对于UDP协议来说, 是否也存在 “粘包问题” 呢?

对于UDP, 如果还没有上层交付数据, UDP的报文长度仍然在. 同时, UDP是一个一个把数据交付给应用层. 就有很明确的数据边界.
站在应用层的角度, 使用UDP的时候, 要么收到完整的UDP报文, 要么不收. 不会出现"半个"的情况.

9.TCP异常情况

  1. 进程终止: 在OS看来,一个异常进程终止和进程正常退出没有区别,都是把pcb等资源释放。tcp连接和文件是直接相关的,文件的生命周期随进程,tcp连接和进程直接相关!进程终止会释放文件描述符, 仍然可以发送FIN。 和正常关闭没有什么区别。进行正常的四次挥手。【三次链接由用户connect/accept,其余都是OS完成的】
  2. 机器重启: 先进行进程终止。先杀掉所有进程再关机。
  3. 机器掉电/网线断开: 接收端认为连接还在, 一旦接收端有写入操作, 接收端发现连接已经不在了, 就会进行reset. 即使没有写入操作, TCP自己也内置了一个保活定时器, 会定期询问对方是否还在. 如果对方不在, 也会把连接释放.另外, 应用层的某些协议, 也有一些这样的检测机制. 例如HTTP长连接中, 也会定期检测对方的状态. 例如QQ, 在QQ断线之后, 也会定期尝试重新连接.

10.再谈文件和socket的关系

在Linux系统中,TCP套接字(TCP socket)和文件/文件描述符(file descriptors)之间存在紧密的关系。这种关系主要源于Linux内核对资源的统一管理方式,其中文件描述符作为访问所有类型I/O资源的通用接口。

文件描述符

文件描述符是一个非负整数,用于标识打开的文件或其他输入/输出资源(如管道、套接字等)。在Linux中,几乎所有的I/O操作都是通过文件描述符来完成的。文件描述符由内核分配,并在打开文件、套接字或其他I/O资源时返回给用户程序。

TCP套接字

TCP套接字是网络通信中的一个端点,用于在两台机器之间实现双向数据流。在Linux中,TCP套接字是通过socket系统调用创建的,这个调用会返回一个文件描述符,该描述符随后被用于对套接字进行读写操作,以及执行其他套接字相关的操作(如监听、连接、绑定等)。

关系

  1. 统一接口:Linux内核通过文件描述符为TCP套接字提供了统一的I/O接口。对套接字的读写操作可以使用与文件操作相同的系统调用(如read、write、select、poll等)。
  2. 资源管理:文件描述符由内核管理,内核通过文件描述符表来跟踪打开的文件和套接字等资源。当进程通过文件描述符对资源进行操作时,内核会查找文件描述符表来确定实际要操作的对象。
  3. 网络I/O:对于TCP套接字而言,文件描述符是网络通信的入口点。通过这个入口点,进程可以发送和接收数据,进行连接管理等操作。
  4. 限制:由于文件描述符是由内核管理的资源,因此它们的数量是有限的。这个限制可以通过/proc/sys/fs/file-max(系统级限制)和ulimit -n(用户级限制)来查看和修改。对于需要大量文件描述符的应用程序(如数据库服务器、Web服务器等),合理管理文件描述符的数量非常重要。

结论

在Linux中,TCP套接字通过文件描述符与内核进行交互,实现了网络通信的底层机制。文件描述符作为统一的I/O接口,不仅简化了系统调用的设计,也提高了资源管理的效率。对于开发涉及网络通信的应用程序来说,深入理解TCP套接字和文件描述符之间的关系是非常重要的。

struct proto_ops{ }

在这里插入图片描述

sk_buff_head — sk_buff — 接收缓冲区接收的是一个个报文 用双链表链接报文链表 — 报文的数据类型即为sk_buff

在这里插入图片描述

sk_buff
在这里插入图片描述

对传输层的理解

协议栈:用特定数据结构表述的协议;和特定协议匹配的方法集

无论是图中的file结构体里的方法集还是socket/udp_sock/tcp_sock里的方法集他们的任务都是接受上层调用把数据传给下层,tcp没有把数据直接干到网络里。

服务器收到多个报文,而报文是要被管理的?如何管理呢?先描述,在组织!

在这里插入图片描述