你需要知道的TCP/IP

392 阅读19分钟

前言

TCP/IP 协议 是网络通信的基石,TCP/IP 协议 不是只有 TCPIP 协议,它是整个网络通信中所有协议的简称。

维基百科:TCP/IP协议簇

维基百科:OSI模型

# TCP/IP 参考模型维基百科
https://zh.wikipedia.org/wiki/TCP/IP%E5%8D%8F%E8%AE%AE%E6%97%8F
# OIS 参考模型维基百科
https://zh.wikipedia.org/wiki/OSI%E6%A8%A1%E5%9E%8B

图片来自 《图解 TCP/IP 与 OSI 参考模型》 中 TCP/IP 协议分层模型

OSI 参考模型 (七层)是个理论模型,实际我们用的是 TCP/IP (四层)模型。不过我们可以通过 OSI 参考模型 来学习 TCP/IP 模型。

应用层:应用程序通信细节的协议,比如常用的 HTTP

传输层:主要是负责两个节点之间数据传输,通信标识是 port 端口号。

网络层:地址管理和路由选择,在两点之间找到一条最佳的通信路线,通信标识是 IP

数据链路层:负责物理层面链接的通信(同一个网段内)。也就是局域网中通过交换机链接的节点。通信标识是 Mac 地址,网卡出厂自带的标识。

物理层:将链路层的数据帧(字节流)转换为电压或光信号传播。

网络通信可以做什么呢?

redisson (一个操作 redis 的 java 库),就是使用的 netty 来做网络通信连接 redis 服务的。

微服务中的服务发现和通信,就需要你熟悉网络通信。

你要是在通信行业,那就不是了解了,你连协议的规范都得很清楚,不然路由器你都整不出来,还说什么 5G

作为一个 Java 后端开发,主要是开发偏应用层面的程序,离底层相对比较远,熟练掌握即可,如果以后做通信行业的时候,你也一定会进一步学习的相关细节的。

TCP/IP 你不了解,也不会有多大问题,CRUD 还是没有问题的。但是你了解了之后,日常开发定位和解决问题方面有很大助力,总之学习 TCP/IP 是一个重要不紧急的事情,根据自己目标和层次安排。

本文内容

  • 局域网中各节点怎么通信
  • 介绍 IP,ICMP,ARP 协议在网络层的作用及路由表的作用,及网段划分,子网掩码、网关的作用
  • 介绍交换机和路由器的作用
  • 介绍 TCP/IP 三次握手和四次挥手,TCP 中通信状态的作用,滑动窗口
  • 介绍 tcp 包格式,ip 包格式,链路层 数据格式

交换机与路由器

交换机

维基百科:交换机

交换机上有多个端口(不是 port)供计算机连接,交换机会维护端口与连接这个端口的 PC 的 Mac 地址映射表。当交换机接受到数据的时候,会根据目的 Mac 地址,发送到对应的端口上,然后经过网线发送到目的 PC。

交换机链接多个电脑组成一个局域网,交换机链接交换机又可以组成一个更大的局域网。

比如 A、B 交换机各有 100 个端口,A 链接了 99 个PC,然后 B 交换机链接99 个,再将其中的一个端口 A/B 之间相互连接组成一个更大的局域网。

路由器

维基百科:路由器

路由器工作在网络层,主要用于将一个网段数据包转发到另一个网段内。路由器上也会有个几个 LAN 口 (Local Area Network,局域网),用于建立局域网。还会有一个 WAN(Wide Area Network,广域网),连接运营商的网络。

路由器也具有交换机的功能,只是 LAN 口 比较少,可以接入的电脑比较少。

PC 或者 手机 连接无线路由器时也会给 PC 分配一个局域网 IP,子网掩码,网关等。

我住的地方的网络拓扑图如下:未命名文件

当手机与电脑通信的时候,实际通过 LAN 口走局域网通信。

当手机访问 维基百科 时,实际是通过路由器跳入到光猫网段,再通过光猫跳入到小区运营商的网络,… 到维基百科的服务器上。

只要需要有 IP 地址的设备(光猫,路由器,PC,手机)都需要有网卡,网卡出厂自带有 Mac 地址。IP 和 Mac 地址的作用后文中会介绍。

