当前位置:   article > 正文

TCP/IP协议(二、初识tcp)_tcbenc

tcbenc

tcp是网路协议的重点的重点,也是我们经常说的,这一次就好好的学习分析tcp协议。

2.1 TCP介绍

tcp提供了一种面向连接的的可靠的字节流服务。术语“面向连接的”是指使用TCP的两个应用程序必须在它们可交换数据之前,通过相互联系来建立一个tcp连接。最典型的比喻就是打一个电话,等待另一方接听电话并说“喂”,然后说“找谁”。这个正是一个TCP连接的两个端点在互相通信。(三次握手)

2.1.1 TCP可靠性

  1. tcp必须把一个发送应用程序的字节流转换成一组ip可以携带的分组。这被称为组包。这些分组包含序列号,该序列号在tcp中实际代表了每个分组的第一个字节在整个数据流中的字节偏移,而不是分组号。在接收端那边是可以把这些包重新组合起来,叫重新组包。
  2. tcp维持一个强制的校验和。该校验和涉及它的头部,任何相关应用程序数据和IP头部的所有字段。
  3. tcp会设置一个重传计时器,等待对方的确认接收。tcp不会为每个报文段设置一个不同的重传计时器,想反,发送一个窗口数据,它只设置一个计时器,当ACK到达时再更新超时。如果有一个确认没有及时接收到,这个报文段就会被重传。
  4. 两个方向之间的流量控制。

2.1.2 TCP头部

在这里插入图片描述
tcp的头部,紧跟着IP头部,经常是20字节(不带选项字节的话),我们现在要分析一个tcp的头部。

在这里插入图片描述
看这这个图就比昨天写的udp协议复杂的多,不过确实tcp协议很复杂的,不过怎么复杂,我们今天也要来分析理解。

source port:源端口
destination port:目的端口,源端口和目的端口再加上源ip目的ip,就会对应着一个socket,也就是网络四元组
seq num:序列号,用来标识从TCP源端向TCP目标端发送的数据字节流,它表示在这个报文段中的第一个数据字节。
ack num: 只有ACK标志为1时,确认号字段才有效。它包含目标端所期望收到源端的下一个数据字节。
header len:头部长度,单位是4个字节,这个位是4bit ,最大值为15,所以最大的字节数为15*4=60字节,但是一般都是20字节。
resv:保留位
定义了8位字段:

  1. CWR——拥塞窗口减(发送方降低它的发送速率)
  2. ECG——ENC回显(发送方接收到一个更早的拥塞通告)
  3. URG——紧急(紧急指针字段有效——很少被使用)
  4. ACK——确认(确认号字段有效——连接建立以后一般都是启用状态)
  5. PSH——推送(接收方应尽快给应用程序传送这个数据——没被可靠地实现或用到)
  6. RST——重置连接(连接取消,经常是因为错误)
  7. SYN——用于初始化一个连接的同步序列号
  8. FIN——该报文的发送方已经结束向对方发送数据

windows size:窗口控制,tcp流量控制由这个字段来完成的,也是接收方想要接收的那个字节开始。
TCP checksum:tcp检验和
urgent pointer:紧急指针,只有在URG的时候才有效。
选项字节:以后用到再说

这个tcp头部字段确实复杂,不过更复杂的在后面,继续加油。

2.2 TCP连接管理

tcp是一种面向连接的单播协议。在发送数据之前,通信双方必须在彼此间建立一条连接。

2.2.1 TCP三次握手

终于学习到了三次握手,这个三次握手很多年前都听过了,只是都不知道具体做什么用,直到最近才渐渐明白三次握手的重要性。
一个TCP的连接由一个4元组构成,他们分别是两个IP地址和两个端口号。(这个就对应者我们应用成socket申请的一个fd)。一个tcp的连接有3个阶段:启动,数据传输和退出。所谓的启动就是三次握手,这里我用画图工具自己画一个三次握手,这样才能加深印象。

在这里插入图片描述
大家都开启电脑,画起来,多画才能更好的记得tcp的三次握手,竟然画是都写的挺清楚了,文字描述就不用写那么清楚。
1、由客户端发送一个连接,由于是第一帧报文,所以需要置位tcp报文头的syn位,就是在上图的8个位中,syn位表示着,这是可是通信,在一个报文中,也携带了源ip地址和目的ip地址,源端口号和目的端口号,就是四元组,说了很多遍了。

2、服务接收到这个syn信号后,会把接收到的syn包连接存储到syn队列中,这个队列的职责就是需要给客户端回复一个ack信号,并且等待客户端再次回复ack报文,如果没接收到客户端回复的ACK报文的话,就会启用重传机制。

