4595 字
23 分钟
计算机网络基础1219

1. 网络基础篇#

1.1 网络的概述#

互联网#

MAC地址数据链路层,交换机通过

IP协议网络层,路由器通过IP

Ping使用ICMP,Ping应用层,ICMP是网络层

速率 带宽 吞吐量 时延

socket编程#

socket编程 服务器 socket socket bind bind() listen connect() accept read read() write write() 使用现成的协议栈进行网络通信 一定用到TCP和IP accept()返回表明服务器已经完成三次握手 connect()返回表明

mac地址只能上局域网,上不了公网

3. TCP#

3.1 TCP基本认识#

TCP头格式有哪些?#

TCP头部如图:

主要介绍以下几个部分:

  • 序列号:

    • 在建立连接时,由计算机生成的随机数作为其初始值
    • 通过 ==SYN== 包传给接收端主机
    • 每发送一次数据,就「累加」一次该「数据字节数」的大小
      • 即新seq等于原seq + 报文字节数
    • 序列号的作用:
      • 解决网络包乱序问题
  • 确认应答号:

    • 指下一次「期望」收到的数据的序列号
    • 发送端收到这个确认应答以后,可以认为在这个序号以前的数据都已经被正常接收
    • 确认应答号作用:
      • 解决丢包的问题
  • 控制位:

    • ACK:
      • 该位为 1 时,「确认应答」的字段变为有效
      • TCP 规定除了最初建立连接时的 ==SYN== 包之外,该位必须设置为 1
    • RST:
      • 该位为 1 时,表示 TCP 连接中出现异常必须强制断开连接
    • SYN:
      • 该位为 1 时,表示希望建立连接
      • 并在其「序列号」的字段进行序列号初始值的设定
    • FIN:
      • 该位为 1 时,表示今后不会再有数据发送,希望断开连接
      • 当通信结束希望断开连接时,通信双方的主机之间就可以相互交换 FIN 位为 1 的 TCP 段

为什么需要 TCP 协议? TCP 工作在哪一层?#

需要TCP协议的原因:

  • IP 层是「不可靠」的
  • 为什么说它不可靠:
    • 它不保证网络包的交付
    • 不保证网络包的按序交付
    • 不保证网络包中的数据的完整性

如果需要保障网络数据包的可靠性,需要:

  • 由上层(传输层)的 TCP 协议来负责

TCP/IP分层模型与OSI七层模型如图:

为什么要TCP协议负责:

  • TCP 是一个工作在==传输层==的==可靠数据传输==的服务
  • 它能确保接收端接收的网络包是==无损坏==、==无间隔==、==非冗余==、==按序==的

什么是 TCP ?#

TCP的本质:

  • TCP 是==面向连接==的、==可靠==的、==基于字节流==的==传输层==通信协议

对这三个概念的理解:

  • 面向连接:
    • 一定是「一对一」才能连接
    • 不能像 UDP 协议可以一个主机同时向多个主机发送消息
      • 也就是一对多是无法做到的
  • 可靠:
    • 无论网络链路中出现了怎样的链路变化,TCP 都可以保证一个报文一定能够到达接收端
      • 即不保证发的这个你能收到,但是丢了我会再发,直到你收到了为止
  • 基于字节流:
    • 用户消息通过 TCP 协议传输时,消息可能会被操作系统「分组」成多个的 TCP 报文
    • 如果接收方的程序不知道「消息的边界」,是无法读出一个有效的用户消息的
    • 并且 TCP 报文是「有序的」
      • 当「前一个」TCP 报文没有收到的时候,即使它先收到了后面的 TCP 报文,那么也不能扔给==应用层==去处理
      • 同时对「重复」的 TCP 报文会自动丢弃

什么是 TCP 连接?#

首先我们要知道连接的定义:

  • 用于保证==可靠性==和==流量控制维护==的某些==状态信息==
  • 这些信息的==组合==,包括 ==Socket==、==序列号==和==窗口大小==称为连接

所以建立一个 TCP 连接需要:

  • 客户端与服务器达成上述三个信息的共识
  • 即:
    • Socket:由 ==IP地址==和==端口号==组成
    • 序列号:用来解决==乱序==问题等
    • 窗口大小:用来做==流量控制==

如何唯一确定一个 TCP 连接呢?#

TCP ==四元组==可以唯一的确定一个连接,四元组包括:

  • 源地址
  • 源端口
  • 目的地址
  • 目的端口