image-20200801144243065

交互机和路由器的区别

这部分内容是我自己的理解,我没有在网上找到资料佐证,请谨慎对待

其实交换机和路由器硬件差别不大,只是硬件上的软件决定了它能做什么。

2 层交换机上的软件(只有数据链路层)可能只做解析帧,拿到 mac 地址,然后查找当前交换机的端口对应的 mac 地址,然后从对应的端口传递过去。

路由器(有网络层和数据链路层),当拿到数据包的时候,发现目的 mac 地址不是自己,就会将数据包通过 LAN 口发送出去。

当发送的数据包的 目的 MAC 地址 是当前路由器上 MAC地址 ,路由器就会对其解包,拿到数据包 目的 IP ,然后根据 目的 IP 匹配下一跳 mac 地址,封包为新的帧数据发送出去。

TCP/IP 通信

TCP_IP 同一以太网 (2)

从发送端发送数据的时候,数据经过每层的封包,经物理层传送到接收端。接收端收到数据包,一层一层进行拆包,然后将数据数据发送给我接收端的应用层的应用程序。

通常我们说的第一层就是 物理层 ,第二层是 链路层 …...

数据链路层

image-20200801220255714

源 MAC 地址 就是发送端的 MAC 地址,目标 MAC 地址不是最终的 MAC 地址,是下一跳节点的 MAC 地址。

类型 指的是这个以太网帧中的 数据 是何种类型的数据,比如 IPV4,IPV6。然后调用对应的接口进行处理。

数据链路层传输的帧是有大小限制的(64-1518 字节),能传输的数据的最大值就是 最大传输单元,简称 MTUMaximum Transmission Unit。这个值在以太网中通常是 1500。

# 查看网卡对应的 MTU
ifconfig -a
netstat -i

网络层

网络层主要以 IP 协议为主,也有 ICMP,ARP(在 TCP\IP 模型 中,arp 属于网络层。在 osi 七层模型arp 数据链路层。)。

DNS

IP 是网络层通信的标识。但是 IP 不容易记忆,所以出现了 域名

访问 DNS 可以将域名解析为 IP

可以在本地配置 host ,定义域名和 IP 对应关系,这样就不用解析了。

也可以在电脑配置 DNS 解析时访问的 ip,这样域名解析时就会访问这个服务。

image-20200801182357581
# 解析域名的 ip
dig www.mflyyou.cn

IP 基础

IP 地址 又可以分为 IPV4IPV6,目前使用比较广的是 IPV4 ,所以只介绍 IPV4

IP 地址 由 32 (2 进制)位组成,32 位被 . 分为了四组。每组 8 位,十进制表示就是 xxx.xxx.xxx.xxx(xxx 取值在 0-255)。

IP 地址网络地址 (网段) 和 主机号

同一个网段的电脑用 2 层交互机相连,然后就可以局域网通信了。

同一个网段内,主机号不能重复,重复主机号的电脑不能上网。

为了便于区分出 IP 在那个网段,引入了子网掩码 (netmask)。IP 地址与子网掩码按位与计算可以得出网段,32 位 中取出网段所在的位,剩余就是主机号能取得值。

IP 中主机号全为 0 就是网段,全为 1 就是广播地址。这两个是不能被分配给电脑的。

IP:192.168.202.116

子网掩码:255.255.252.0

网段为:192.168.200.0

广播地址为:192.168.203.255

IP:192.168.201.56

子网掩码:255.255.252.0

网段为:192.168.200.0

广播地址为:192.168.203.255

ICMP

网络层是不可靠传输,发送失败的数据包,网络层是不会再发一次数据包,但是会有 ICMP 包回复告诉你发包到底是什么问题。传输层 可以根据 ICMP 来判断是否需要重发包。

ARP

ARP 用于 IP 的 对应的MAC 地址。

目的 IP 在路由表中查询下一跳的 IP,在查询这个 IP 对应的 mac 地址

查询的这个 IP 是当前网段内的 ip,它会通过广播地址发送给当前网段内所有主机,收到这个协议的主机会判断是否是当前主机,是的话就会恢复当前 ip 对应的 MAC 地址。

image-20200801223925086

通信过程分析

