TCP/IP网络协议总结(二)传输层协议之TCP连接的建立与断开

      前面已经介绍了了TCP/IP协议栈,网络数据帧/报/段结构,TCP连接等网络通讯基础知识,这一篇文章我来总结一下TCP协议:
1.TCP的连接的建立
2.TCP三次握手
  握手为什么是3次而不是2次或4次;
  SYN攻击;
3.TCP断开连接,四次挥手;
  TIME_WAIT状态;
 
1.TCP三次握手和四次挥手的过程:
               
               
三次握手的过程:
       1次握手:由客户端主动发起握手请求,将TCP报文头部SYN标志位置为1,随机产生一个序号seq=x,发送第一个SYN包给服务器端,客户端从CLOSE状态进入SYN_SENT状态,等待server确认。
       2次握手:server端收到来自客户端的数据包后,解析知道了客户端要建立TCP连接的请求,server端将TCP报文头部标志位SYN和ACK都置为1,ack=x+1;并且随机产生一个seq=y,并将数据包发给客户端,server端从LISTEN状态进入SYN_RCVD状态。
       3次握手:客户端收到server端返回的确认后,校验ack=x+1值正确,客户端将标志位ACK置为1,ack=y+1,并把数据包发给server, server端收到ACK包后先校验ack=y+1;如果校验成功,那么成功建立TCP连接;
ps:三次握手的数据包被称为握手包,是由操作系统来处理的
 
四次挥手:正常断开一个TCP连接。tcp连接是全双工通信,所以通信双方每一个方向的接收/发送都需要单独进行关闭。   
主动断开TCP连接有可能是server端,也有可能是客户端;正常断开连接是由客户端主动发起的
       1次挥手:客户端向server发送一个FIN包,将TCP报文头部FIN状态位置1,向server发送出FIN包后客户端进入FIN_WAIT1;
       2次挥手:server收到来自客户端发的FIN包,回复客户端,将TCP报文头部ACK状态位置1,server端由ESTABLISHED状态进入CLOSE_WAIT状态,这时如果server有数据可以继续正常发送,只是不再接收数据了。
       3次挥手:当客户端收到ACK包后,客户端从FIN_WAIT1进入到FIN_WAIT2状态。第3次挥手是当server端没有要发送的数据时,将TCP报文头部FIN状态位置1,向客户端发送FIN包,server端从CLOSE_WAIT状态进入到LAST_WAIT状态。
       4次挥手:客户端收到server的FIN包后,将TCP报文头部ACK状态位置为1,向server端发送ACK包,由此client客户端从FIN_WAIT2进入到TIME_WAIT状态。server端收到ACK包的时候,进入到CLOSE状态,server端连接正常关闭,2MSL后,客户端正常进入CLOSE状态,client端连接正常关闭。
只有当客户端主动关闭连接时才会有TIME_WAIT状态。
 
2.TCP连接建立的原理,探索三次握手细节
       TCP连接的建立:(也是三次握手)
1.TCP是面向连接的传输层协议,建立的连接不是物理的,建立的连接是逻辑上的连接,这个过程也就是TCP三次握手。
2.三次握手:服务器端处于listen监听状态,客户端要想和服务器建立连接connect(),首先要发送一个数据包,这个握手的控制数据包由传输控制层产生的,服务器端收到握手包会返回一个syn+ack包,客户端再回复一个ack包,这三个由通信两端内核里的传输控制层产生的数据包被称为三次握手,与应用软件无关。
3.三次握手可以让通信双方都确认自己是通的,然后双方的操作系统内核为各自应用程序开辟资源。
三次握手之后双方内存里开辟资源开始为应用程序提供服务,这时就有上面的连接了。在这里开辟的资源是物理实际存在的,而连接是没有通道的。

用来描述一个连接的状态信息组合{Socket、序列号和滑动窗口大小},这个状态信息组合就可以被称为是一个连接。
       socket: socket五元组{sIP,sport,dIP,dport,protocol}表示网络中唯一的一个网络进程。
       序列号:解决TCP乱序的问题。
       滑动窗口大小:负责通讯中流量控制。
 socket五元组就是通过sIP+sport,dIP+dport还有协议包来标识一个网络通信。协议号就是TCP/UDP,在IP协议头部就会有协议号这个字段,8位协议号是6时标识选择TCP传输层协议,协议号是17时表示选择UDP;
                 
 
