TCP报文结构
我们想要介绍TCP/IP
三次握手,需要有个前置条件,我们得补充下相关背景,目前我们所使用的的网络协议为TCP/IP
4层协议,从下往上即: 数据链路层、网络层、传输层 以及 应用层。
图示如下:
在使用网络进行数据发送过程中,会将数据进行封包操作,到了对方机器,会逐步解包。我们使用的TCP
协议,则是在传输层上,我们一般将其数据称之为报文段。
在介绍TCP
如何通过三次握手建立连接之前,还需要先看看其TCP
报文段结构,图示如下:
TCP
报文段由2部分组成,首部 (黄色+绿色)和数据报文(蓝色),其中首部又包含基本首部20字节(上图黄色部分) 和 选项部分(上图绿色部分) 最多40字节。
上述报文段中,
- 源端口 和 目的端口
这2个没啥特别的意思,就是记录双方端口的,分别是2个字节,所以说,服务器的端口最大为65535,因为这是2的16次方还要减去一个数(这个数为0),所以是65535。
- 序号 和 确认序号
分别占了4个字节,我们常听见的seq
和ack
以及seq_ack
等都是指的它两。
- 首部长度
首部长度,也称之为数据偏移,用来指定首部长度的,如前面所述,报文段固定首部20个字节,还有40个字节的选项部分,如该首部长度为5,则证明首部就20个字节(5*4个字节),该值最多为15,即60个字节。
- 保留
占了3个字节,现在还没有启用。
- 状态标志位
URG
、ACK
、PSH
、RST
、SYN
、FIN
这些都是占用了一个位,用以标志该报文段的状态,这些标志位在启用的时候会置为1,不启用的时候会置为0。其中
SYN
表示新建连接。FIN
表示释放连接。ACK
表示确认收到数据。RST
表示请求重发。PSH
表示紧急数据,需要尽快交付至应用层。URG
表示紧急数据,需优先发送。
- 接收窗口:2个字节,用于告知对方接收窗口的大小,做流量控制使用。
- 校验和:2个字节,会校验伪首部、首部以及数据三个部分。
- 紧急指针:2个字节,当
URG
置为1的时候才有效,该数据会指出有多少字节需要紧急传输。 - 可选项:最大为40字节,包括窗口缩放、
MSS
、SACK
、时间戳等。
以上就是整个tcp
报文段的内容了,只是做了一个简单的概述。
tcp如何通过三次握手建立连接
假设主机A
和主机B
想要建立连接,在实际发送报文数据之前,需要先发送3个空包用于建立连接,建立连接和TCP
的状态图示如下:
首先,在建立握手之前,服务器必须要优先于客户端启动服务,而后客户端才能进行连接。
当客户端第一次发起请求的时候,需将报文段SYN
标志位置为1
,并且指定一个随机数作为seq
,此时客户端的状态为SYN SEND
。
当服务器收到客户端请求后,需将报文段中的SYN
和ACK
标志位置为1
,并且指定一个随机数作为seq
,ack_seq
值为x+1
,此时服务器的状态为SYN REVD
。
当客户端收到请求服务器请求后,此时客户端的状态为ESTABLISHED
需将报文段中的ACK
标志位置为1
,seq
值为x+1
,ack_seq
值为x+1
。服务器接收到后,此时服务器的状态也为ESTABLISHED
。
至此,客户端和服务器已经成功建立连接。
使用tcpdump进行抓取一个会话包
上面已经简单介绍了什么tcp的前置只是 和 为什么需要三次握手,而不是两次,本段落就来介绍一下如何使用tcpdump
进行抓包,抓包工具有很多,在windows
下,你可以选择Wireshark
,在linux
你可以选择tcpdump
。也可以在linux
抓包后保存到文件中,再导入Wireshark
打开。
前置条件:若我们想使用tcpdump
抓取信息,需要提前建立一个tcp
服务器才行,这里搭建了一个nginx
服务器,index.html
简单设置为了hello pdudo
。
如果你的操作系统没有安装tcpdump
,那么需要使用yum
来安装一下:
yum install tcpdump -y
使用man tcpdump
可以获取更加详细的帮助文档,本篇文章需要用到的参数为如下几个:
-A
: 以ascii
码来显示报文。
-S
: 输出TCP
握手的全部信息,而非缩略信息。
-s num
: 从每个数据包中抓取num
字节的数据。
port
: 指定从哪个端口抓取。
-i interface
: 指定抓取的网口。
例如: 我们想抓取本地回环地址(lo
)的80
端口,以ASCII
码的方式输出:
tcpdump -s 0 -S -A port 80 -i lo
抓取之后,结果为:
其中黄色线条框起来的地方就是三次握手的信息了。
如之前所述,可以清晰的看到,第一次握手信息是客户端发送给http
服务器的,即: localhost.46452 > localhost.http
。
来解释一下请求的第一段报文信息:
Flags [S]
: 表示报文的SYN
标志位为1。seq 532696697
:表示seq
为: 532696697 。win 43690
:表示滑动窗口为43690。options
存储的是TCP
的选项信息,这是可变长信息,最大为40字节,内容为:
mss 65495
:mss
表示每一个报文段所能支持的最大数据长度,这个只在握手的时候会进行协定,所以看后续的报文中,都没有出现mss
相关信息。sackOK
:TS val 3302976138 ecr 0
: 时间戳,用于测试报文往返时间。wscale 7
: 表示接收窗口
length
:表示报文数据长度。
其他的,就不一一叙述了。
在结合之前的理论信息,我们可以知道:
当客户端第一次请求握手协议的时候,seq
发送的是x
为2992020176
。
当服务器收到后,客户端发送上来的x+1
作为ack
返回,其值为2992020177
,并且重新取了一个随机数y
,其值为4127097599
。
当客户的接收到该数据后,向服务器发送了一个ack
,并且还携带了seq
和seq_ack
,只不过这里tcpdump
给屏蔽了,使用其他抓包软件,例如 Wireshark 就可以正常显示出来。
在linux中如何查看连接状态
之前还介绍了tcp
三次握手的状态信息,例如SYNC SEND
、 SYNC REVD
应该从哪儿看呢?注意,这个只是一个标准来规定它的状态,而不是存储在报文中,在linux
中,可以使用netstat
、ss
等命令来查看当前服务器有哪些链接,这些链接分别是什么状态,例如:
ss -a | head -n 1 ; ss -a | grep ^tcp
上述命令表示展示当前机器所有的socket
信息,并且使用grep
过滤了一下以tcp
开头的,我们能够得到如下截图:
上述截图中会显示当前机器的所有socket
,包括服务器,如果是真实服务器,会有很多条信息,比如我们想赛选出当前服务器哪些状态分别有多少个,应该如何选择呢?
这里可以使用awk
,将状态给存储到数组中,继而遍历出来即可,命令为:
ss -a | grep ^tcp | awk '{status[$2]=status[$2]+1} END{for (i in status) {print i,status[i]}}'
该命令为使用了awk
将第二列的值当做数组的key
给存上,当再次出现时,该数组key
的值就+1,最后再遍历数组,就可以得到结果了,我们找台机器看下:
如果是使用的netstat
命令,则需要取其最后一列用以awk
计算。
linux对于tcp三次握手调整队列
对于tcp
三次握手,linux
中会用到2个队列,分别是 半连接队列 和 全连接队列。
其中半连接队列也称之为SYN
队列,是指已经收到了客户端的请求,但是还没有建立完整连接,此时网络状态为SYN_REVD
介于此linux
会将该链接放入SYN
队列中,并且等待客户端发送ACK
报文以便建立连接。
而全连接队列也称之为Accept
队列,该队列用于存储完成握手的连接,此时网络状态为ESTABLISHED
,如果全连接队列满了,那么此时新链接上来会在SYNC_REVD
处等待,等待全连接队列有空的出来,或者超时断开连接。
在linux中如何查看并且修改这2个值呢?
全连接队列的配置内核参数为net.core.somaxconn
,而半连接内核参数为net.ipv4.tcp_max_syn_backlog
,使用sysctl
可以查看并且修改2个值:
sysctl net.core.somaxconn sysctl net.ipv4.tcp_max_syn_backlog
上述结果显示服务器全连接队列为最高为128
,而半连接则为256
。
修改有2种方法:
- 使用
sysctl -w
设置。 - 将该值写入
/etc/sysctl.conf
中,并且使用sysctl -p
使其重新加载。
例如: 修改全连接为1024
,可以有2种方法:
方法1:
sysctl -w net.core.somaxconn=1024
方法2:
echo "net.core.somaxconn=1024" >> /etc/sysctl.conf && sysctl -p
总结
作为运维而言,网络和存储是必须且必要的知识,再结合linux
调优,才能真正成为一名运维,在解决问题的过程中,特别是服务器性能过程中,是非常有意思的。运维不仅只是开关服务器以及背锅,还有其他更深层次有意义的事情等待运维去探索。