未命名文件

当我在浏览器输入 wwww.mflyyou.cn 的时候:

1、先解析域名(DNS) www.mflyyou.cnIP (目的 IP: 47.104.168.20)

2、将目的 IP 与本地路由表中的子网掩码进行按位与,计算出网段与 Destination 匹配,看哪个匹配度更高,走哪个条目。都没有匹配到走默认条目(0.0.0.0)

# 查看路由表
route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.31.1    0.0.0.0         UG    100    0        0 eth0

3、然后用 arp 查询(有缓存可不查,走缓存)192.168.31.1 对应的 mac 地址

4、数据链路层封装以太网帧数据包中的目的 MAC 地址址就是 192.168.31.1 对应的 mac 地址,然后将数据帧发送到下一个节点(这也就常说的下一跳,数据包发送只是找到当前接节点的下一个节点)

5、到下一个路由器节点,路由器解包,看是发给自己的数据包(根据帧中的目的 MAC 地址与自己的 MAC 地址比较),不是就丢弃了;是的话就会解包拿到 目的 IP (47.104.168.20),然后在当前路由器上根据路由表查询下一跳,发送给下一个节点;。。。。 直到目的服务器,或者发送的包 TTL 为 0

6、发到目的服务器的网卡上,网卡将数据复制到内核缓冲区,应用程序从缓冲区中读取数据

IP 数据格式

IPv4 数据结构

图来自《图解 TCP/IP》

  • 版本(Version):4 bit 构成,代表当前 IP包是哪个版本,IPv4 或者 IPv6,为 4 时表示当前是 IPv4。

  • 首部长度(Internet Header Length):由 4 bit 构成,一般 20字节大小。

  • 标识(Identification):用于分片重组用,值相同的属于同一个 IP 数据包

  • 标志(Flags):用于判断是否还有分片。

  • 总长度(Total Length):16 个字节,IP 数据包总的长度,最长可为 65525 字节。

  • 分段偏移(Fragment Offset):表示这个包在原来 IP 包中的位置。

  • 生存时间 TTL(Time To Live): IP 包在路由转发中存活的时间,被路由转发一次,次数减 1,为 0 时,数据包被丢弃。

  • 挂载协议标识 (Protocol):记录数据包中 Data(实际发送的数据)是什么类型的数据,1 标识 ICMP, 4 标识 IP, 6标识 TCP, 17 标识 UDP。根据这个挂载协议程序就知道调用哪些接口来进行后续的处理了。

数据链路层中 以太网数据帧MTU 是 1500 字节,限定了 IP 数据包最大为 1500 字节。然后去掉 IP 包首部 20 字节,一般 IP 数据包发送的数据为 1480 字节。

当我们发送一个 3058 字节的 IP 数据包时,这显然大于了数据链路层的 MTU (1500 字节)。所以网络层会对大于链路层MTU 的数据包进行分片。拆分一个一个的1500 的数据包发送接收端,接收端接收到这三个包,在汇聚成一个完成的,在调用传输层接口。

# 会发送 3050 字节数据与 8 字节的 ICMP 首部,这个命令会总共发送 ip 数据大小 3058 字节。
ping -s 3050 www.mflyyou.cn

image-20200801230015436

image-20200801230141070 image-20200801230528418 image-20200801230423653

通过 wireshark 抓包可以看到,IP 数据包的首部长度占了 20 字节,实际每次发送数据为 1480 字节,最后一次发送了 98 字节。

从 Fragment 和 Identification 可以看到这三个包属于同一个 IP 数据包,并且从 Fragment offset 能将这三个包合成一个完成的网络层数据包。

传输层 TCP

TCP 是面向链接的,可靠的,全双工协议。

面向连接就是发送之前,需要建立一个链接通道,数据都是在这个链接中发送。

网络层 是不可靠协议,数据发送失败是不会重发的。

TCP 协议中发送端会记录发送的那些数据包被客户端收到了。接收端接受数据之后,会回复一个 ACK 包(由数据格式中的控制位决定),确认应答号告诉发送端哪些数据包接收到了。

发送端 发送了数据包之后,这个包会有一个重发倒计时,在这个倒计时内没有收到接收端 回复 ACK 包,就会再重发一个数据包。如果是 HTTP 请求 ,就相当于同样的数据请求了两次。

