文章目录
- @[TOC](文章目录)
- 一、端口号
- 1.端口号划分范围
- 2.常用知名端口号
- 二、网络命令
- 1.netstat 命令
- 2.pidof 命令
- 三、UDP协议
- 1.格式
- 2.协议的分离和合并
- 3.特点
- 4.缓冲区
- 四、TCP协议
- 1.格式
- 2.4位的数据偏移
- 3.确认应答机制
- 4.序号与确认序号
- 5.16位窗口
- 6.标志位
- 7.超时重传
- 8.三次握手
- 9.四次挥手
- 10.滑动窗口
- 11.拥塞控制
- 12.延迟应答
- 13.面向字节流
- 14.粘包问题
- 15.长短链接
- 16.异常情况
- 17.理解 listen 的第二个参数
- 五、使用wriseshark分析tcp通信流程
文章目录
- @[TOC](文章目录)
- 一、端口号
- 1.端口号划分范围
- 2.常用知名端口号
- 二、网络命令
- 1.netstat 命令
- 2.pidof 命令
- 三、UDP协议
- 1.格式
- 2.协议的分离和合并
- 3.特点
- 4.缓冲区
- 四、TCP协议
- 1.格式
- 2.4位的数据偏移
- 3.确认应答机制
- 4.序号与确认序号
- 5.16位窗口
- 6.标志位
- 7.超时重传
- 8.三次握手
- 9.四次挥手
- 10.滑动窗口
- 11.拥塞控制
- 12.延迟应答
- 13.面向字节流
- 14.粘包问题
- 15.长短链接
- 16.异常情况
- 17.理解 listen 的第二个参数
- 五、使用wriseshark分析tcp通信流程
一、端口号
端口号是确定一个网络通信必不可少的元素,端口号分为源端口号和目的端口号,它们和源IP地址和目的IP地址以及协议号可以确定一个网络通信.
1.端口号划分范围
0-1024是知名的固定端口号, 协议与端口号是绑定的.HTTP, FTP, SSH等这些广为使用的应用层协议, 他们的端口号都是固定的.
1024 - 65535: 操作系统动态分配的端口号. 客户端程序的端口号, 就是由操作系统从这个范围分配的
2.常用知名端口号
ssh服务器, 使用22端口,ftp服务器, 使用21端口,telnet服务器, 使用23端口,http服务器, 使用80端口,https服务器, 使用443;
在文件/etc/services中可以查看常用协议的固定端口号.
二、网络命令
1.netstat 命令
命令选项,-l:只列出正在监听的网络服务,-a:显示所有条目,不只是用户级的,-n:将可以转化为数字的别名转化为数字,-t:只显示tcp协议的服务,-u: 只显示udp协议的服务,-p显示进程
2.pidof 命令
选项进程名,查询进程名为xxx的进程号.
sshd是shell外壳程序,是一个守护进程,systemd是一个操作系统守护进程,d代表daemon守护进程.
三、UDP协议
1.格式
udp协议是定义与操作系统linux中的,而linux是用c语言写的;udp协议是结构化数据,从语言中结构化数据就是结构体或者位段.所以udp协议不是结构体就是位段
结构体
struct udp_header
{uint16_t src_port;uint16_t dst_port:uint16_t src_ip;uint16_t dst_ip;
}
位段
struct udp_header
{uint32_t src_port: 16;uint32_t dst_port: 16;uint32_t src_ip: 16;uint32_t dst_ip: 16;
}
16位UDP长度, 表示整个数据报(UDP首部+UDP数据)的最大长度,16位表示方法,说明数据报整体最大长度不能超过2^16/64k,超过了就要手动分割数据报有效载荷;如果校验和出错, 就会直接丢弃;
2.协议的分离和合并
假设udp协议存储在一个buffer中,已知报头的大小是固定的8字节,可以将一个char* p跳过8字节指向有效载荷的起始位置,得到有效载荷,然后将p指针指向buffer起始位置强转为struct udp_header类型的指针,就得到了报头部分.
udp协议是按照一个结构体整体来发送和接收的,不存在序列化和反序列化,因为udp的定义不管在那个操作系统上都是相同的.报头的各个部分全部都是按照8字节存储,不存在差别.
3.特点
无连接: 知道对端的IP和端口号就直接进行传输, 不需要建立连接;
不可靠: 没有确认机制, 没有重传机制; 如果因为网络故障该段无法发到对方, UDP协议层也不会给应用层返回任何错误信息;
面向数据报: 不能够灵活的控制读写数据的次数和数量;
4.缓冲区
UDP没有真正意义上的 发送缓冲区. 调用sendto会直接交给内核, 由内核将数据传给网络层协议进行后续的传输动作;
UDP具有接收缓冲区. 但是这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致; 如果缓冲区满了, 再到达的UDP数据就会被丢弃
四、TCP协议
1.格式
2.4位的数据偏移
4位的数据偏移: 表示该TCP头部有多少个32位bit(有多少个4字节); 所以TCP头部最大长度是15 * 4 = 60;报头的保留项部分是变长的,所以报头就有两种情况,一种是没有保留项部分(为0),这时报头位20字节,4位的数据偏移就表示20二进制是0101.如果有保留项,保留项等于报头总长度减去20字节.剩下的就是有效载荷部分.
3.确认应答机制
因为通信长度的增加会造成数据丢包,乱序,重复,效验失败,发送太快/太慢,或者网络故障等问题,所以为了增加通信质量,需要互相确认自己发送给对方的信息是否被有效收到.tcp协议就有了确认应答机制.
客户端与服务器之间通信,客户端发送信息给服务器,服务器确认应答,发送给客户端一个确认收到有效信息的消息,确认应答消息不需要确认是否有效,客户端就以有没有收到来应答消息来判断服务器有没有收到完成有效的信息.同样服务器发送给客户端的消息,也用这种方法确认.
串行的发送信息和确认应答是不高效的,所以发送方和接收方是并行发送的方式进行通信的,发送方多条消息一并发送,接收方也将每条消息都都确认应答并行响应.但是这样做需要确认那一条报文对应那一个确认应答;客户端发送的是含有有效载荷的报文,而确认应答发送的是不含有效载荷的报头.通过报头中的确认序号类确认报文.
4.序号与确认序号
tcp是面向字节流的,即以字节为单位接收和发送数据,tcp是有发送缓冲区和接收缓冲区的,缓冲区可以看成是以一个字节为单位的数组空间,这样每个字节的数据就天然有了编号.序号填写的就是数据的编号,客户端填写序号为已经发送的字节序号,服务端确认应答,在确认序号中填写没有传送数据的起始数据编号.表示之前的数据已经收到,下次传送新的数据.之所以设置两个填写序号的位置,是因为,不光是客户端在发送数据,服务端也在发送数据,客户端在报文中填写自己已发送数据的末尾序号,服务器捎带应答,在报文确认序号中填写客户端未发送数据的起始数据序号.在序号填写服务器端已发送数据末尾序号.发送给客户端.
5.16位窗口
tcp有发送缓冲区和就收缓冲区,数据发送和接收的本质是将用户缓冲区拷贝至传输层缓冲区,然后由操作系统按照自己的策略自动将数据拷贝至对端传输层缓冲区.既然是缓冲区就有大小,有大小就有满的时候,接收方如果接收缓冲区满了就会将后续接收到的数据丢包.这会大大折损传输效率,所以发送端会在报文的16位窗口填写自己的接收缓冲区大小,用来告诉对端自己还能接受多少数据.如果接收端接收缓冲区快满了,发送端就调增发送策略,发送数据的速度变慢一些.这中行为叫做流量控制.双方的窗口大小信息是在三次握手链接的时候交换通知的.
窗口越大代表对端信息吞吐量就越大.当对端的窗口大小为0时,发送端会隔一段意见发送一次不带有效载荷的报头,这叫做窗口探测,接收端会发送相应报文告诉发送端当前的窗口大小.而接收也会在接受缓冲区有空间的时候给发送端发送窗口更新通知,告诉发送端可以发送数据了.
6.标志位
报文是分类型的,有链接报文,通信报文,退出链接时的报文等,不同报文发挥不同的作用.标志位就是用来标志报文类型的.
ACK:Acknowledge response确认应答或者是捎带应答报文.
SYN:连接请求报文,用于三次握手链接时发送的报文.
FIN:finish链接断开请求报文,用于四次挥手时发送的报文.
PSH:当对端接收缓冲区已经满的时候,发送端会发送psh提示接收端应用程序立刻从TCP缓冲区把数据读走.
RST:reset(重置),服务器与客户端三次握手建立链接,首先是客户端发送链接请求,服务器响应客户端,然后客户端再次响应服务器,客户端响应服务器的报文一经发出,客户端就认为链接已经建立成功了,下一步就会发送信息给服务器,但是这个时候服务器如果没有收到客户端的请求就不会建立链接.这时服务器如果收到客户端发来的信息,就判断链接异常,会发送给客户端一个标志了RST的报文,重置链接,客户端会从新发起链接请求.这种情况也会发生在客户端与服务端已经建立链接后,服务器网络中断,服务器会清除掉老的链接.也会造成链接异常.
URG:用来标志紧急指针是否有效.紧急指针是紧急数据的偏移量,紧急数据具只有一个字节.紧急数据不用排队,会直接被服务器相应.send系统调用中的flag设置为MSG_OOB可以发送紧急数据.但是这个功能比较鸡肋,一般服务器设计者会给服务器开两个端口,一个用来通信,一个用来控制服务器.
7.超时重传
可靠性不代表不会发送或者接受报文失败,而是不管成功或者失败都有响应.所以发送端还是会有收不到响应的时候,如果发送端在特定时间内收不到响应就认为报文丢包了.
造成发送端认为报文丢包的情况有两种,一种是接收端接收到了信息,但是接收端给发送端的确认应答丢包了,还有一种情况是接收端没有收到信息.这时发送端会超时重传.如果只是确认应答丢包引起的超时重传,势必会造成报文重复,所以接收端需要对报文去重,去重的依据就是序号对比.相同就删除丢掉的报文即可.
已经发送过的报文可能会重新发送,就意味着报文发送完毕之后不能立即丢弃
等待应答的时间不能太长,太长会影响传输效率,也不能太短,有可能会误判.要始终,linux一般初始时间间隔是500ms,如果500ms有收到应答,下次会设置2*500ms的时间间隔,一次类推一定次数就会重新向服务器发起链接请求.
8.三次握手
链接的过程
connect系统调用的作用就是触发链接请求,由客户端的操作系统发起标志了SYN的报文,服务器操作系统发送标志了SYN+ACK的响应报文,客户端收到响应报文后操作系统给服务器发送标志ACK的响应报文,accept作用是等待链接成功获取文件描述符.
链接的管理
服务端就一个但是一个操作系统可以有多个服务,客户端有任意多个,不管是客户端或者服务器要网络通信都需要创建链接,链接是操作系统创建的,就需要操作系统维护.操作系统以先描述再组织的方式管理链接,所以链接本质上是一个结构体,是以一种合适的数据结构组织起来的数据集合.随意创建链接是有成本的,创建一个链接需要new一个结构体对象,这需要消耗内存空间,对这个对象的维护需要计算,这需要消耗CPU资源.
握手三次的含义
先不管几次握手,如果是偶数次握手服务端在发出给客户端的响应后就认为建立链接成功,操作系统就创建链接,维护链接,而客户端在收到服务端的响应后认为链接成功,这样的方案是有漏洞的,如果链接异常,处理异常的成本就会由服务端的操作系统承担,客户端可以发送海量的SYN报文给服务端,从而造成服务器系统崩溃.
如果是奇数次握手,链接请求由客户端发起的,所以是客户端在将第最后一次给服务器的响应发送出去以后就认为链接成功了.而服务端是在收到客户端的响应时才认为链接建立成功.这样一来链接异常的处理成本就由客户端的操作系统来承担了.
而三次握手是因为,建立链接之前要先确认通信畅通,三次就足够确认通信畅通了,客户端个服务器发送SYN报文,客户端收到服务端发来的SYN+ACK响应报文,客户端就可以确定本端的发送和接收是正常的.服务器在收到客户端发来的SYN报文,服务端就可以证明本端的接收是正常的,再接收到客户端发来的ACK报文就可以确定本端的发送时正常的.
9.四次挥手
断开链接过程
情况一:客户端发起请求,调用close(fd)系统调用,操作系统将客户端连接设置FIN_WAIT1状态,给服务端发送FIN报文,服务端操作系统将本端链接状态设置为CLOSE_WAIT状态,向客户端发送ACK报文,客户端收到报文,操作系统将链接状态设置为FIN_WAIT2状态,服务端调用close(fd),操作系统将链接状态设置为LAST_ACK状态,发送给客户端FIN报文,客户端收到ACK报文,将链接状态设置为TIME_WAIT状态,过2msl的时间就自动关闭连接.服务端收到客户单发来的ACK报文后断开链接.
情况二:服务端直接退出进程,文件描述符声明周期随进程,所以服务端fd直接被关闭,客户端直接断开链接.服务端链接状态被设置为TIME_WAIT,在60秒后断开链接.
TIME_WAIT
TIME_WAIT代表链接等待2msl的时候的断开,msl是max second lifetime最大存在时间的意思,也就是报文还没有到达接收方设备,在网络中停留的最大时间,发送和接收一个报文的时间.linux设置的是30秒2msl就是60秒;在文件/proc/sys/net/ipv4/tcp_fin_timeout文件中可以查看时间.
所以服务端链接在程序退出后依然存在一段时间,如果不想等待,可以用setsockopt套接字属性设置函数,将套接字的属性设置为允许重复使用该端口.
setsockopt函数是一个用于设置套接字选项的系统调用函数。它可以设置套接字的各种属性,如发送缓冲区大小、接收超时、广播等属性。该函数的原型如下:
#include <sys/types.h>
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);其中,参数含义如下:
- sockfd:要设置选项的套接字描述符。
- level:选项所在的协议层。对于套接字而言,通常使用 SOL_SOCKET 表示选项在套接字层。
- optname:要设置的选项名称。
- optval:指向存放选项值的缓冲区的指针。
- optlen:缓冲区长度。下面是常见的选项名称和作用:
- SO_REUSEADDR:允许在绑定端口时重用该端口。通常用于服务器快速重启。
- SO_KEEPALIVE:启用 TCP 的 keepalive 机制,当连接空闲一段时间后,自动发送一个探测包,检测连接是否正常。
- SO_SNDBUF 和 SO_RCVBUF:设置发送和接收缓冲区大小。
- SO_BROADCAST:允许发送广播消息。
- SO_LINGER:设置关闭连接时的行为。如果设置了 SO_LINGER,那么在关闭连接时,将等待套接字发送缓冲区中的数据发送完毕,或者等待指定的时间后强制关闭连接。使用 setsockopt 函数时,需要注意以下几点:
- level 参数表示选项所在的协议层,不同的协议层有不同的选项。对于套接字层,通常使用 SOL_SOCKET,对于 TCP 协议层,可以使用 IPPROTO_TCP。
- optval 参数是一个指向 void 类型的指针,因此需要将选项值的类型转换为 void *。例如,设置 SO_REUSEADDR 选项的代码如下:
int reuse = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (void *)&reuse, sizeof(reuse));
- optlen 参数表示缓冲区的长度,需要根据不同的选项来设置。如果 optval 参数为 NULL,则 optlen 参数应该为 0。
- setsockopt 函数的返回值为 0 表示设置成功,返回 -1 表示设置失败,errno 变量将被设置为相应的错误码。
close(fd)
通信有发送有接收,close是关闭本端的发送和对端的接受,shutdown()调用可以导致与sockfd相关联的套接字上的全双工连接的全部或部分被闭。如果是SHUT_RD,将不允许进一步的接收。如果是SHUT_WR,则不允许进一步传输。如果是SHUT_RDWR,将不允许进一步的接收和传输。
调用close(fd)操作系统向对方发送断开链接的请求,而后断开链接.一般断开链接的请求都是客户端先发起的,所以是客户端先调用的close,然后服务端调用close.如果客户端已经断开链接,服务端不调用close,服务端的链接状态会被置为CLOSE_WAIT状态,一直维护这条连接.而且以后每次创建链接后客户端退出都会多出一个CLOSE_WAIT状态的链接,造成资源泄露.
10.滑动窗口
由来
通信双方的发送和接收如果是串行的,通信效率就显得很低.所以它们发送和接收报文是并发执行的.也就是说不用等到上次发送信息的捎带应答就可以再次发送信息,但是这样就需要将发送缓冲区定义一个可发送范围,而且随着信息的发送,这个窗口还要跟着更新.这就是滑动窗口.
缓冲区划分
发送缓冲区是一个面向字节流的数组,所以滑动窗口本质就是一个数据区间.缓冲区被滑动窗口分为三个区域,滑动窗口左边是已经发送的数据,右边是没有发送的数据,应用程序向缓冲区拷贝数据要在滑动窗口右边插入数据.滑动窗口中的数据是可以发送的数据.
滑动窗口边界更新
滑动窗口的起始位置win_start是对端确认应答报文中的确认序号,结束位置win_end等于win_start+对端的16位窗口大小.所以滑动窗口win_start随着对端确认序号更新,win_end随着对端16位窗口大小更新,不用担心win_end以及win_start越界的问题,因为缓冲区是一个环形队列.
丢包的处理
发生丢包时,有两种处理方案,一种是确认应答丢包了,因为发送和接收报文是并发而且有序的,所以这种情况可以根据后面接收到的确认应答报文排除,不用超时重发报文.如果是数据丢包了,这个数据以及后面数据对应的捎带应答或者确认应答报文的确认序号都是这个数据在发送缓冲区中的起始编号.那么这个数据和后面的数据都会被重发,如果这个数据咋滑动窗口左边就从滑动窗口左边开始发送数据,如果是中间或者是结尾,win_start会更新到丢包数据的位置后,开始从起始位置重发数据.如果发送端主机连续三次收到了同样的应答, 就会将对应的数据重新发送,这种机制被称为 “高速重发控制”(也叫 "快重传").
11.拥塞控制
不管是滑动窗口,超时重传,都是在控制服务端或者客户端内部通信,它们最终都是要通过网络通信,网络中的交换机或者路由器也可能因为负载过大而造成网络拥塞.当发生网络拥塞时,会出现大量丢包的情况,这时候如果tcp只有超时重传或者快重传机制是不够的,因为网络中已经拥塞了大量数据网络中的设备都在使用tcp协议,所有设备都继续没有策略的向网络中输出数据,势必会加重网络拥塞程度.
tcp引入了慢启动机制,增加了拥塞窗口,拥塞窗口初始值是1,意味着一次只能发送一个数据包,根据对方的应答报文探测有没有发生网络拥塞,每收到一个ACK报文,不拥塞的情况下拥塞窗口就翻倍一次.因此拥塞窗口开始的时候增长速度慢,后面增长速度非常快,为了不增长那么快,不能一直单纯的加倍增长,所以又引入一个慢启动的阈值ssthresh,阈值的初始值是窗口最大值,拥塞窗口到达阈值后不再成倍增长,而是成线性增长,直到网络拥塞,拥塞窗口再次为1.实际发送数据的时候,发送的数据包数量等于滑动窗口与拥塞窗口比较的最小值.所以拥塞窗口是动态变化的,变化的过程叫做网络探测,网络探测一直都在进行.
12.延迟应答
如果接收端每次收到数据的时候都及时应答,可能返回的窗口比较小,如果延迟应答时间,在这段时间内,可能应用程序会将接收缓冲区迅速腾出很大的空间,返回的窗口可能更大,从而提高传输效率.当然也不是每个数据包都延迟应答,而是每收到几个数据报就返回一个ACK,当最后没有数据报传输给接收方,达到最大延迟时间,就返回ACK;具体的数量和超时时间, 依操作系统不同也有差异; 一般包的数量取2, 超时时间取200ms
13.面向字节流
创建一个TCP的socket, 同时在内核中创建一个 发送缓冲区 和一个 接收缓冲区;调用write时, 数据会先写入发送缓冲区中;如果发送的字节数太长, 会被拆分成多个TCP的数据包发出;如果发送的字节数太短, 就会先在缓冲区里等待, 等到缓冲区长度差不多了, 或者其他合适的时机发送出去;接收数据的时候, 数据也是从网卡驱动程序到达内核的接收缓冲区;然后应用程序可以调用read从接收缓冲区拿数据;另一方面, TCP的一个连接, 既有发送缓冲区, 也有接收缓冲区, 那么对于这一个连接, 既可以读数据, 也可以写数据. 这个概念叫做 全双工
由于缓冲区的存在, TCP程序的读和写不需要一一匹配, 例如:写100个字节数据时, 可以调用一次write写100个字节, 也可以调用100次write, 每次写一个字节;读100个字节数据时, 也完全不需要考虑写的时候是怎么写的, 既可以一次read 100个字节, 也可以一次read一个字节, 重复100次;
14.粘包问题
TCP是面向字节流的,是无保护消息边界的,应用层看到的是一串连续的字节数据,不知道从什么地方截取是一个完成数据包,这就是粘包问题,UDP则是面向数据报的,是有保护消息边界的,接收方一次只接受一条独立的信息,所以不存在粘包问题。
解决办法:对于定长的包, 保证每次都按固定大小读取即可; 例如上面的Request结构, 是固定大小的, 那么就从缓冲区从头开始按sizeof(Request)依次读取即可;
对于变长的包, 可以在包头的位置, 约定一个包总长度的字段, 从而就知道了包的结束位置;
对于变长的包, 还可以在包和包之间使用明确的分隔符(应用层协议, 是程序猿自己来定的, 只要保证分隔符不和正文冲突即可);
15.长短链接
短连接:请求完一个任务就断开链接,请求另一个资源,需要重新建立链接.长连接;请求完一个任务不断开链接,继续保持客户端与服务端通信,直到链接超时服务器自动断开链接,或者客户端主动断开链接.短连接适用于不频繁请求链接的场景,长连接适用于请求频发的应用场景.
16.异常情况
进程终止: 进程终止会释放文件描述符, 仍然可以发送FIN. 和正常关闭没有什么区别.
机器重启: 和进程终止的情况相同.
机器掉电/网线断开: 接收端认为连接还在, 一旦接收端有写入操作, 接收端发现连接已经不在了, 就会进行reset. 即
使没有写入操作, TCP自己也内置了一个保活定时器, 会定期询问对方是否还在. 如果对方不在, 也会把连接释放.
另外, 应用层的某些协议, 也有一些这样的检测机制. 例如HTTP长连接中, 也会定期检测对方的状态. 例如QQ, 在QQ断线之后, 也会定期尝试重新连接
17.理解 listen 的第二个参数
调用listen系统调用,系统会创建一个全链接队列和一个半链接队列,listen系统调用第二个参数+1就是全链接队列的最大长度.半链接队列中放的是客户端与服务端进行了两次握手的链接,当三次握手后还未被accept取走的链接放在全链接队列中,第三次握手是客户端向服务端发送ACK报文,如果服务端的全链接队列满了,服务端就会丢弃客户端发来的ACK报文.这时服务端链接状态时SYN_RECV,而客户端认为链接建立成功,如果特定时间内,全链接队列还没有位置,半链接队列中的链接会被服务器断开.
全链接队列就是一个容器,客户端生产链接放入队列中,服务端消费链接.
ss 命令可以查看全连接队列的大小和当前等待 accept 的连接个数,执行 ss -lnt 即可。recv-Q是半链接队列,send-Q是全链接队列.
五、使用wriseshark分析tcp通信流程
wireshark是 windows 下的一个网络抓包工具. 虽然 Linux 命令行中有 tcpdump 工具同样能完成抓包, 但是tcpdump 是纯命令行界面, 使用起来不如 wireshark 方便
下载 wireshark
https://1.na.dl.wireshark.org/win64/Wireshark-win64-2.6.3.exe或者链接:https://pan.baidu.com/s/159UUIoZ8b7guWDeuAHoF9A 提取码:k79r
安装 wireshark
启用 telnet 客户端
参考 https://jingyan.baidu.com/article/95c9d20d96ba4aec4f756154.html
启动 wireshark 并设置过滤器
由于机器上的网络数据报可能较多, 我们只需要关注我们需要的. 因此需要设置过滤器
在过滤器栏中写入ip.addr = [服务器 ip]则只抓取指定ip的数据包,或者在过滤器中写入tcp.port =9090则只关注 9090 端口的数据
更多过滤器的设置, 参考
https://blog.csdn.net/donot_worry_be_happy/article/details/80786241
观察三次握手过程,启动好服务器.使用 telnet 作为客户端连接上服务器.抓包结果如下:
观察三个报文各自的序列号和确认序号的规律.在中间部分可以看到 TCP 报文详细信息
观察确认应答
在 telnet 中输入一个字符可以看到客户端发送一个长度为 1 字节的数据, 此时服务器返回了一个 ACK 以及一个 9 个字节的响应(捎带应答), 然后客户端再反馈一个 ACK(注意观察 序列号和确认序号)
观察四次挥手
在 telnet 中输入 ctrl + ], 回到 telnet 控制界面, 输入 quit 退出.
实际上是 “三次挥手”, 由于捎带应答, 导致其中的两次重合在了一起.
注意事项
如果使用虚拟机部署服务器, 建议使用 “桥接网卡” 的方式连接网络. NAT 方式下由于进行了 ip 和 port 的替换.使用云服务器测试, 更加直观方便
。