四元组字段存在哪里,作用:

  • 源地址和目的地址的字段(32 位)是在 IP 头部中
  • 作用是:
    • 通过 ==IP协议==发送报文给对方主机
  • 源端口和目的端口的字段(16 位)是在 TCP 头部中
  • 作用是:
    • 告诉 ==TCP协议==应该把报文发给哪个==进程==

有一个 IP 的服务端监听了一个端口,它的 TCP 的最大连接数是多少?#

服务端通常是:

  • 固定在某个本地端口上监听,等待客户端的连接请求

由于客户端 IP 和端口是可变的,那么它能建立的TCP最大连接数就取决于:

  • 客户端的==IP地址==和==端口号==的数量

其理论值计算公式为:

  • ==最大TCP连接数 = 客户端的IP数 X 客户端的端口数==
  • 例如对 IPv4:
    • 客户端的 IP 数最多为 2 的 32 次方
    • 客户端的端口数最多为 2 的 16 次方
    • 也就是服务端单机最大 TCP 连接数,约为 2 的 48 次方

当然,服务端最大并发 TCP 连接数远不能达到理论上限,会受以下因素影响:

  • 文件描述符限制:
    • 每个 TCP 连接都是一个文件
    • 如果文件描述符被占满了,会发生Too many open files
    • Linux 对可打开的文件描述符的数量有三个方面的限制:
      • 系统级:当前系统可打开的最大数量,通过 cat /proc/sys/fs/file-max 查看;
      • 用户级:指定用户可打开的最大数量,通过 cat /etc/security/limits.conf 查看;
      • 进程级:单个进程可打开的最大数量,通过 cat /proc/sys/fs/nr_open 查看;
  • 内存限制:
    • 每个 TCP 连接都要占用一定内存
    • 操作系统的内存是有限的
      • 如果内存资源被占满后,会发生 OOM错误(“Out of Memory” 内存耗尽)。

UDP 和 TCP 有什么区别呢?分别的应用场景是?#

我们先了解一下UDP:

  • UDP 不提供复杂的==控制机制==
    • 利用 IP 提供面向「无连接」的通信服务
  • UDP 协议真的非常简单,头部只有 8 个字节(64 位)
    • UDP 的头部格式如下:
    • 我们来介绍一下UDP协议的头部:
      • 源端口和目的端口:
        • 告诉 UDP 协议应该把报文发给哪个进程
      • 包长度:
        • 该字段保存了 UDP ==首部==的长度跟==数据==的长度之==和==
      • 校验和:
        • 为了提供可靠的 UDP 首部和数据而设计
        • 防止收到在网络传输中==不完整==的 UDP 包

了解了UDP后,就可以看看TCP和UDP有什么区别了:

  • 连接:
    • TCP:
      • 面向连接的传输层协议,传输数据前先要建立连接
    • UDP:
      • 不需要连接,即刻传输数据
  • 服务对象:
    • TCP:
      • 只能一对一
    • UDP:
      • 既能一对一、又能一对多、还能多对多
  • 可靠性:
    • TCP:
      • 可靠交付数据的
        • 什么是可靠交付数据呢:
          • 数据可以==无差错==、==不丢失==、==不重复==、==按序到达==
    • UDP:
      • 是尽最大努力交付,不保证可靠交付数据
      • 如果想要可靠传输呢:
        • 可以基于 UDP 传输协议实现一个可靠的传输协议,比如 ==QUIC== 协议
  • 拥塞控制、流量控制:
    • TCP:
      • 有==拥塞==控制和==流量==控制机制
      • 保证数据传输的==安全==性
    • UDP:
      • 没有这两个机制
      • 即使网络非常拥堵了,也不影响 UDP 的发送速率
  • 首部开销:
    • TCP:
      • 首部长度较长,会有一定的开销
      • 首部在没有使用「选项」字段时是 ==20== 个字节
      • 如果使用了「选项」字段还会变长
    • UDP:
      • 首部只有 8 个字节,并且是固定不变的
      • 开销较小
  • 传输方式
    • TCP:
      • 是==流式传输==,没有边界
      • 但保证==顺序==和==可靠==
    • UDP:
      • 是==一个包一个包==的发送,是有边界的
      • 但可能会==丢包==和==乱序==
  • 分片不同
    • TCP:
      • 数据大小如果大于 MSS 大小,会在==传输层==进行分片
      • 目标主机收到后,也同样在传输层组装 TCP 数据包
      • 如果中途丢失了一个分片,只需要重传丢失的这个分片
    • UDP:
      • 数据大小如果大于 MTU 大小,则会在 ==IP层==进行分片
      • 目标主机收到后,在 IP 层组装完数据,==再传给传输层==

