TCP那些事儿

TCP(Transmission Control Protocol),传输控制协议,是一种面向连接的、可靠的、基于字节流的通信协议。TCP是位于OSI网络模型的传输层的协议,用于实现端到端的数据传输。

上面这段文字相信大家早已滚瓜烂熟,但是我们是否真正理解了这段晦涩难懂的文字呢?下面让我们一起来复习下这个基础知识。

TCP协议栈

TCP协议栈如下图所示:

 图1 TCP协议栈

每一层有4个字节(32bit),通常情况下每个TCP报文段(Segment)都会有20字节固定长度的报文头和自定义长度的报文体(数据部分Data)。

每个字段的解释如下:

  1. 源端口号:2字节,范围是0~65535。
  2. 目的端口号:2字节,范围是0~65535。
  3. 序列号:Sequence Number(下面简写成Seq)。因为TCP是面向字节流的,他会将报文都分成一个个字节,给每个字节进行编号,比如一个报文有900个字节组成,那么就会编成1-900个序号,然后分几部分来进行传输。比如第一次传,序列号就是1,传了50个字节, 那么第二次传,序列号就为51,以此类推。所以序列号就是当前TCP包的首字节在(本次交互过程中)所有字节中的位置。
  4. 确认号:Ack(acknowledge Number),确认方Ack=发起方Seq+1,两端配对。如刚说的例子,第一次传了50个字节给对方,对方也会回应你,其中带有确认应答,就是告诉你下一次要传第51个字节来了,所以这个确认应答就是告诉对方要传第多少个字节了。
  5. 首部长度:就是首部的长度。
  6. 保留:预留位,这个保留的位置放的东西是跟控制位类似的。
  7. 控制位:目前有的控制位为6个。URG:紧急,当URG为1时,表明紧急指针字段有效,标识该报文是一个紧急报文,传送到目标主机后,不用排队(插队),优先被应用程序接收处理。ACK:确认,当ACK为1时,确认序号才有效。当ACK为0时,确认序号没用。PSH:推送,当为1时,接收端在遇到此报文时,会减少数据向上交付,本来向应用进程交付数据是要等到一定的缓存大小才发送的,但是遇到它,就不用在等足够多的数据才向上交付,而是让应用进程早点拿到此报文。这个要和紧急分清楚,紧急是插队,但是提交缓存大小的数据不变,这个推送就要排队,但是遇到他的时候,会减少交付的缓存数据,提前交付。RST:复位,报文遇到很严重的差错时,比如TCP连接出错等,会将RST置为1,然后释放连接,全部重新来过。SYN:同步序列号(Synchronize sequence Number),TCP Session的起始编号是OS随机生成的,4个字节。在进行连接的时候,也就是三次握手时用得到,下面会具体讲到,配合ACK一起使用。FIN:终止,在释放连接时,也就是四次挥手时用的。
  8. 窗口:指发送报文段一方的接收窗口大小,用来控制对方发送的数据量(从确认号开始,允许对方发送的数据量)。
  9. 检验和:检验首部和数据这两部分,和UDP一样,需要拿到伪首部中的数据来帮助检测。
  10. 选项:长度可变,最长可达40字节。这里只介绍2种选项。选项一:最大报文段长度MSS(Max Segment Size)。 能够告诉对方TCP每次能够传输的最大数据分段长度(不包括TCP首部),只出现在来SYN报文段中。另一个选项:滑动窗口扩大因子,取值范围是1~14(2幂指数)。如果没有使用选项,那么首部固定是20个字节。
  11. 填充:就是为了让其成为整数个字节。
  12. 数据部分:也就是传输的业务数据,可以是自定义的纯字节流,也可以是遵循某个协议标准的应用层数据,如HTTP,SMTP,FTP等等。

在这些字段当中,对于开发者来说接触最多的就是源端口目的端口以及数据部分。其余字段由内核控制配合使用,大多数情况下不需要我们关心。

常见问题

1、为什么端口号范围是0~65535?(相信很多同学还不知道)

答:因为TCP协议栈中的源端口和目的端口都只有2个字节呀!

