【计算机基础】TCP

63 阅读9分钟
mindmap
      TCP
          包结构
          三次握手
          四次挥手
          可靠传输
          滑动窗口
          拥塞控制
          全连接和半连接
          粘包

TCP是在传输层的一种可靠传输协议,是面向连接的全双工通信,其发送内容是基于字节流的;

TCP包结构

image.png 总大小:20B

源端口+目的端口:各占2Byte = 2*8 = 16Bits【short】

序列号seq+确认号ack :各占4B = 32Bits【int】

数据偏移:首部长度,占4bits = 1/2 B: 0000

其单位为4B,当偏移为1111=15时,首部为15*4B = 60B

6个特殊置位:URG(紧急位), ACK,SYN,PSH(推送位), RST(复位位), FIN

窗口字段:2B:允许对方发送的数据量,单位为字节

0000 0000 0000 0000 ,最大为2^16B

TCP连接

TCP连接采用客户端/服务器方式,主动发起连接叫客户机

三次握手

image.png

  • 首先 B 处于 LISTEN(监听)状态,等待客户的连接请求。
  • A 向 B 发送连接请求报文,SYN=1,ACK=0,选择一个初始的序号 x。
  • B 收到连接请求报文,如果同意建立连接,则向 A 发送连接确认报文,SYN=1,ACK=1,确认号为 x+1,同时也选择一个初始的序号 y。【ack报文不携带数据】
  • A 收到 B 的连接确认报文后,还要向 B 发出确认,确认号为 y+1,序号为 x+1。
  • B 收到 A 的确认后,连接建立【可以携带数据,不消耗序号】

Attention:

Server资源在第二次握手分配,Client资源在第三次握手分配,可能导致SYN洪泛攻击;

DDoS(分布式拒绝服务);这个的攻击借助于客户/服务器技术,将多个计算机联合起来作为一个攻击平台,对一个或者是多个目标发动攻击,从而成倍的提高就裁决服务攻击的威力。

如果说攻击端,发送完第一次握手的数据后,然后就"消失"了,那么服务器就会不断的发送第二次握手的数据,可是攻击端的人找不到了。于是,服务器的资源大量被消耗,直到死机为止。

预防常用手段就是优化主机系统设置。比如降低SYN timeout时间,使得主机尽快释放半连接的占用或者采用SYN cookie设置,如果短时间内收到了某个IP的重复SYN请求,我们就认为受到了攻击。我们合理的采用防火墙设置等外部网络也可以进行拦截。

为什么不两次握手就好?

TCP连接的目的有3个:1、双方知道彼此存在;2、双方协定一些参数,比如最开始的序列号seq;3、能够运行实体资源;

2次只达成了1,而第二次服务器给client发送ack包时,不能确定他是否收到,也就不能确定开始的序列号是多少;

四次挥手

image.png

  • A 发送连接释放报文,FIN=1。
  • B 收到之后发出确认,此时 TCP 属于半关闭状态Close_wait,B 能向 A 发送数据但是 A 不能向 B 发送数据。
  • 当 B 不再需要连接时,发送连接释放报文,FIN=1。
  • A 收到后发出确认,进入 TIME-WAIT 状态,等待 2 MSL(最大报文存活时间)后释放连接。
  • B 收到 A 的确认后释放连接。

why 四次挥手?

客户端发送了 FIN 连接释放报文之后,服务器收到了这个报文,就进入了 CLOSE-WAIT 状态。这个状态是为了让服务器端发送还未传送完毕的数据,传送完毕之后,服务器会发送 FIN 连接释放报文。

TIME_WAIT的意义:

MSL: 一个包存在的最大存在时间;

客户端接收到服务 器端的 FIN 报文后进入此状态,此时并不是直接进入 CLOSED 状态,还需要等待一个时间计时器设置的时间 2MSL。这么做有两个理由:

  • 确保最后一个确认报文能够到达。如果 B 没收到 A 发送来的确认报文,那么就会重新发送连接释放请求报文,A 等待一段时间就是为了处理这种情况的发生。
  • 等待一段时间是为了让本连接持续时间内所产生的所有报文都从网络中消失,使得下一个新的连接不会出现旧的连接请求报文。

closewait太多:

close_wait 按照正常操作的话应该很短暂的一个状态,接收到客户端的fin包并且回复客户端ack之后,会继续发送fin包告知客户端关闭关闭连接,之后迁移到Last_ACK状态。但是close_wait过多只能说明没有迁移到Last_ACK,也就是服务端是否发送fin包,只有发送fin包才会发生迁移,所以问题定位在是否发送fin包。fin包的底层实现其实就是调用socket的close方法,这里的问题出在没有执行close方法。说明服务端socket忙于读写