我们知道支付接口都要求幂等性,有一部分原因是因为这个超时重发。发送端发送了请求,接收端处理好业务之后回复的 ACK 包超时,发送端超时重发这个请求。如果不保证接口的幂等性,那么扣钱就会扣两次。

我们要做的就是保证这个重发 n+1 次不再扣用户的钱,一般会用一个 token 来判断是不是重复请求,重复就不走扣款处理了,直接返回已经支付,保证接口的幂等性。或者用一个账单流水来保证幂等性。

连接既然需要建立,那么也会有连接断开。断开连接需双方协商好之后断开连接,不能单方面关闭而不管对方。因为建立连接之后占用的计算机资源需要释放掉。你单方面强制断开连接释放了资源,但是对方不知道需要断开连接,分配的计算机资源一直占用那就是不可靠协议了。所以 TCP 有四次挥手断开连接。

全双工就是连接两边都可以主动发送接受数据,而不是轮训访问有没有数据到达。

TCP 数据格式

首先我们要先了解 TCP 数据格式,才能更容易知道 TCP 的工作原理。

image-20200802000246545

源端口号(Source Port)

占用 2 个字节。标识 发送端 程序的端口号,当接收端需要回复消息的时候,需要带上这个端口号。

目的端口号(Destination Port)

占用 2 个字节。标识 接收端 程序的端口号,可以传递给监听在这个端口的程序

控制位(Control Flag)

占用 6 位,不满一个字节。标识当前 TCP 包是什么包,在通信过程中有一些特殊作用。

SYN

表示希望建立三次握手链接,并初始化序列号。

ACK

对收到数据包的应答确认。接收端接受数据之后,会回复 ACK 包,发送端从其上 确认应答号 知道接收端哪些数据已经接受了。

FIN

表示没有数据发送了,希望断开连接

PASH

接收端接收到这个数据包需要立刻传递给应用层,不能等待接收更多的数据包

RET

链接出现异常,需要强制断开连接

URG

表示包中有需要紧急处理的数据

序列号(Sequence Number)