3、客户端接收到服务器的ack的报文之后,再次回复确认ack,这时服务接收到这个ack信号之后,会在syn对列中查找,只要是查找四元组是否匹配,如果匹配的话,就从syn队列中删除,然后加入到accept队列中。(accept队列的数据等下再讲)

我们是不是发现,发送的包里面的seq和ack都会有+1,不错,要回答上一次请求的话,就需要在上一个的seq的值+1,这样回复一个ack报文。

2.2.2 syn和accept队列的长度是怎么定的呢?

我们在调用listen函数的时候是不是需要传一个参数,不知道大家还记不记得,这个参数就是backog参数,这个backlog参数其实有两个解释,是因为内核版本不同,第一种,就是单纯指定是syn队列的长度,后来好像又改成指定的是syn和accept队列的总长度。

竟然队列都来了,那么队列的长度是多少比较合适:
需要增大队列的大小:

  1. 当建立连接的请求速度确实很大时,即使是对于一个高性能的服务来说,SYN队列也可能需要设置的大一些。
  2. SYN队列的大小,换言之就是等待ACK包的连接数。也即与客户端的平均往返时间越大,堆积在SYN队列中的连接就越多。对于那些大部分客户端都距离服务器很远的场景,比如说往返时间几百毫秒以上,可以将队列大小设置的大一些。
  3. TCP_DEFER_ACCEPT选项如果打开了,会导致socket在SYN-RECV状态下维持更长的时间,也即增大了处于SYN队列中的时间。

但是,将backlog设置的过大也会带来不好的影响:

  1. SYN队列中的每一个槽位都需要占用一些内存。当遇到SYN Flood攻击时,我们没有必要为这些发起攻击的包浪费资源。SYN队列中的inet_request_sock结构体,在4.14内核下,每个将占用256字节的内存。

这几个相是参考了这个博客的,TCP的SYN队列和Accept队列,并且我也是初学,不能了解太多,所以就参考了一个别人的博客。

2.2.3 tcb控制块

是不是大家也好奇这个accept队列中存储的是什么东西,其实里面存储的是一个tcp的控制块叫tcb,这个tcb就是维护者tcp的一个连接,当tcp三次握手成功后,就会把这个tcb存储在accept队列中,等到四次挥手完毕,这个tcb控制块就可以释放了,也就是整个tcp一个连接的生命周期。

如何找到对应的tcb呢?
肯定有很多人说fd,我当初也是认为是fd,但是经过king老师的讲解后,才发现fd是应用层的东西,而现在我们在传输层,那来的fd。所以说用fd来寻找是一个错误,这时候是不是想起一个5元组的东西,不错,这个5元组在网络中是很重要的存在,也一直强调了很多次了,没错,对方发送过来的数据报文中,包含了5元组,然后我方接收到数据之后就是通过这个5元组来找到tcb,再通过tcb中找到应用层的fd,这样应用层才能就关心一个fd,就能完成网络通信了。

简单写一下,tcb的结构:

