初识TCP

Posted 无赖H4

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了初识TCP相关的知识,希望对你有一定的参考价值。

TCP -- Transmission Control Protocol


TCP协议和UDP协议是运行在传输层的协议,也是为了结局进程间通信的问题,它相对于UDP协议而言,是一种有连接的,可靠的协议。

可靠性

为什么需要可靠性?
因为网络的不可靠,而应用层很多时候需要可靠的传输(核心)

什么是可靠性?

  • 尽可能的吧数据发送给对方,不丢包
  • 如果实在发送不到,至少通知应用层这个错误
  • 保证数据的有序到达
  • TCP保证不会收的错误的数据(只能保证无意识的错误,不能防范有意识的篡改)

TCP报头

确认应答(ACK)机制

简单理解,可以理解为TCP发送的数据是有编号的,当接收方收到数据后,有责任进行应答,将期待下一次收到的数据的第一个编号发送回来
例:


TCP(Segment)既可以当发送使用,也可以当应答使用,互相不冲突
标志位上的ACK就是含有应答的意义(==1是应答,==0不是应答,ASN无效)

超时重传

如果长时间未收到应答,可能发生什么?

  • 数据包可能丢了,对方没有收到。
  • 对方应答了,但应答包丢失

如果超过一定的时间,仍未收到对应应答,则将对应的数据重新进行发送

如果是应答包丢失的情况下,再次发送数据过去,接收方是否可以判断?
可以,TCP内部管理着接收数据的序号,TCP段带有序号
接下来,接收端会将数据丢弃,然后发送应答

如果超时重传后还是没有收到应答怎么办?
多次尝试(有限次数)
1、多次尝试(retry)过程中,超时时间往往越来越长
2、当多次尝试不成功,达到一定阈值时,就会停止发送,同时还会发一个Segment的充值链接,同时通知应用层,(java中会收到一个异常——SocketException:reset by remote peer)

数据编号 + 确认应答 + 超时重传 可以使得数据有序

连接管理机制

为什么需要建立连接?至少能证明对方是在线的。

在正常情况下,TCP都要经过三次握手建立连接,四次挥手断开连接
有了连接管理后,就只能一对一通信了,没办法广播了。

TCP状态转移图

三次握手

SYN是用作TCP段同步的意义
首先由主动连接方发起一个 SYN,别连接方收到会回复一个ACK,然后被连接方也会发送一个SYN,连接方收到回复ACK
如图:

也可以理解为:

第一次握手: A给B打电话说,你可以听到我说话吗?
第二次握手: B收到了A的信息,然后对A说: 我可以听得到你说话啊,你能听得到我说话吗?
第三次握手: A收到了B的信息,然后说可以的,我要给你发信息啦!
 在三次握手之后,A和B都能确定这么一件事: 我说的话,你能听到; 你说的话,我也能听到。 这样,就可以开始正常通信了。
 如果两次,那么B无法确定B的信息A是否能收到,所以如果B先说话,可能后面的A都收不到,会出现问题 。
 如果四次,那么就造成了浪费,因为在三次结束之后,就已经可以保证A可以给B发信息,A可以收到B的信息; B可以给A发信息,B可以收到A的信息。

四次挥手

当需要释放连接的时候,至少由一方主动发起,
主动关闭方发送FIN,被关闭方发送ACK,被关闭方发送FIN,主动关闭方发送ACK


为什么不可以三次挥手呢?

关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了

所以你未必会马上关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。

可能有人会有疑问,tcp我握手的时候为何ACK(确认)和SYN(建立连接)是一起发送。挥手的时候为什么是分开的时候发送呢?

因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。

但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭 SOCKET,所以只能先回复一个ACK报文,告诉Client端,“你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步挥手。

简单理解四次挥手:
A:“喂,我不说了 (FIN)。”A->FIN_WAIT1
B:“我知道了(ACK)。等下,上一句还没说完。Balabala……(传输数据)”B->CLOSE_WAIT | A->FIN_WAIT2
B:”好了,说完了,我也不说了(FIN)。”B->LAST_ACK
A:”我知道了(ACK)。”A->TIME_WAIT | B->CLOSED
A等待2MSL,保证B收到了消息,否则重说一次”我知道了”,A->CLOSED
这样,通过四次挥手,可以把该说的话都说完,并且A和B都知道自己没话说了,对方也没花说了,然后就挂掉电话(断开链接)了 。