wireshake抓包发现,实际只有三次挥手:

原因:ACK表示确认收到关闭请求,FIN表示希望终止连接,如果当时Server已经不存在未收到的包,则可以关闭连接;

zorrozou.github.io/docs/tcp/wa…

我们发现在这个实际例子中,确实把标准的四次挥手过程中的第2、3个包合并了,简化成了三次挥手。那这里是为什么呢?原因在于,Linux中支持了tcp的延时ack机制改变了这一行为。因为开启了延时ack机制,导致收到第一个fin之后,发送ack的条件不能满足立即发送ack的条件,导致ack的发送被延时了,在延时的过程中,应用如果确认没数据要发,并且也要关闭此连接的情况下,会触发发送fin,这个fin就会和之前的ack合并被发出。

TCP可靠传输

可靠传输:client发送啥,server就接受啥

TCP使用了序号,校验,确认,重传来保证这一目的;

序号seq:

  • 对传输的数据流中的每一个字节都编上一个序号,每次发送序号的值指向当前数据包发送的第一个字节;

确认ack:

  • 接收方希望收到下一个报文段的第一个字节的序号;

累积确认:TCP只确认数据流中至第一个丢失字节为止的字节;比如A->B发送了0-2,3-5,6-7但3-5丢失了,则B的下一个字段一直是3;

重传:

超时+冗余ACK

  • 超时:发送方发送报文段后一个RTT后都没有收到ack包【RTT,Round-Trip-Time:报文段一次往返时间】,使用慢开始开始;

  • 冗余ACK:当数据丢失后,接收方会发送3个希望接收到的下一个数据序列号,发送方进行重传,使用快恢复开始;

TCP滑动窗口

定义:接收方同时可以处理发送方多少数据的包大小;

  • 引入概念:

因为TCP是接收到包就会回复ACK的,所以如果完全串行的传输效率太低,所以引入窗口的概念:

那么有了窗口,就可以指定窗口大小,窗口大小就是指无需等待确认应答,而可以继续发送数据的最大值。

image.png 滑动窗口,分为发送窗口和接收窗口两个;

  • 发送窗口

image.png

#1 是已发送并收到 ACK确认的数据:1~31 字节

#2 是已发送但未收到 ACK确认的数据:32~45 字节

#3 是未发送但总大小在接收方处理范围内(接收方还有空间):46~51字节

#4 是未发送但总大小超过接收方处理范围(接收方没有空间):52字节以后

  • 接收窗口

0

image.png

拥塞控制

接收窗口rwnd:接收方根据缓存表示我现在能接受多少数据

拥塞窗口cwnd:发送发根据网络计算出现在能接受多少数据

最大报文段长度MSS

慢开始门限ssthresh

网络拥塞:发送方超时未收到确认,

流量控制和拥塞控制的区别:

  • 流量控制:设置的是两个缓冲区,一个TCP发送缓冲区,范围为【2048/2k,249856/255Kb】,default = 16384/16Kb, 一个TCP接受缓冲区,范围为【2280,249856/255Kb】,default = 87380/85Kb,主要保证的是传输过程中发送和接收方来不及接受的情况;

  • 拥塞控制:通过几种传输方式来防止过多的数据注入网络,导致网络中路由器或链路过载;

慢开始和拥塞避免

  • 慢开始:

开始时让cwnd=1个MSS,每接收到一个ack,cwnd ^2

一开始指数增长,知道cwnd = ssthresh,随后接收到ack后,cvnd += 1;

  • 拥塞避免:

收到一个ack包,cwnd++;

cwnd=ssthresh: 两者均可

cwnd>ssthresh:停止慢开始,用拥塞控制;

遇到网络拥塞:门限ssthresh /= 2;cwnd= 1开始慢开始

快重传和快恢复:

快重传:当client收到三次来自Server的重复ACK包时,直接进行重传,而不需要等待获得一个RTT;

快恢复:把慢开始门限值ssthresh /= 2,直接从ma新的ssthresh开始进行重新传输;

TCP粘包

什么是粘包?

(1)接收端收到的一个数据包包含了两个数据包的内容;由于接收端不知道数据包的界限,则很难处理;

image.png (2)接收端收到了两个数据包,但都不完整,要么是多出来一块,要么是少了一块;

0

image.png 出现的原因:

1、要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包。

2、待发送数据大于MSS(最大报文长度),TCP在传输前将进行拆包。

3、要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包。

4、接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。

解决方案:

1、发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。

2、发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。

3、可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。