Linux操作系统中用来查看TCP连接的状态的命令
> netstat -napt   
 
2.1:握手为什么是3次而不是2次或4次
      1.三次握手可以避免历史连接的初始化;
      TCP的确认机制,TCP建立连接时第二次握手,客户端会收到server的ack_num,如果ack序列号校验ack已超时作废,那么当前收到ack序列号的连接是由于网络环境拥堵等影响的历史连接。如果客户端对server发送的ack序列号校验失败,将TCP报文头部RST标志位置为1发送给server端中止连接。
     2.握手需要三次才能同步通信双方的初始序列号;也和TCP的确认机制有关,用来确认数据报文成功有序的被通信对方收到,而确认机制会导致服务器通信以外的额外开销;
     3.当在syn-ack两次握手建立连接的场景下,客户端主动给server发送SYN报文请求建立连接,当SYN包在网络环境中堵塞时,客户端迟迟收不到ACK包,会超时重传继续向server发送SYN包。同时,在有多个SYN包的情况下,server回复完第一个ACK包后,并不能确定连接是否已经成功建立,server会只要继续收到SYN包都会认为是一个新的连接,这样就会建立多个冗余的无效链接,造成不必要的资源浪费。
 
2.2:SYN攻击
       SYN攻击是在TCP连接的建立三次握手期间,攻击者在短时间内伪造大量不同IP向server发送SYN包,server收到SYN包回复ACK+SYN包,但却无法收到来自客户端的ACK包数据,造成大量半连接状态的fd,SYN半连接队列将会满,导致server无法正常为其他真正的网络客户端提供服务。
 
SYN攻击过程图解:
          正常建立连接的流程详细分析如下:
                    
 
      图一:正常建立连接:
              
 
  图二:由于应用程序处理慢导致accept队列占满:
             
 
  图三:客户端恶意发送SYN包导致的SYN队列占满:
     
 
2.2.1.避免SYN解决方案一:
      很容易想到一个解决方案:通过修改linux内核参数,控制队列的大小,当队列满了的话采取处理措施。
      1.网卡接收数据包的速度大于内核处理的速度时,会有一个队列,可设置其长度最大参数:net.core.netdev_max_backlog;
      2.SYN_RECV状态连接最大数的参数:net.ipv4.tcp_max_syn_backlog
      3.对超出处理能力之外的SYN新连接,返回RST,server丢弃该连接;设置参数:net.ipv4.tcp_abort_on_overflow
 
RST状态位:当建立连接时,通信双方任意一方发现异常,而中止连接;分为两种情况,
  1.客户端发起RST包中止连接,当client发送SYN包请求建立连接seq=87,由于网络堵塞而没有收到server发送的ACK+SYN包,会再次发送新的SYN包seq=117;由于网络环境的影响,旧的SYN包seq=87先到,server发送SYN+ACK包seq=222,ack=88.这个时候client如果收到的AYN+ACK包seq=任意值;ack=118就可以正常建立连接,这样就会导致实际上收到的包校验错误,有client发送RST包中止连接。
  2,服务器端发起RST包中止连接,如上解决SYN攻击的解决方案一中所述,server端对超出处理能力之外的SYN新连接,返回RST,server丢弃该连接;
  
         
 2.2.2.避免SYN攻击的解决方案二
 
  如图四:
     
 
设置linux内核参数:net.ipv4.tcp_syncookies = 1;
当SYN队列被占满时启动cookie,具体处理流程如下:
       1,当 SYN 队列满之后,后续服务器收到 SYN 包,不进入SYN 队列;
       2,计算出一个 cookie 值,再以 SYN + ACK 中的序列号返回客户端;
       3,服务端接收到客户端的应答报文时,服务器会检查这个 ACK 包的合法性。如果合法,直接放入到Accept 队列;
       4,最后应用通过调用 accpet() socket 接口,从 Accept 队列取出的连接。
 