2、怎么理解“端到端”通信?

答:TCP指定了进程的通信端口,是“端口”到“端口”之间的通信。

3、“字节流”是什么意思?

答:字节流是按 8 位传输,以字节为单位输入输出数据。而字符流按 16 位传输,以字符为单位输入输出数据。

4、TCP的可靠性?

答:TCP协议的可靠主要依赖三个机制:分包、排序确认、超时重传

1)TCP将数据拆分成多个数据包,给每个包一个“序列号”-Sequence Number,接收方可以根据序号保证数据的顺序;

2)TCP使用首部 “校验和” 字段保证数据的准确性,发送和接收时都要计算校验和,校验和一致则判定数据正常,接收方就响应一个确认(ACK),表示数据已经收到了;

3)如果在规定时间内,发送方没有收到接收方的确认(ACK),则判定数据丢失,重新发送这个数据包,也就是“超时重传”。

5、怎么理解TCP是面向连接的?

答:建立连接时3次握手,断开时4次挥手。

6、TCP连接时发生了什么?三次握手过程?

现在我们应该对3次握手有了更深入的理解:

图2 TCP-3次握手

第1次握手:客户端调用socket接口connect,发起连接,发送SYN消息(控制位SYN=1)和序列号 Seq=J(对应图中 SYN J)给服务端,然后进入SYN_SENT状态;

第2次握手:服务端接收到同步序列号J后,发送响应信息确认号 Ack J+1(即 Ack Number =发起方Seq+1,控制位ACK=1)和同步消息 SYN K,告诉客户端自己已经准备好连接了,然后进入 SYNC_RECV 状态;

第3次握手:客户端收到服务器响应后,发送确认号 Ack K+1,完成握手;此时,服务端进入 ESTABLISHED 状态。

需要注意:

不要将确认序号Ack与标志位中的ACK搞混了。确认方Ack=发起方Seq+1;ACK=1时,Ack才有效。

关于状态:

  • LISTEN:首先服务端需要打开一个socket进行监听,状态为LISTEN。
  • SYN_SENT:客户端通过应用程序调用connect进行active open。于是客户端TCP发送一个SYN以请求建立一个连接,之后状态置为SYN_SENT。
  • SYN_RECV:服务端应发出ACK确认客户端的SYN,同时自己向客户端发送一个SYN,之后状态置为SYN_RECV。
  • ESTABLISHED:代表一个打开的连接,双方可以进行或已经在数据交互了。

7、TCP释放连接时发生了什么?四次挥手?...

图3 TCP-4次挥手

上图中以客户端为关闭的发起端,服务端发起关闭时将方框中的文字对调即可。

由于TCP是全双工通信,两个方向的连接都需要单独关闭,所以需要4次交互。

第1次挥手:客户端程序调用close,向服务端发送FIN K(控制位FIN=1,Seq=K),进入 FIN_WAIT1 状态;

第2次挥手:服务端read收到FIN消息后,响应Ack K+1(控制位ACK=1,Seq=K+1),然后进入 CLOSE_WAIT 状态;此时服务端不再接收客户端消息(单向关闭,但是可以继续发送消息给客户端);

第3次挥手:服务端在发送完所有数据后,发起关闭,向客户端发送 FIN M(控制位FIN=1,Seq=M),然后进入 LAST_ACK 状态;

第4次挥手:客户端收到FIN消息后,发送Ack M+1(控制位ACK=1,Seq=M+1)给服务端,确认关闭服务端到客户端的连接,然后进入TIME_WAIT状态。服务端收到ACK消息后,连接正式关闭。

需要注意:

1)主动关闭端,可能是客户端,也可能是服务端。那么当面试官问你 “TIME_WAIT状态会出现在哪里”时,你可以斩钉截铁的告诉他:出现在主动关闭端。在大多数人的印象中,TIME_WAIT这个状态在服务端会经常看到 ,这是因为服务端主动关闭了一些长时间不活跃的连接,有的时候会产生大量TIME_WAIT,这时候为了保证性能就需要开启“端口重用”(SO_REUSEADDR)了。

