网络|学习一下tcp三次握手

本文涉及的产品
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
简介: 网络|学习一下tcp三次握手

TCP报文结构


我们想要介绍TCP/IP三次握手,需要有个前置条件,我们得补充下相关背景,目前我们所使用的的网络协议为TCP/IP4层协议,从下往上即: 数据链路层、网络层、传输层 以及 应用层。


图示如下:


image.png


在使用网络进行数据发送过程中,会将数据进行封包操作,到了对方机器,会逐步解包。我们使用的TCP协议,则是在传输层上,我们一般将其数据称之为报文段。


在介绍TCP如何通过三次握手建立连接之前,还需要先看看其TCP报文段结构,图示如下:

image.png


TCP报文段由2部分组成,首部 (黄色+绿色)和数据报文(蓝色),其中首部又包含基本首部20字节(上图黄色部分) 和 选项部分(上图绿色部分) 最多40字节。


上述报文段中,

  • 源端口 和 目的端口

这2个没啥特别的意思,就是记录双方端口的,分别是2个字节,所以说,服务器的端口最大为65535,因为这是2的16次方还要减去一个数(这个数为0),所以是65535。


  • 序号 和 确认序号

分别占了4个字节,我们常听见的seqack以及seq_ack等都是指的它两。


  • 首部长度

首部长度,也称之为数据偏移,用来指定首部长度的,如前面所述,报文段固定首部20个字节,还有40个字节的选项部分,如该首部长度为5,则证明首部就20个字节(5*4个字节),该值最多为15,即60个字节。


  • 保留

占了3个字节,现在还没有启用。


  • 状态标志位

URGACKPSHRSTSYNFIN这些都是占用了一个位,用以标志该报文段的状态,这些标志位在启用的时候会置为1,不启用的时候会置为0。其中

  • SYN表示新建连接。
  • FIN表示释放连接。
  • ACK表示确认收到数据。
  • RST表示请求重发。
  • PSH表示紧急数据,需要尽快交付至应用层。
  • URG表示紧急数据,需优先发送。
  • 接收窗口:2个字节,用于告知对方接收窗口的大小,做流量控制使用。
  • 校验和:2个字节,会校验伪首部、首部以及数据三个部分。
  • 紧急指针:2个字节,当URG置为1的时候才有效,该数据会指出有多少字节需要紧急传输。
  • 可选项:最大为40字节,包括窗口缩放、MSSSACK、时间戳等。


以上就是整个tcp报文段的内容了,只是做了一个简单的概述。




tcp如何通过三次握手建立连接


假设主机A和主机B想要建立连接,在实际发送报文数据之前,需要先发送3个空包用于建立连接,建立连接和TCP的状态图示如下:

image.png


首先,在建立握手之前,服务器必须要优先于客户端启动服务,而后客户端才能进行连接。

当客户端第一次发起请求的时候,需将报文段SYN标志位置为1,并且指定一个随机数作为seq,此时客户端的状态为SYN SEND


当服务器收到客户端请求后,需将报文段中的SYNACK标志位置为1,并且指定一个随机数作为seqack_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


抓取之后,结果为:

image.png

其中黄色线条框起来的地方就是三次握手的信息了。


如之前所述,可以清晰的看到,第一次握手信息是客户端发送给http服务器的,即: localhost.46452 > localhost.http


来解释一下请求的第一段报文信息:


  • Flags [S]: 表示报文的SYN标志位为1。
  • seq 532696697:表示seq为: 532696697 。
  • win 43690:表示滑动窗口为43690。
  • options存储的是TCP的选项信息,这是可变长信息,最大为40字节,内容为:
  • mss 65495mss表示每一个报文段所能支持的最大数据长度,这个只在握手的时候会进行协定,所以看后续的报文中,都没有出现mss相关信息。
  • sackOK
  • TS val 3302976138 ecr 0: 时间戳,用于测试报文往返时间。
  • wscale 7: 表示接收窗口
  • length:表示报文数据长度。


其他的,就不一一叙述了。


在结合之前的理论信息,我们可以知道:

当客户端第一次请求握手协议的时候,seq发送的是x2992020176

当服务器收到后,客户端发送上来的x+1作为ack返回,其值为2992020177,并且重新取了一个随机数y,其值为4127097599


当客户的接收到该数据后,向服务器发送了一个ack,并且还携带了seqseq_ack,只不过这里tcpdump给屏蔽了,使用其他抓包软件,例如 Wireshark 就可以正常显示出来。



在linux中如何查看连接状态


之前还介绍了tcp三次握手的状态信息,例如SYNC SENDSYNC REVD应该从哪儿看呢?注意,这个只是一个标准来规定它的状态,而不是存储在报文中,在linux中,可以使用netstatss等命令来查看当前服务器有哪些链接,这些链接分别是什么状态,例如:


ss -a | head -n 1 ; ss -a | grep ^tcp

上述命令表示展示当前机器所有的socket信息,并且使用grep过滤了一下以tcp开头的,我们能够得到如下截图:

image.png


上述截图中会显示当前机器的所有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,最后再遍历数组,就可以得到结果了,我们找台机器看下:

image.png


如果是使用的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


image.png

上述结果显示服务器全连接队列为最高为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调优,才能真正成为一名运维,在解决问题的过程中,特别是服务器性能过程中,是非常有意思的。运维不仅只是开关服务器以及背锅,还有其他更深层次有意义的事情等待运维去探索。




相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
1月前
|
监控 网络协议 Linux
网络学习
网络学习
132 68
|
1天前
|
域名解析 存储 网络协议
TCP套接字【网络】
TCP套接字【网络】
18 10
|
5天前
|
网络协议 网络架构
网络协议介绍与学习
网络协议介绍与学习
18 4
|
5天前
|
网络协议 网络安全 数据安全/隐私保护
网络基础知识学习
如果你打算深入学习网络技术,建议从上述基础知识入手,并逐渐扩展到更高级的主题,如网络编程、网络安全、网络管理等。同时,实践是学习网络技术的关键,可以通过搭建自己的小型网络环境来进行实验和探索。
10 2
|
1月前
|
网络协议 C语言
C语言 网络编程(十三)并发的TCP服务端-以进程完成功能
这段代码实现了一个基于TCP协议的多进程并发服务端和客户端程序。服务端通过创建子进程来处理多个客户端连接,解决了粘包问题,并支持不定长数据传输。客户端则循环发送数据并接收服务端回传的信息,同样处理了粘包问题。程序通过自定义的数据长度前缀确保了数据的完整性和准确性。
|
1月前
|
网络协议 C语言
C语言 网络编程(十一)TCP通信创建流程---服务端
在服务器流程中,新增了绑定IP地址与端口号、建立监听队列及接受连接并创建新文件描述符等步骤。`bind`函数用于绑定IP地址与端口,`listen`函数建立监听队列并设置监听状态,`accept`函数则接受连接请求并创建新的文件描述符用于数据传输。套接字状态包括关闭(CLOSED)、同步发送(SYN-SENT)、同步接收(SYN-RECEIVE)和已建立连接(ESTABLISHED)。示例代码展示了TCP服务端程序如何初始化socket、绑定地址、监听连接请求以及接收和发送数据。
|
1月前
|
网络协议 C语言
C语言 网络编程(十四)并发的TCP服务端-以线程完成功能
这段代码实现了一个基于TCP协议的多线程服务器和客户端程序,服务器端通过为每个客户端创建独立的线程来处理并发请求,解决了粘包问题并支持不定长数据传输。服务器监听在IP地址`172.17.140.183`的`8080`端口上,接收客户端发来的数据,并将接收到的消息添加“-回传”后返回给客户端。客户端则可以循环输入并发送数据,同时接收服务器回传的信息。当输入“exit”时,客户端会结束与服务器的通信并关闭连接。
|
1月前
|
网络协议 C语言
C语言 网络编程(十二)TCP通信创建-粘包
TCP通信中的“粘包”现象指的是由于协议特性,发送方的数据包被拆分并在接收方按序组装,导致多个数据包粘连或单个数据包分割。为避免粘包,可采用定长数据包或先传送数据长度再传送数据的方式。示例代码展示了通过在发送前添加数据长度信息,并在接收时先读取长度后读取数据的具体实现方法。此方案适用于长度不固定的数据传输场景。
|
1月前
|
缓存 网络协议 网络性能优化
C语言 网络编程(二)TCP 协议
TCP(传输控制协议)是一种面向连接、可靠的传输层协议,通过校验和、序列号、确认应答等机制确保数据完整性和可靠性。通信双方需先建立连接,再进行通信,采用三次握手建立连接,四次挥手断开连接。TCP支持任意字节长度的数据传输,具备超时重传、流量控制及拥塞控制机制。三次握手用于同步序列号和确认双方通信能力,四次挥手则确保双方均能完成连接关闭操作,保证数据传输的可靠性。
|
1月前
|
网络协议 C语言
C语言 网络编程(十)TCP通信创建流程---客户端
在TCP通信中,客户端需通过一系列步骤与服务器建立连接并进行数据传输。首先使用 `socket()` 函数创建一个流式套接字,然后通过 `connect()` 函数连接服务器。连接成功后,可以使用 `send()` 和 `recv()` 函数进行数据发送和接收。最后展示了一个完整的客户端示例代码,实现了与服务器的通信过程。
下一篇
无影云桌面