3.TCPl连接中端口号原理
端口号体现在同一台机器开启多个网络进程的场景下:
  mac:数据链路层的地址,用来表示同一链路中不同的机器;
  IP:IP层中的的地址,用来标识TCP/IP网络中互联的主机和路由器;
  端口号:传输层地址;用来识别同一台机器中正在通信的不同的网络进程(即正在通信中的不同应用程序);
 
 如下图为例:
                  
   两个客户端采用web浏览器服务器进行通信,通信中采用TCP传输层协议,http应用层协议。当客户端1:192.168.1.114:23535和192.168.1.107:23535同时访问server端120.94.23.180:80时客户端IP地址成了server端区别客户端网络进程的标识;当客户端2的两个网络进程同时访问server端的80端口时,IP相同,这时server端就根据客户端的端口号来标识区分这两个网络进程。
 
端口号分布:
1.标准固定的端口号:分布范围:0-1023。静态分配。每个应用程序的端口号固定,其他应用程序不可随意使用。比如http 80端口。
2.被正式注册的端口号:分布范围:1024-49151。这个范围内的端口号可用于任意通信中。
3.时序分配端口号:分布范围:49152-65535。客户端与服务器端通信时,客户端无需绑定端口号,而是完全有操作系统随机动态分配端口号。
 
4.TCP断开连接,探索4次挥手细节
     Q:断开连接为什么需要四次挥手:
        TCP是一种面向连接的全双工的通信方式,因此断开连接需要双方分别发送各自的FIN包和ack确认包后,在双方都确认通信对方可读可写都已经关闭才正常断开TCP连接,释放系统资源。三次握手中第二次握手时,server发送ACK+SYN一次发送给客户端,在四次挥手断开连接时FIN和ACK需要分开发送,因此挥手需要四次,其实通信双方协商分别关闭通信状态中自己的读和写。
 
  4.1.TCP的TIME_WAIT状态的作用
  
 一般由cliect主动关闭方,收到server发送的FIN,就会陷入TIME_WAIT状态,保证网络延时的数据包可以被正常的而接收到;TIME_WAIT状态有两个作用:
       1.确保断开连接前的网络上的数据包自然消失;保证下次通信网络中的数据包都是新连接中产生的。
       2.保证了TCP连接正常断开,不影响下一次的连接。
 
TIME_WAIT时长:MSL默认时长是30s;2MSL,默认时长是60s。
       2MSL表示网络中报文从发送到确认丢失的时间长。在TCP断开连接的过程中,client收到FIN包后从发送ACK包开始计时,如果client在TIME_WAIT状态2MSL内,因为网络环境的问题没有传输到server,由于TCP的超时重传机制,server会继续发送FIn包给client,client收到FIn包后发送ACK包,2MSL会被清零重新计时,直到确认server收到ACK包正常断开连接。
 
TCP的TIME_WAIT状态作用一:
       防止这次TCP通信的端口号的数据,因为网络环境的因素导致,下一次在这个端口上建立的连接,接收到历史数据包,产生数据错乱的问题。
 
                       
        如果TIME_WAIT,时间小于2MSL,或者没有TIME_WAIT状态时,如图,seq=781这个数据包因为网络堵塞或其他网络环境因素导致client没有接收成功,下一次三次握手建立连接后通信时,采用了相同的TCP端口进行通信,网络堵塞的数据包seq=781可能被client正常接收,导致此次通信中数据错乱的问题.
                      
 
TCP的TIME_WAIT状态作用二:
        client发送完ACK包,没有TIME_WAIT状态直接进入CLOSE状态,如果ACK丢失,server没有收到ACK包,将一直在LAST_WAIT状态下,无法正常关闭;下一次有client发送新的请求建立连接时,server不具备有处理请求的能力,直接返回RST包,将连接终止丢弃。TIME_WAIT状态等待2MSL后,即时ACK丢弃,server没有正常收到ACK包;由于超时重传机制,server会重新发送FIN包,直到server收到ACK包正常断开连接,释放系统资源。
                      
 
  4.2.TCP的TIME_WAIT状态的优化:
 server维持过多TIME_WAIT状态将要面临的问题:       
       试想在网络高并发的场景下,如果server维持过多的TIME_WAIT这种半断开状态的TCP连接时,会导致内存资源得不到释放,还有端口资源占用的问题,当有限的端口资源(32768~65535,可以通过linux内核参数的设置:net.ipv4.ip_local_port_range 的值)被全部占用时,导致无法创建新的连接。
 