struct tcb{
   int  sip;   //5元组
 int dip;
short sport;
short dport;
char proto;

unsigned char status; //11中状态,等下讲
int fd;			//这个就是传说中的fd
unsigned char *rbuf;	//接收buf,网络传输是全双工的
unsigned char *sbuf;	//发送buff

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

看到这个数据结构之后是不是就通透了,这个也肯定不是内核代码中的原型,等以后分析内核部分的时候,再做介绍。

2.2.4 send/recv函数

竟然连接都已经建立好了,那剩下来就通信了,我们在应用层编程的时候,是不是就是用send/recv这两个函数,并且都有一个fd.其实send的过程,是通过fd去寻找tcb的控制块,然后通过这个tcp控制块把应用层的数据拷贝到sk_buff中,(这个又是一个内核模块,以后有空再分析),然后这个时候send就可以返回了,所以说send返回0,不代表者数据应该被对方接受到,而是拷贝到内核的队列中,还有一处拷贝,就是把sk_buff中的数据拷贝到网卡驱动里,再由网卡驱动发送。

重点来了,我们通过应用层发送数据,直到数据发送出去, 需要两次拷贝(不考虑应用层的拷贝),先从应用层的数据拷贝到内核空间(sk_buff),再有内核空间拷贝到网卡驱动中,就是两次拷贝。最近有比较火的技术,零拷贝技术,所谓的零拷贝技术不是真正的零拷贝,而是直接把网卡的数据直接往应用层拷贝,跳过内核空间,我们上节课讲的netmap就是零拷贝技术

recv的过程就跟send的过程相反,也是通过两次拷贝才来到应用层,这时候是通过5元组来寻找tcb,找到了tcp就找到了fd,然后就往应用层的buff中拷贝,即可完成整个流程。

2.2.5 四次挥手

上面我们已经介绍了启动和数据传输了,接下来数据都传输完成了,是时候说拜拜了,因为tcp是短连接的,可以做长连接,长连接以后再讲,短连接,是建立连接,然后发送数据,然后就断开连接,这就是短连接。

说到断开连接,就又要说起tcp的四次挥手了。
这个虽然比三次握手的名气小点,但是也要有始有终是吧,竟然连接了,就要断开,改说分手的时候,就要和平分手嘛。

在这里插入图片描述
写的虽然逗比了一点,但是理解就好,为什么挥手需要四次,通过上面的描述就可以看出,因为是涉及到两端通信,需要两边都确认我已经断开了,这样才是真正的断开。

四次挥手的详细步骤就不描述了,看图就懂了,知道大家都是老司机。

2.2.6 tcp的11个状态

tcp的状态图确实有点难,不过可以通过网上找到一个图,然后我们再自己画一个简单一点的状态迁移图

在这里插入图片描述

这玩意一看就头痛,不过是经典的图,所以还是需要拿过来分析分析,不通我们不在这个图分析,我们自己画个图分析:
在这里插入图片描述
画的是丑了点,但是也是经过自己的手了,好记性不如烂笔头,还是要多写写,熟悉熟悉。

1、客户端和服务器同时处在CLOSED状态
2、服务器先行,通过listen函数,切换到SYN_RECV状态,客户端发送一个SYN信号,同时切换成SYN_SEND状态。
3、服务器接收到SYN信号之后,会返回一个ack,这时候客户端的也会返回一个ack,并且把状态切换成ESTABLISHED,就绪状态。
4、服务器接收到客户的ACK之后,也会切换成ESTABLISHED,就绪状态。

5、客户端发起关闭连接的时候,也是发送FIN信号,切换成FIN_WAIT_1状态,
6、服务器接收到客户端的FIN信号之后,会发送一个ACK信号,并切换成CLOSE_WAIT,等待关闭状态
7、这时候客户端接收到服务器的ACK状态之后,会切换成FIN_WAIT_2状态
8、这时候服务器把该发送的数据都发送完了之后,会再次发送一个FIN信号,通过客户端,说我也关闭连接了,并切换成LAST_ACK,等待ACk的状态。
9、客户端接收到FIN信号之后,会切换到TIME_WAIT状态,并给服务器发送ack,TIME_WAIT状态需要等待2ML的时候,这段时间内,不能重新建立这个链接,等到超时,会自动进入CLOSED状态。
10、服务器接收到ACK之后,就切换到CLOSED状态。
11、如果FIN信号比ACK信号先达到客户端,这时候就是CLOSING状态,这时候接收到ACK也会切换到TIME_WAIT状态。

2.2.7 time_wait状态

TIME_WAIT状态也称为2MSL等待状态。在该状态中,TCP将会等待两部于最大段生存期的时间,有时也被称作加倍等待。每个实现必须为最大段生存期选择一个数值。它代表任何报文段在被丢弃前在网络中被允许存在的最长时间。我们知道这个时限是有限制的,因为TCP的报文是以IP数据报的形式传输的,IP数据报用于TTL字段和跳数限制字段,

在linux系统中net.ipv4.tcp_fin_timeout的数据记录了2MSL状态需要等待的超时时间,在windows下,下面注册键值也保存了超时时间:
HKLM\SYSTEM\CurrentControlSer\Services\Tcpip\Parameters\TcpTimeWaitDelay

是不是也有人有这样的疑问,如果最后一个ACK没有送达怎么办?

其实这个time_wait状态就是解决这个问题的,如果最后一个ACK丢失的话,就能让客户端重新传输一个ACK信号,这样就能保证ACK不丢失。重新发送最终ACK并不是因为TCP重传ACK,而是因为通信另一端重传了它的FIN。事实上,TCP总是重传FIN,直到它接收到一个最终的ACK。

time_wait也有不好的影响
如果客户端处于time_wait状态时,通信双方将该连接定义为不可重新使用,只有等2ML结束之后,或一条新连接使用的初始序列号超过了连接之前的实例所使用的最高序列号,或者允许使用时间戳选项来区分之前的连接实例的报文段以避免混淆时,这条连接才能被再次使用。不幸的是,如果一个端口号被处于2ML等待状态的任何通信端所用,那么该端口号将不能被再次使用。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/466251
推荐阅读
相关标签
  

闽ICP备14008679号