TIME_WAIT状态

TIME_WAIT这个状态出现在主动关闭方,并且持续时间是2 *MSL(Maximum Segment Live)
出现的原因:

  • 最后一个ACK可能会丢失,所以需要保持一段时间
  • 把停止使用的五元组闲置一段时间,保证网络上这条连接的数据全部失效,小概率可能性这个五元组又重新分配了,也保证收到数据一定是新连接
  • 2 * MSL的时间,可以保证 : 对方大概率收到了我们的ACK,网络上所有老连接的包已经失效了,即使五元组在分配,也安全了。

作为开发人员,发现服务器上出现了大量的TIME_WAIT,是不是合理的呢?对服务器有没有影响?如果有影响怎么调增代码结构?

理论上说是合理的。
会有一些影响,连接对象一直存在,关于对象关联下的一系列资源都需要保留(内存)
解决方法:避免我们主动关闭链接

CLOSE_WAIT状态。是被关闭方的状态

一般而言,对于服务器上出现大量的CLOSE_WAIT,原因是服务器没有正确的关闭socket导致四次挥手没有正确的完成,只需要加上对应的close解决问题。

流量控制

根据接收方的接受能力进行发送量控制
接收窗口:接收方告诉发送方自己当前的接收能力
发送窗口:控制发送量
滑动窗口:控制发送量过程中的过程、动作

在TCP首部的窗口大小的字段通过ACK机制通知发送端,

在TCP发送缓冲区:
例如:

拥塞控制

根据网络拥塞窗口推断出发送量的大小
根据丢包情况,反推网络拥塞情况


发送量控制 流量控制 + 拥塞控制

TCP细节优化

快重传

数据包不停地向接收方发送(不必等到收到ACK在发送下一个),接收方传递会ACK应答。如果有一个包丢了,那么应答ACK会一直发送下一个应该发送的字节头,当到达一定次数,发送方会重新发送,丢掉的包,收到后返回下一个应该发送的字节头。

延迟应答

可以每N个包应答一次,超过最大的延迟时间就应答一次

捎带应答

非sync包,理论上都可以捎带应答
在延迟应答的基础上我们发现每次发送都是 一发一收,我怕,我们的ACK可以搭顺风车。

面向字节流

TCP超时重传机制 + 发送量控制带来的副作用是:数据只能通过字节流的形式提供给对方
TCP有自己的发送缓冲区和接收缓冲区。

我们要在UDP协议上开发一个可靠的应用层协议,具体怎么做?
具有TCP的特性:
校验和、序列号、确认应答、超时重发、连接管理、流量控制、拥塞控制

TCP异常情况

1、进程终止:进程终止会释放文件描述符,仍然可以发送FIN,和正常关闭一样
2、机器重启:和进程终止一样
机器掉电/网线断开:接收端认为连接还在,一旦接收端有写入操作,接收端发现连接不在了,就会进行reset,TCP自己也内置了一个保活定时器,会定期询问对方是否还在,如果不在也会释放。

  • 只要os有机会介入,os就有能力,将所有的连接进行四次挥手
  • 如果是os没有机会介入的关机,主机B上的进程、连接都没有了
  • 如果处于写数据(发送数据)的状态,当到达充实阈值时,会判断为连接已关闭(异常关闭),通知应用层,SocketException,需要一定时间,不能立即可知
  • 如果处于读数据的状态——确实无法判定
    解决问题思路:
    1、TCP协议层面有一种KeepAlive机制(每隔一段时间,尝试发送个空数据)
    2、应用层方法:read(timeout),如果超时了,尝试写一个数据过去——>心跳包(heartbeat)

以上是关于初识TCP的主要内容,如果未能解决你的问题,请参考以下文章

Arduino 上的模拟读数返回错误值

初识Nodejs

初识Socket通讯编程

《图解HTTP》-读数笔记

QQ聊天原理初识

初识网络协议:HTTP和HTTPS