TCP和UDP既然存在区别,那么就有会不同的应用场景:

  • 由于 TCP 是面向连接,能保证数据的可靠性交付,因此经常用于:
    • FTP 文件传输
    • HTTP / HTTPS
  • 由于 UDP 面向无连接,它可以==随时==发送数据,再加上 UDP 本身的处理既简单又高效,因此经常用于:
    • 包总量较少的通信
      • 如 DNS 、SNMP 等
    • 视频、音频等多媒体通信
    • 广播通信

扩展:

  • 首部长度:

为什么 UDP 头部没有「首部长度」字段,而 TCP 头部有「首部长度」字段呢?

  • TCP 有可变长的「选项」字段
  • 而 UDP 头部长度则是不会变化的
  • 所以不需要多一个字段去记录 UDP 的首部长度
  • 包长度:

为什么 UDP 头部有「包长度」字段,而 TCP 头部则没有「包长度」字段呢?

  • 先说说 TCP 是如何计算负载数据长度:
    • 其中 IP 总长度 和 IP 首部长度,在 IP 首部格式是已知的
    • TCP 首部长度,则是在 TCP 首部格式已知的
    • 所以就可以求得 TCP 数据的长度
  • 大家这时就奇怪了问:
    • “UDP 也是基于 IP 层的呀,那 UDP 的数据长度也可以通过这个公式计算呀? 为何还要有「包长度」呢?”
  • 这么一问,确实感觉 UDP 的「包长度」是冗余的
  • 我查阅了很多资料,我觉得有两个比较靠谱的说法:
    • 第一种说法:
      • 因为为了网络设备硬件设计和处理方便,首部长度需要是 4 字节的整数倍
      • 如果去掉 UDP 的「包长度」字段,那 UDP 首部长度就不是 4 字节的整数倍了
      • 所以我觉得这可能是为了补全 UDP 首部长度是 4 字节的整数倍,才补充了「包长度」字段
    • 第二种说法:
      • 如今的 UDP 协议是基于 IP 协议发展的
      • 而当年可能并非如此,依赖的可能是别的不提供自身报文长度或首部长度的网络层协议
        • 因此 UDP 报文首部需要有长度字段以供计算

上面的TCP数据长度不懂???#

TCP 和 UDP 可以使用同一个端口吗?#

答案:

  • 可以

在不同层中有不同的寻址方法:

  • 在数据链路层中:
    • 通过 MAC 地址来寻找局域网中的==主机==
  • 在网络层中:
    • 通过 IP 地址来寻找网络中互连的==主机==或==路由器==
  • 在传输层中:
    • 需要通过端口进行寻址,来识别同一计算机中同时通信的不同==应用程序==

传输层的「端口号」的作用:

  • 是为了区分同一个主机上不同应用==程序==的数据包

而一个程序可以使用两种协议:

  • TCP 和 UDP
    • 这两种协议在内核中是两个完全独立的软件模块
    • 当主机收到数据包后
    • 可以在 IP 包头的「协议号」字段知道该数据包是 TCP/UDP
    • 所以可以根据这个信息确定送给哪个模块(TCP/UDP)处理
    • 送给 TCP/UDP 模块的报文根据「端口号」确定送给哪个应用程序处理
    • 因此,TCP/UDP 各自的端口号也相互独立
      • 如 TCP 有一个 80 号端口,UDP 也可以有一个 80 号端口,二者并不冲突

如何理解是 TCP 面向字节流协议?#

先了解一下字节流与什么有关:

  • 之所以会说 TCP 是面向字节流的协议,UDP 是面向报文的协议
    • 是因为操作系统对 TCP 和 UDP 协议的发送方的机制不同
    • 也就是问题原因在发送方

那么分别看一下TCP和UDP,先来说说为什么 UDP 是面向报文的协议?

  • 当用户消息通过 UDP 协议传输时,操作系统不会对消息进行拆分
    • 在组装好 UDP 头部后就交给网络层来处理
  • 所以发出去的 UDP 报文中的数据部分就是完整的用户消息
    • 也就是每个 UDP 报文就是一个用户消息的边界
  • 这样接收方在接收到 UDP 报文后,读一个 UDP 报文就能读取到完整的用户消息
  • 那么如果收到了两个 UDP 报文,操作系统是怎么区分开的?
    • 操作系统在收到 UDP 报文后,会将其插入到队列里
    • 队列里的每一个元素就是一个 UDP 报文
    • 这样当用户调用 recvfrom() 系统调用读数据的时候
      • 就会从队列里取出一个数据
    • 然后从内核里拷贝给用户缓冲区
    • 如图所示:

再来说说为什么 TCP 是面向字节流的协议?

  • 当用户消息通过 TCP 协议传输时,消息可能会被操作系统分组成多个的 TCP 报文
  • 也就是一个完整的用户消息被拆分成多个 TCP 报文进行传输
  • 这时,接收方的程序如果不知道发送方发送的消息的长度
    • 也就是不知道消息的边界时
  • 是无法读出一个有效的用户消息的
  • 因为用户消息被拆分成多个 TCP 报文后,并不能像 UDP 那样,一个 UDP 报文就能代表一个完整的用户消息

举个实际的例子来说明:

  • 发送方准备发送 「Hi.」和「I am Xiaolin」这两个消息
    • 在发送端:
      • 当我们调用 send 函数完成数据“发送”以后
      • 数据并没有被真正从网络上发送出去,只是从应用程序拷贝到了操作系统内核协议栈中
      • 至于什么时候真正被发送,取决于发送窗口、拥塞窗口以及当前发送缓冲区的大小等条件
      • 也就是说,我们不能认为每次 send 调用发送的数据,都会作为一个整体完整地消息被发送出去
  • 如果我们考虑实际网络传输过程中的各种影响:
    • 假设发送端陆续调用 send 函数先后发送 「Hi.」和「I am Xiaolin」 报文
    • 那么实际的发送很有可能是这几种情况:
      • 第一种情况,这两个消息被分到同一个 TCP 报文,像这样:
      • 第二种情况,「I am Xiaolin」的部分随 「Hi」 在一个 TCP 报文中发送出去,像这样:
      • 第三种情况,「Hi.」 的一部分随 TCP 报文被发送出去,另一部分和 「I am Xiaolin」 一起随另一个 TCP 报文发送出去,像这样:
      • 类似的情况还能举例很多种
        • 这里主要是想说明,我们不知道 「Hi.」和 「I am Xiaolin」 这两个用户消息是如何进行 TCP 分组传输的
    • 因此,一个用户消息与一个 TCP 报文不是对应的
    • 正因为这样,所以 TCP 是面向字节流的协议
  • 当两个消息的某个部分内容被分到同一个 TCP 报文时,就是我们常说的 TCP ==粘包==问题
  • 这时接收方不知道消息的边界的话,无法读出有效的消息
  • 要解决这个问题,要交给==应用程序==

如何解决粘包?#

粘包是指:

  • 两个消息的部分内容被分到同一个 TCP 报文

发生粘包的原因是:

  • 不知道一个用户消息的边界在哪
    • 如果知道了边界在哪,接收方就可以通过边界来划分出有效的用户消息

要解决粘包问题,一般有三种方式分包的方式:

  • 固定长度的消息
  • 特殊字符作边界
  • 自定义消息结构

下面来分别解释一下这三个分包方式:

  • 固定长度的消息:

    • 概念:
      • 每个用户消息都是固定长度
      • 比如规定一个消息的长度是 64 个字节
        • 当接收方接满 64 个字节,就认为这个内容是一个完整且有效的消息
    • 优点:
      • 这种是最简单方法
    • 缺点:
      • 灵活性不高,实际中很少用
  • 特殊字符作边界:

    • 概念:
      • 在两个用户消息之间插入一个特殊的字符串
      • 这样接收方在接收数据时,读到了这个特殊字符,就把认为已经读完一个完整的消息
      • HTTP 是一个非常好的例子
      • HTTP请求报文如图:
      • HTTP 通过设置回车符、换行符作为 HTTP 报文协议的边界
      • 注意:
        • 作为边界点的特殊字符,如果刚好消息内容里有这个特殊字符,我们要对这个字符转义
        • 避免被接收方当作消息的边界点而解析到无效的数据
  • 自定义消息结构:

    • 自定义一个消息结构

    • 由包头和数据组成

    • 其中包头是固定大小的,而且包头里有一个字段来说明紧随其后的数据有多大

    • 比如这个消息结构体,首先 4 个字节大小的变量来表示数据长度,真正的数据则在后面

    • 结构体代码如下:

      struct {
      u_int32_t message_length;
      char message_data[];
      } message;
    • 当接收方接收到包头的大小(比如 4 个字节)后,就解析包头的内容

    • 就可以知道数据的长度

    • 再继续读取数据,直到读满数据的长度,就可以组装成一个完整到用户消息来处理了

3.2 TCP连接建立#

计算机网络基础1219
https://fuwari.cbba.top/posts/计算机网络基础1219/
作者
Chen_Feng
发布于
2023-12-19
许可协议
CC BY-NC-SA 4.0