占用 4 个字节。TCP 三次握手的时候,发送端和接收端各自初始化(随机的)自己的 `序列号。

我们可以这样理解,发送端发送的数据就是一个字节数组,这个数组中每个字节都有一个 序列号

发送端和接收端都有自己的序列号,并且不相同,在三次握手的时候自己初始化,然后告知对方。

确认应答号(Acknowledgement Number)

占用 4 个字节。确认应答号 也是指的序列号,指的是期望发送端下次发送的序列号,这个序列号(确认应答号)之前的数据已经接受处理了。

下图是我抓包建立三次链接,然后我发送三次 1\n 数据。

三次握手,发送端通过发送 SYN 包,发送自己的初始化序列号(893189542),然后发送的每个字节都会有一个序列号。

接收端发送 ACK 包中的 确认应答号,指明这个序列号之前的数据我已经接受了。

窗口大小(Window Size)

窗口大小适用于流控的。发送端不能一直发送消息,需要根据我的接受能力来调整发包的速率。

未命名文件

内核会为每个 TCP/IP 分配读写缓冲区,网卡会从这些读写缓冲区中把数据取走,然后发送。数据大致可以分为这几类。

TCP/IP 是可靠连接,所以它需要记录哪些数据发送已被对方接受了(由确认应答号可以知道),接受的数据会被淘汰掉,节省内存空间。

窗口大小作用:接收端会通过 ACK 告诉 发送端 调整窗口大小。

当窗口中的数据全都是 已发送未确认数据 时,发送端不能再发送新的数据,必须等待窗口空出位置来。

未命名文件 (2)

当有一个数据包被确认了,发送端就可以发送新的数据包。已发送未确认数据 会在超时的时候重新发包。

滑动窗口百度百科

校验和 (Checksum)

占用 2 个字节。校验和 用于校验数据包是否损坏。每个数据包都一个 校验和接收端 接收到数据之后,使用相同的算法对数据计算出一个值,然后和 校验和比较,不一样说明数据在传输过程中损坏了,接收端 会丢弃这个包,等待 发送端 重新发这个包。

TCP 中 MSS

链路层能发送的最大以太网帧为 1500 字节,MTU 为 1500。

IP 数据包能发送的最大数据 = MTU - IP 首部大小(一般 20 字节),IP 数据包超过这个 1500 字节会分片

TCP 传输数据以段 (Segment) 为单位。

TCP 为了避免分片,会主动将数据分片之后交给网络层。 TCP 能传输的最大分段(只是数据不包括首部)称之为 Max Segment Size,简称为 MSS。

MSS = MTU - IP 首部大小 - TCP 首部大小

在以太网中 TCP 的 MSS = 1500(MTU) - 20(一般 IP 首部大小) - 20(一般 TCP 首部大小)= 1460,这个值需要根据首部计算

image-20200802211639395

MSS 值在三次握手时,会通过 MTU 计算的。

TCP 三次握手建立连接

图片来自 码出高效:Java 开发手册

为什么是三次握手建立连接呢?很多面试官也会问。这其实是可靠连接的最少握手次数。

image-20200802212808724

图片来自 码出高效:Java 开发手册

这里还有个 全连接队列和半链接队列 的知识点

TCP 四次挥手断开连接

图片来自 码出高效:Java 开发手册

CLOSE_WAIT 是收到对方 FIN 包之后,回复 ACK 之后进入的状态。之后不会接受数据了,进行已收数据的业务处理之后,在发送一个 ACK+FIN,进入 LASK_ACK,然后等待对方发送 ACK,超时没有等到,会重试发送(内核可以配置重试发送次数)。当你发现服务端有大量的 CLOSE_WAIT 链接,服务端的代码有问题,需要排查。

TIME_WAIT 的链接多的话,服务端可以优化,不然这个链接会占用很长时间,在高并发的时候,会导致没有资源释放的慢。

MSL 为 Maximum Segment Lifetime,在 centos 中默认值为 60s

# sysctl -a | grep tcp_fin_timeout
# 推荐小于 30,也不能太小,15-30
net.ipv4.tcp_fin_timeout = 60

说明 A 机器链接会在 120 s 之后才能释放。这个是为了保证 B 机器 能接收到最后一个 ACK,当处于 LAST_ACK 的超时没有收到A 发来的 ACK 的话,会重试发送一个 FIN+ACK。这个 2MSL 也是为了最大限度保证 B 机器正常关闭。

三次握手建立连接四次挥手断开连接 需要结合抓包工具自己分析一下,理解会更深刻。

网络抓包

Wireshark 抓包分析是很厉害的,mac oslinux 都有命令行程序 tshark,可以在服务器用 tshark 抓包,拿到本地来分析。

抓包的时候一定要指定抓什么包,什么包都抓的话,一会你的电脑内存就飙升好多(别问我为啥知道,问就是 30g 内存都让它吃了)。

Wireshark 有个 抓包过滤器显示过滤器。抓包的时候指定抓什么包这是 抓包过滤器的作用,抓包之后显示显示那些内容那是 显示过滤器的作用

# -i 指定那个网卡 
# -f 指定抓包过滤器
# -Y 显示过滤器
# -w 指定抓包数据到文件,没有 -w 输出屏幕
# -V 显示 TCP/IP 每层包的详细信息,建议将抓包的文件在图形化界面中查看,不指定 -V
tshark  -i en0 -f "tcp" -Y "http"

# 抓取访问 www.mflyyou.cn 的包
tshark  -i en0 -w a.pcap -f "host www.mflyyou.cn"

# 指定抓那个协议 tcp,ip,icmp,arp,udp
tshark  -i en0 -f "tcp"


# host 指定域名或者 ip
# port 指定端口
# 访问 www.mflyyou.cn 的包,或者 icmp. ping www.baidu.com 也会被抓到
tshark  -i en0 -f "host www.mflyyou.cn || icmp"
tshark  -i en0 -f "port 80"

# 条件之间支持逻辑运算符 || && !
# 抓取 ssh 链接的包
tshark  -i en0 -f "host www.mflyyou.cn && port 22"

参考资料

《图解 TCP/IP》

linux-tcp 说明

鸟哥私房菜:基础网络的概念