源端口、目标端口:计算机上的进程要和其他进程通信是要通过计算机端口的,而一个计算机端口 某个时刻只能被一个进程占用,所以通过指定源端口和目标端口,就可以知道是哪两个进程需要通 信。源端口、目标端口是用16位表示的,可推算计算机的端口个数为2^16个,即65536
序列号:表示本报文段所发送数据的第一个字节的编号。在TCP连接中所传送的字节流的每一个字节都会按顺序编号。由于序列号由 32 位表示,所以每2^32个字节,就会出现序列号回绕,再次从 0 开始,作用是解决数据包在网络传输中的乱序问题
确认号:表示接收方期望收到发送方下一个报文段的第一个字节数据的编号。也就是告诉发送方:我希望你(指发送方)下次发送的数据的第一个字节数据的编号为此确认号,发送方收到确认应答以后,说明在这个序号以前的数据包都已经被成功接收了,作用是确认数据包在传输过程中是否有丢失
数据偏移:表示TCP报文段的首部长度,共4位,由于TCP首部包含一个长度可变的选项部分,需要指定这个TCP报文段到底有多长。它指出 TCP 报文段的数据起始处距离 TCP 报文段的起始处有多远。该字段的单位是32位(即4个字节为计算单位),4位二进制最大表示15,所以数据偏移也就 是TCP首部最大60字节
控制位
URG:表示本报文段中发送的数据是否包含紧急数据。后面的紧急指针字段(urgent pointer)只 有当 URG=1 时才有效
ACK:表示是否前面确认号字段是否有效。只有当 ACK=1 时,前面的确认号字段才有效。TCP 规定,连接建立后,ACK必须为1,带 ACK 标志的 TCP 报文段称为确认报文段
PSH:提示接收端应用程序应该立即从TCP接收缓冲区中读走数据,为接收后续数据腾出空间。如 果为1,则表示对方应当立即把数据提交给上层应用,而不是缓存起来,如果应用程序不将接收到 的数据读走,就会一直停留在TCP接收缓冲区中
RST:如果收到一个 RST=1 的报文,说明与主机的连接出现了严重错误(如主机崩溃),必须释放连接,然后再重新建立连接。或者说明上次发送给主机的数据有问题,主机拒绝响应,带 RST 标志的 TCP 报文段称为复位报文段
SYN:在建立连接时使用,用来同步序号。当SYN=1,ACK=0时,表示这是一个请求建立连接的报文段;当 SYN=1,ACK=1 时,表示对方同意建立连接。SYN=1,说明这是一个请求建立连接或同意建立连接的报文。只有在前两次握手中SYN才置为 1, 标志的 TCP 报文段称为同步报文段
FIN:表示通知对方本端要关闭连接了,标记数据是否发送完毕。如果 FIN=1 ,即告诉对方:“我的数据已经发送完毕,你可以释放连接了”,带 FIN 标志的 TCP 报文段称为结束报文段
窗口大小:表示现在允许对方发送的数据量,也就是告诉对方,从本报文段的确认号开始允许对方 发送的数据量,达到此值,需要ACK确认后才能再继续传送后面数据,由Window size value * Window size scaling factor(此值在三次握手阶段TCP选项Window scale协商得到)得出此值
校验和:提供额外的可靠性
紧急指针:标记紧急数据在数据字段中的位置
选项部分:其最大长度可根据TCP首部长度进行推算。TCP首部长度用4位表示,选项部分最长 为:(2^4-1)*4-20=40字节
TCP包头常见选项:
指明自己期望对方发送TCP报文段时那个数据字段的长度。比如:1460字节。数据字段的长度加 上TCP首部的长度才等于整个TCP报文段的长度。MSS不宜设的太大也不宜设的太小。若选择太 小,极端情况下,TCP报文段只含有1字节数据,在IP层传输的数据报的开销至少有40字节(包括 TCP报文段的首部和IP数据报的首部)。这样,网络的利用率就不会超过1/41。若TCP报文段非常 长,那么在IP层传输时就有可能要分解成多个短数据报片。在终点要把收到的各个短数据报片装配 成原来的TCP报文段。当传输出错时还要进行重传,这些也都会使开销增大。因此MSS应尽可能 大,只要在IP层传输时不需要再分片就行。在连接建立过程中,双方都把自己能够支持的MSS写入 这一字段。 MSS只出现在SYN报文中。即:MSS出现在SYN=1的报文段中 MTU和MSS值的关系:MTU=MSS+IP Header+TCP Header
通信双方最终的MSS值=较小MTU-IP Header-TCP Header
为了扩大窗口,由于TCP首部的窗口大小字段长度是16位,所以其表示的最大数是65535。但是随 着时延和带宽比较大的通信产生(如卫星通信),需要更大的窗口来满足性能和吞吐率,所以产生 了这个窗口扩大选项
可以用来计算RTT(往返时间),发送方发送TCP报文时,把当前的时间值放入时间戳字段,接收方 收到后发送确认报文时,把这个时间戳字段的值复制到确认报文中,当发送方收到确认报文后即可 计算出RTT。也可以用来防止回绕序号PAWS,也可以说可以用来区分相同序列号的不同报文。因 为序列号用32为表示,每2^32个序列号就会产生回绕,那么使用时间戳字段就很容易区分相同序 列号的不同报文
实际生产中我们可以直接使用 wireshark 进行抓包查看 TCP 的通信过程,本文中使用 tcpdump 和 wireshark 结合, 主要是想顺带演示一下 tcpdump,毕竟在生产中我们使用的大多是 Linux 服务器,查看简单的网络问题,使用命令行模式的 tcpdump 可能会更多一些。
centos8 服务端 http 服务的信息如下
[root@centos8 ~]#hostname -I | awk '{print $2}'
10.0.0.11
[root@centos8 ~]# yum -y install httpd
[root@centos8 ~]# yum -y install tcpdump # 需要配置 epel 源
# epel 源配置如下
[root@centos8 ~]#cat /etc/yum.repos.d/epel.repo
[epel]
name=epel
baseurl=https://mirrors.tuna.tsinghua.edu.cn/epel/$releasever/Everything/x86_64
gpgcheck=0
enabled=1
[root@centos8 ~]#
centos7 的客户端机器信息
[root@centos7 ~]# hostname -I|awk '{print $2}'
10.0.0.12
[root@centos7 ~]#
在 centos8 机器上使用 tcpdump 抓包
[root@centos8 ~]#tcpdump -i eth1 -nn -c5 tcp port 80 -w tcp.pcap
dropped privs to tcpdump
tcpdump: listening on eth1, link-type EN10MB (Ethernet), capture size 262144 bytes
5 packets captured
11 packets received by filter
0 packets dropped by kernel
[root@centos8 ~]#
在 cetons7 机器上访问 centos8 的 http 服务
[root@centos7 ~]# curl 10.0.0.11
因为 httpd 服务采用 TCP 协议通信,所以,首先需要三次握手
将保存下来的 tcp.pcap 文件下载到桌面上,使用 wireshark 打开(文件–打开)即可
TCP 第一次握手
TCP 第二次握手
TCP 第三次握手
客户端请求网页(发送 GET 请求)
服务端响应内容
四次挥手关闭连接,关闭连接的目的:防止数据丢失;与应用层交互
四次挥手分为两种情况
客户端(后服务端)主动断开连接
客户端和服务端同时断开连接
此处仅演示客户端主动断开连接的情况
实验环境不变,只是这次为了方便演示我们使用 ssh 登录后退出来演示四次挥手的过程(我们只考虑正常的通信情况)
服务端 centos8 抓包
[root@centos8 ~]#tcpdump -i eth1 -nn tcp port 22 and host 10.0.0.12 -w tcp_wave.pcap
dropped privs to tcpdump
tcpdump: listening on eth1, link-type EN10MB (Ethernet), capture size 262144 bytes
^C66 packets captured
66 packets received by filter
0 packets dropped by kernel
[root@centos8 ~]#
centos7 客户端登录 centos8 服务端,之后退出来演示 TCP 四次挥手的过程
[root@centos7 ~]# ssh [email protected]
[email protected]'s password:
Last login: Tue Apr 20 09:59:38 2021 from 10.0.0.12
[root@centos8 ~]#exit
logout
Connection to 10.0.0.11 closed.
[root@centos7 ~]#
TCP 第一次挥手
TCP 第二次挥手
TCP 第三次挥手
TCP 第四次挥手
IP 层是不可靠的,它不能保证网络包的按需按序交付,也不能保证网络包中数据的完整性。
网络中数据包的可靠性需要其由其上层(传输层)来负责
简单来说 TCP 连接由三部分组成:socket + 序列号 + 窗口大小
socket:由 IP 地址 + 端口号组成
序列号:用来解决数据包乱序问题
窗口大小:目的是流量控制,限速等
答:TCP 五元组可以唯一的确定一个连接,五元组包括如下:
协议
源 IP
源端口
目的 IP
目的端口
源地址和目的地址的字段(32位)是在 IP 头部中,用于主机与主机之间的通信
源端口和目的端口的字段(16位)是在 TCP 头部中,用于进程与进程之间的通信
采用三报文握手主要是为了防止已失效(序列号超时或过期)的连接请求报文段突然又传送到了,因而产生错误。
客户端和服务端是双向通信的,如果是两次握手,只能保证一方初始序列号能被对方成功接收,没办法保证另外一方的初始序列号都能被确认接收
四次握手其实也是可以的,只不过服务器端可以将 ACK 和 SYN 一起发送给客户端,同样也可以同步双方的初始化序号,建立可靠的连接,所以没必要在增加一次通信开销了
客户端和服务端是双向通信的,客户端向服务端发送 FIN 时,只是表示客户端没有数据要发送了,并不能代表服务端也没有数据要发送了,此时客户端仍然是可以接受数据的
服务器收到客户端的 FIN 报文后,回复一个 ACK 应答报文,表示同意断开连接
而此时服务端可能还有数据需要发送和处理,等服务端没有数据要发送时,会发送一个 FIN 报文给客户端,客户端收到 FIN 报文后,回复一个 ACK 应答报文,表示同意断开连接
以上可知,TCP 挥手的过程不能像握手那样,将 FIN 和 ACK 报文一起发送,所以不能使用三次挥手,而是四次挥手
第三次握手是可以携带数据的,前两次握手是不可以携带数据的
# 内核接收数据包的最大值
net.core.netdev_max_backlog
# 增⼤半连接队列
net.ipv4.tcp_max_syn_backlog = 1024
# 增大 somaxconn
echo 1024 > /proc/sys/net/core/somaxconn
# 增⼤应用程序的 backlog 的⽅式,以 Nginx 为例
server {
listen 80 default backlog=1024;
server_name localhost;
......
}
# 减少 SYN+ACK 重传次数
# 连接每个 SYN_RECV 时,如果失败的话,内核还会自动重试,并且默认的重试次数是 5 次
net.ipv4.tcp_synack_retries = 1
# 开启 tcp_syncookies 功能
net.ipv4.tcp_syncookies = 1
TCP SYN Cookies 也是一种专门防御 SYN Flood 攻击的方法。SYN Cookies 基于连接信息(包括源地址、源端口、目的地址、目的端口等)以及一个加密种子(如系统启动时间),计算出一个哈希值(SHA1),这个哈希值称为 cookie。
然后,这个 cookie 就被用作序列号,来应答 SYN+ACK 包,并释放连接状态。当客户端发送完三次握手的最后一次 ACK 后,服务器就会再次计算这个哈希值,确认是上次返回的 SYN+ACK 的返回包,才会进入 TCP 的连接状态。
因而,开启 SYN Cookies 后,就不需要维护半开连接状态了,进而也就没有了半连接数的限制。
TIME_WAIT 是 TCP 四次挥手过程中,主动断开连接的一方在第三次挥手后才会有的状态
是正常挥手中必不可少的一个状态,原因是:
防止因为网络原因收到旧的数据包
保证被动断开连接的一方能正确的收到 ACK 从而正常的关闭连接
如果服务器有大量的 TIME_WAIT,则首先说明是服务器主动断开的连接,过多的 TIME_WAIT 会暂用大量的内存和端口资源,导致服务端无法再处理新的连接请求
解决办法是优化内核
# 增大处于 TIME_WAIT 状态的连接数量
net.ipv4.tcp_max_tw_buckets = 1048576
# 增大连接跟踪表的大小
net.netfilter.nf_conntrack_max = 1048576
# 缩短从 TIME_WAIT_2 到 TIME_WAIT 状态的超时时间 ,让系统尽快释放它们所占用的资源。
net.ipv4.tcp_fin_timeout = 15
# 缩短连接跟踪表中处于 TIME_WAIT 状态连接的超时时间 ,让系统尽快释放它们所占用的资源
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 30
# 增大本地端口的范围 ,这样就可以支持更多连接,提高整体的并发能力
net.ipv4.ip_local_port_range = 10000 65000
# 增大单个进程可分配的最大文件数
fs.nr_open = 1048576
# 增大系统的最大文件描述符数,或在应用程序的 systemd 配置文件中,配置 LimitNOFILE
fs.file-max = 1048576
# 开启 TIME_WAIT 重用,这样,被 TIME_WAIT 状态占用的端口,还能用到新建的连接中,只能用于客户端
net.ipv4.tcp_tw_reuse = 1
# 开启 TCP 时间戳(配合上面使用)
net.ipv4.tcp_timestamps=1
# 重置超时连接(可能会引起其他问题,慎用!!!)
net.ipv4.tcp_max_tw_buckets
注意:net.ipv4.tcp_tw_reuse 和 net.ipv4.tcp_tw_recycle 都依赖于 net.ipv4.tcp_timestamps,net.ipv4.tcp_timestamps 是 tcp 的一个选项,如果开启了它,那么就无法使用 tcp 的其他选项了,包括 net.ipv4.tcp_syncookies。
TCP服务器进程以后就不能再收到TCP客户进程发来的数据
因此,应当有措施使TCP服务器进程不要再白白等待下去
TCP 有一个保活机制,原理就是
TCP 服务器进程每收到一次 TCP 客户进程的数据,就重新设置并启动保活计时器(2小时定时)若保活计时器定时周期内未收到 TCP 客户进程发来的数据,则当保活计时器到时后,TCP 服务器进程就向 TCP 客户进程发送一个探测报文段,以后每隔 75 秒发送一次。若连续发送 9 个探测报文段后仍无 TCP 客户进程的响应,TCP 服务器进程就认为 TCP 客户进程所在主机除了故障,接着就关闭这个连接
可以在内核中设置,默认如下:
net.ipv4.tcp_keepalive_time=7200
net.ipv4.tcp_keepalive_intvl=75
net.ipv4.tcp_keepalive_probes=9
因为时间等待状态以及处于该状态2MSL时长,可以确保TCP服务器进程可以收到最后一个TCP确认报文段而进入关闭状态
另外,TCP客户进程在发送完最后一个TCP确认报文段后,在经过2MSL时长,就可以使本次连接持续时间内所产生的所有报文段都从网络中消失,这样就可以使下一个新的TCP连接中,不会出现旧连接中的报文段
ss 命令来自于 iproute 包,代替 netstat,netstat 通过遍历 /proc 来获取 socket 信息,ss 使用 netlink 与内核 tcp_diag 模块通信获取 socket 信息,用法和输出结果类似于 netstat 命令
# 查看 TCP 的通信状态
[root@centos8 ~]#ss -antp
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=767,fd=8),("nginx",pid=766,fd=8))
LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=734,fd=4))
LISTEN 0 100 127.0.0.1:25 0.0.0.0:* users:(("master",pid=849,fd=16))
ESTAB 0 0 10.0.0.11:22 10.0.0.10:49483 users:(("sshd",pid=1281,fd=5),("sshd",pid=1269,fd=5))
LISTEN 0 128 [::]:80 [::]:* users:(("nginx",pid=767,fd=9),("nginx",pid=766,fd=9))
LISTEN 0 128 [::]:22 [::]:* users:(("sshd",pid=734,fd=6))
LISTEN 0 100 [::1]:25 [::]:* users:(("master",pid=849,fd=17))
# 统计 TCP 各个状态的总数
[root@centos8 ~]#ss -antp|awk 'NR>=2{status[$1]++}END{for(i in status){print i"\t"status[i]}}'
LISTEN 6
ESTAB 1
[root@centos8 ~]#