一、基本结构:
TCP协议报头20个字节,里面可以添加选项
4位首部长度,4个比特位,于是规定4位首部长度基本单位是4字节,不然最多是1111也就是15位。然后为了有20个字节,所以取值范围为20,60换成比特位就是5,15。TCP报头是可以整除4个字节的,4位首部长度最小值0101,所以分离,读取前20个字节读取4位首部长度,转成10进程乘4。

二、确认应答和连接管理机制:
tcp一般的通信过程:发消息,对方应答,保证朝向的可靠性。
具有应答,可保证对历史消息的可靠性
通信中,最新的报文永远没有应答,最新可靠性无法保证。保证TCP可靠性的核心是确认应答机制。
但是发一条回一条效率太低,于是一次发多条,然后对面连续发多条应答。
确认序号=序号+1,报文有自己的序号,指定报文之前的序号确认收到
乱序问题:先发送的不一定先到,通过序号排序解决乱序问题
为什么要有两个序号,一个叫序号一个叫确认序号。因为服务器在回应时也可以发自己的消息,就顺便把应答和要发的消息结合在一起,这叫捎带应答,发送应答也是发送完整的报文。也就是它既要给对方应答,自己的报文也要有序号。
一台主机接收数据的能力由缓冲区决定,就跟吃饭跟胃口大小决定一样。
标志位:报头中的比特位,确认报文类型,建立连接时三次握手,断开连接四次挥手
ACK设为1表示报文是应答报文
建立连接不一定成功
对客户端来说,三次握手在发送最后一次报文就建立好了,但对服务端来说,三次握手得再收到客户端的报文确认才结束。所以三次握手会有时间差,可能会导致客户端认为建立连接了但服务端确不认为已经连接,如果这时候客户端再发送消息,服务端为了建立连接就得发RST标识符告诉客户端进行连接重置
URG紧急指针是否有效
TCP保证可靠性,报文按序到达,是一个入缓冲区的过程,然后先进入的先被操作系统读取,但是有时候有些数据要优先读取优先处理,但这种优先也不是主流
紧急指针,当前报文有效载荷中特定偏移量处有紧急数据。紧急数据只有一个字节,用来设置状态码。
把发送缓冲区想象成一个char类型的数组,那么从上层拷贝数据,就有下标作为天然的序号。
丢包:A向B传数据丢失或者B向A应答丢失。没收到ACK意味着数据发送可能丢失。
发送方没有收到ACK并且超时才认为丢包了
序号的作用:确认应答,按需到达,去重
由于网络是会变化的,所以时间间隔是要变化的,网好间隔要短,网差间隔要长。
连接管理机制:服务器会与多个客户端建立连接,所以要管理连接。先描述再组织。
connect发起三次握手,连接由操作系统自己完成
为什么要三次握手:
1.最短方式验证全双工,本质是验证网络是否通畅支持全双工。
2.以最小成本验证双方通信意愿。
三、TIME_WAIT状态:
1.相关概念:
• TCP协议规定,主动关闭连接的一方要处于TIME_WAIT状态,等待两个MSL(maximumsegment lifetime)的时间后才能回到CLOSED状态。谁主动断开连接谁进入timewait状态等待两个MSL时间才进入close状态关闭。
• 我们使用Ctrl-C终止了server,所以server是主动关闭连接的一方,在TIME_WAIT期间仍然不能再次监听同样的server端口。
• MSL在RFC1122中规定为两分钟,但是各操作系统的实现不同,在Centos7/Ubuntu上默认配置的值是60s。
• 可以通过cat /proc/sys/net/ipv4/tcp_fin_timeout 查看msl的值。
• 规定TIME_WAIT的时间请读者参考UNP2.7节。
断开连接的本质:建立双方断开连接的公式。
如果客户端退出但服务端不关闭就进入closewait状态,一直占用着文件描述符fd没有被释放,导致fd泄漏
衡量一个报文在网络中最大生存时间
2.为什么TIME_WAIT的时间是2MSL:
• MSL是TCP报文的最大生存时间,因此TIME_WAIT 持续存在 2MSL 的话。
• 就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失(否则服务器立刻重启,可能 会收到来自上一个进程的迟到的数据,但是这种数据很可能是错误的)。
• 同时也是在理论上保证最后一个报文可靠到达(假设最后一个ACK丢失,那么服务器会再重发⼀个 FIN,这时虽然客户端的进程不在了,但是TCP连接还在,仍然可以重发LAST_ACK)。
发消息被发送方判定超时但是数据在网络里并没有丢失,客户端会给服务器超时重传,关闭连接时,历史上的这个数据可能还在网络里存在,断开之后下一次重启这个报文数据可能就直接到了,就有可能影响下一次连接的建立和通信的过程。所以MSL可以保证主动断开连接这一方发送的数据在网络里消散。
在timewait期间没有再收到FIN就认为对方收到了ACK。
四、滑动窗口问题:
1.发一条回应一条效率太低,所以可以发多条回应多条,规定一次发多条这个最大值就叫做窗口大小,这个窗口叫滑动窗口,决定了无需确认等待一次发多条的最大值,这个滑动窗口在发送缓冲区里,是发送缓冲区的一部分,定义两个指针作为滑动窗口,窗口前面是已发送已确认的信息,后面是待发送的信息。序号依次增大,滑动窗口未来基本是向右滑动的(宏观),本质是指针的移动。
• 发送前四个段的时候,不需要等待任何ACK,直接发送。
• 收到第一个ACK后,滑动窗口向后移动,继续发送第五个段的数据,依次类推。
• 操作系统内核为了维护这个滑动窗口,需要开辟发送缓冲区来记录当前还有哪些数据没有应答,只有确认应答过的数据,才能从缓冲区删掉。
• 窗口越大,则网络的吞吐率就越高。
• 滑动窗口的本质是流量控制的具体实现方案。
start=报文序号,end=start+win
2.滑动窗口可以变大或变小吗?取决于服务端缓冲区更新的大小。如果缓冲区数据一下全没了就可以变大,同理也可以变小。同时也可以变成0
3.滑动窗口丢包了怎么办?
最左侧报文数据丢失,滑动窗口左侧不变,在信息快重传和超时重传
快重传:
• 当某一段报文段丢失之后,发送端会一直收到1001这样的ACK,就像是在提醒发送端"我想要的是 1001" 一样。
• 如果发送端主机连续三次收到了同样⼀个"1001"这样的应答,就会将对应的数据1001-2000重新发送。
• 这个时候接收端收到了1001之后,再次返回的ACK就是7001了(因为2001-7000)接收端其实之前 就已经收到了,被放到了接收端操作系统内核的接收缓冲区中。
最左侧报文对应的应答消失,滑动窗口正常工作。
必须确认重传,滑动窗口才能向右滑动。
暂存报文:滑动窗口不动
4.滑动窗口会不会溢出:把char类型的数组想象成环形区域。
五、流量控制
接收端处理数据的速度是有限的,如果发送端发的太快,导致接收端的缓冲区被打满,这个时候如果发送端继续发送,就会造成丢包,继而引起丢包重传等等一系列连锁反应。因此TCP支持根据接收端的处理能力,来决定发送端的发送速度,这个机制就叫做流量控制(Flow Control)。
• 接收端将自己可以接收的缓冲区剩余空间大小放入TCP首部中的"窗口大小"字段,通过ACK端通 知发送端。
• 窗口大小字段越大,说明网络的吞吐量越高。
• 接收端一旦发现自己的缓冲区快满了,就会将窗口大小设置成⼀个更小的值通知给发送端。
• 发送端接受到这个窗口之后,就会减慢自己的发送速度。
• 如果接收端缓冲区满了,就会将窗口置为0,这时发送方不再发送数据,但是需要定期发送一个窗口探测数据段,使接收端把窗口大小告诉发送端。
接收端如何把窗口大小告诉发送端呢?回忆我们的TCP首部中,有⼀个16位窗口字段,就是存放了窗口大小信息。那么问题来了,16位数字最大表示65535,那么TCP窗口最大就是65535字节么?实际上,TCP首部40字节选项中还包含了一个窗口扩大因子M,实际窗口大小是窗口字段的值左移M位。
六、拥塞控制:
虽然TCP有了滑动窗口这个大杀器,能够⾼效可靠的发送大量的数据,但是如果在刚开始阶段就发送大量的数据,仍然可能引发问题。因为网络上有很多的计算机,可能当前的网络状态就已经比较拥堵,在不清楚当前网络状态下,贸然发送大量的数据,是很有可能引起雪上加霜的。TCP引入慢启动机制,先发少量的数据,探探路,摸清当前的网络拥堵状态,再决定按照多大的速度传输数据。于是引入拥塞窗口。
拥塞窗口:一个临界值,值以内,网络较大概率不拥塞,值以上拥塞。网络是变化的,所以这个拥塞窗口也会变化
面对网络拥堵,前期慢一点,判定出网络通畅就尽快恢复通信过程,指数增长。
滑动窗口=min(对方接受能力,拥塞窗口),滑动窗口既要考虑对方的接受能力,也要考虑网络。
拥塞窗口不会一直增长,指数级增长到一定程度就会变成线性增长,这个转折点叫做ssthresh。
加法增大本质是为了探测网络新的拥塞窗口的值。
七、延迟应答:
如果接收数据的主机立刻返回ACK应答,这时候返回的窗口可能比较小。
• 假设接收端缓冲区为1M,一次收到了500K的数据。如果立刻应答,返回的窗口就是500K。
• 但实际上可能处理端处理的速度很快,10ms之内就把500K数据从缓冲区消费掉了。
• 在这种情况下,接收端处理还远没有达到自己的极限,即使窗口再放大一些,也能处理过来。
• 如果接收端稍微等一会再应答,比如等待200ms再应答,那么这个时候返回的窗口大小就是1M。
• 数量限制:每隔N个包就应答一次。
• 时间限制:超过最大延迟时间就应答一次,一定要记得,窗口越大,网络吞吐量就越大,传输效率就越高。我们的目标是在保证网络不拥塞的情况下尽量提高传输效率。那么所有的包都可以延迟应答么?肯定也不是,具体的数量和超时时间,依操作系统不同也有差异,⼀般N取2,超时时间取200ms
八、面向字节流:
创建一个TCP的socket,同时在内核中创建一个发送缓冲区和⼀个接收缓冲区。
• 调用write时,数据会先写入发送缓冲区中。
• 如果发送的字节数太长,会被拆分成多个TCP的数据包发出。
• 如果发送的字节数太短,就会先在缓冲区里等待,等到缓冲区长度差不多了,或者其他合适的时机发送出去。
• 接收数据的时候,数据也是从网卡驱动程序到达内核的接收缓冲区。
• 然后应用程序可以调用read从接收缓冲区拿数据。
• 另一方面,TCP的一个连接,既有发送缓冲区,也有接收缓冲区,那么对于这⼀个连接,既可以读数据,也可以写数据,这个概念叫做全双工。由于缓冲区的存在,TCP程序的读和写不需要一一匹配。
例如:
• 写100个字节数据时,可以调用1次write写100个字节,也可以调用100次write,每次写1个字节。
• 读100个字节数据时,也完全不需要考虑写的时候是怎么写的,既可以一次read100个字节,也可以一次read一个字节,重复100次。
九、TCP异常:
进程终止:进程终止会释放文件描述符,仍然可以发送FIN,和正常关闭没有什么区别。
机器重启:和进程终止的情况相同,机器掉电/网线断开,接收端认为连接还在,一旦接收端有写入操作,接收端发现连接已经不在了,就会进行reset,即使没有写入操作,TCP自己也内置了一个保活定时器,会定期询问对方是否还在,如果对方不在,也会把连接释放。
另外,应用层的某些协议,也有一些这样的检测机制。例如HTTP长连接中,也会定期检测对方的状态,例如QQ,在QQ断线之后,也会定期尝试重新连接。
十、TCP小结:
为什么TCP这么复杂?因为要保证可靠性,同时又尽可能的提高性能。
可靠性:
• 校验和
• 序列号(按序到达)
• 确认应答 • 超时重发
• 连接管理
• 流量控制
• 拥塞控制
提高性能:
• 滑动窗口
• 快速重传
• 延迟应答
• 捎带应答
其他:
• 定时器(超时重传定时器,保活定时器,TIME_WAIT定时器等)