2)使用工具抓包的话,大多数情况下你可能只会看到3次挥手。这是因为被动关闭端在接收到FIN包时,本地并没有需要继续发送的数据,这时就会把 ACK 和 FIN 合并到一起发送,节省了一个包,变成了“三次挥手”。

关于状态

  • FIN_WAIT1:主动关闭(active close)端应用程序调用close,于是其TCP发出FIN请求主动关闭连接,之后进入FIN_WAIT1状态;
  • CLOSE_WAIT:被动关闭(passive close)端TCP接到FIN后,就发出ACK以回应FIN请求(它的接收也作为文件结束符传递给上层应用程序),并进入CLOSE_WAIT;
  • FIN_WAIT2:主动关闭端接到ACK后,就进入了 FIN_WAIT2;
  • LAST_ACK:被动关闭端一段时间后,接收到文件结束符的应用程序将调用CLOSE关闭连接。这导致它的TCP也发送一个FIN,等待对方的ACK,就进入了LAST_ACK;
  • TIME_WAIT:在主动关闭端接收到FIN后,TCP就发送ACK包,并进入TIME_WAIT状态;
  • CLOSING:比较少见;
  • CLOSED: 被动关闭端在接受到ACK包后,就进入了closed 状态。连接结束。
  • UNKNOWN:未知的Socket状态。

8、TCP如何进行流量控制?聊聊滑动窗口?

答:流量控制是指发送方根据接收方的接收数据能力(包括接收缓冲区和处理速度等),调整自身的数据发送速率和数据量。可以通过“滑动窗口”来进行流量控制。

如何理解“窗口”这个概念?

  • 发送方窗口,可以理解为发送缓冲区的大小;
  • 接收方窗口(也有人叫“通告窗口”),指接收缓冲区的大小,还有一种叫法是 TCP Window;
  • 而“滑动窗口”,是由发送方维护的窗口,控制发送方发送数据的大小,让接收方来得及接收,这个窗口的大小需要发送方与接收方协商。通信时,接收方告知发送方自己的窗口大小,发送方立刻更改以实现流量控制,可以根据对端情况灵活调整,这就是滑动窗口。

图4 TCP滑动窗口 

滑动窗口的工作原理这里只做简述,后续会出一篇《图解滑动窗口》,本节我们只聊滑动窗口跟TCP协议栈有什么关系。

如“图1 TCP协议栈”,“窗口大小” 在TCP包中占(16bit)2个字节,表示接收缓冲区最大为65535个字节。发送端发送的数据长度会根据接收端反馈的“窗口大小” 进行调整。

注意:

从Linux 2.4开始,内核支持接收缓冲和发送缓冲的动态调整,应用程序不再需要手动设置(SO_RCVBUF 和 SO_SNDBUF)了,所以开发人员在日常工作中几乎接触不到这个概念。

10、“滑动窗口”问题的引申:“滑动窗口”最大是多少?

答:可以通过启用TCP协议栈中的“选项-滑动窗口扩大因子”,来增加窗口大小,使每次交互传输的数据量增加。

具体过程是:

TCP连接初始化时,通信双方使用该选项来协商接收窗口扩大因子。假设TCP头部中的窗口大小为N,窗口扩大因子(位移数)是M,那么TCP报文段的实际接收窗口大小为:N*(2^M)。M的取值范围为1~14。这样的话,窗口最大约为2^16 * 2^14≈1GB,能够满足大部分应用的需求。

可以通过修改内核参数来tcp_window_scaling开启窗口扩大因子(系统级),也可以调用socket-api来设置(应用级)。

9. TCP_NODELAY 选项与控制位 PSH

相信大家都接触过TCP_NODELAY这个参数,作用简单来说就是让应用程序立即处理缓冲区的数据(而不是等到缓冲区满)。结合图1 TCP协议栈,可以知道当我们在编码时通过socket-api设置了这个选项时,就是将TCP包中的控制位PSH置为1了。

结语

关于TCP的常见问题就先聊这么多,协议中其他字段大多数情况下都无需开发者关心。如果你还遇到了其他问题,还请评论区留言,大家一起讨论!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员柒叔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值