TCP的TIME_WAIT状态的优化有三种方法:
1.通过对linux内核参数的设置打开Linux内核TCP时间戳功能和连接复用功能;
       在client端设置:
       net.ipv4.tcp_timestamps=1;
       net.ipv4.tcp_tw_resue=1;
       在TCP头部打开时间戳功能记录发送端发送数据包时的时间戳和从对端接收该数据包的最新时间戳,一旦发现时间戳过期数据包会被丢弃,有效的解决了2MSL的问题;
       在client端设置net.ipv4.tcp_tw_resue=1;  当client端调用connect();请求建立连接时,内核会随机找一个time_wait状态超过1s的连接给新建立的连接复用。
下面两种方法不推荐使用,只做简单介绍:
2.设置内核参数:net.ipv4.tcp_max_tw_buckets,默认值为18000,限制系统维持TIME_WAIT状态的连接数量,当超过默认值时,系统会将后面的TIME_WAIT连接状态重置。
3.在程序中使用SO_LINGER来设置socket选项,发送RST包,跳过四次挥手直接关闭。
       struct linger so_linger;
       so_linger.l_onoff=1;
       so_linger.l_linger=0;
       setsockopt(s,SOL_SOCKET,SO_LINGER,&so_linger,sizeof(so_linger));
 
  4.3.异常断开连接的检测--保活机制Keepalive
 
TCP断开连接有三种情况:
       1.网络健康,软件崩溃导致连接断开;
       2.正常断开连接,客户端正常退出;
       3.软件正常,网络异常导致连接断开;
保活机制:
       功能:心跳机制用来监测客户端或者server是否正常
       在Linux内核TCP/IP协议栈中自带了心跳机制的功能,其中的内核参数可以根据实际开发的需要进行修改;
       net.ipv4.tcp_keepalive_time=7200
       net.ipv4.tcp_keepalive_intvl=75
 
       net.ipv4.tcp_keepalive_probes=9
 
       限制在一段时间内(2h),如果客户端与服务器端没有任何数据交互,启用心跳机制;对于server来说,给每一个监听的clientfd加一个定时器,在定时器定时内有任何一次数据交互,定时器都会被清零重置。默认参数中超过2h,启动保活机制,server向该client发送试探包,也叫心跳包,是个空包。如果收到响应定时器清零重置,否则每隔75s会再次发送试探,会发9次,如果任意一次有相应定时器重置。9次以后client任然没有任何响应,server主动断开连接,释放系统资源。
 
5.TCP的12种状态迁移图,TCP状态机
                  
 
6.传输层协议之UDP与TCP特点
UDP传输层协议本身不提供复杂的控制机制,是面向无连接的传输层协议;
        如果收到应用层要发送的数据时,立即发送到网络上。单次socket sendto()数据大小受限于UDP头16位数据包长度65535的限制,如果UDP数据包长度超过这个值,sendto()失败,返回值为-1(返回0,表示发送成功)
       
        UDP的特点:如果网络堵塞的场景下,UDP不能进行流量控制和避免网络拥塞的操作;并且传输过程中会出现丢包乱序的问题。如果实际开发中需要以上细节处理,交给UDP的应用层程序去处理。
可靠UDP协议的设计:参考TCP可靠的特性主要是通过序列号,确认应答机制,重发机制,连接管理,滑动窗口控制等机制来实现的。
        
       TCP的特点:面向连接;通讯双向性—TCP是全双工通信协议;支持多连接和多点标识(TCP连接由相连接的一对套接字来表示);可靠的传输层协议;确认机制;流式传输数据流(流式传输;非结构化数据;数据流管理);支持流量控制管理。
TCP是基于字节流传输的传输层协议。
       TCP 是一个工作在传输层的可靠数据传输的服务,它能确保接收端接收的网络包是无损坏、无间隔、非冗余和按序的。
 
如何保证UDP的可靠:
seq序列号:解决网络数据包不乱序的问题;
       在建立连接时由计算机生成的随机数作为其初始值,通过 SYN 包传给接收端主机,每发送一次数据,seq的大小就「累加」一次。
ack应答序号:来解决不丢包的问题;
       指下一次收到的数据的序列号[期望值],发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收。
 
 
posted @ 2020-08-14 00:18  will287248100  阅读(1190)  评论(0编辑  收藏  举报