网络编程(10) : 从connect到三次握手建立连接,再从close到四次挥手断开连接

1、TCP前置知识

1.1什么是TCP

  • TCP 是面向连接的可靠的基于字节流的传输层通信协议。
    • 面向连接:必须是一对一建立连接后才能通信
    • 可靠的:无论网络链路出现怎么样的变化,TCP可以保证报文一定能被对端收到
    • 字节流流式协议,不带边界属性,接收端无法直接确认一组有效的用户信息,且TCP报文是有序的

UPD和TCP的区别

  • TCP要面向连接,UDP不需要连接,即刻传输数据
  • TCP是一对一服务,UDP支持一对一,一对多,多对多的交互通信
  • TCP有拥塞控制、流量控制保证数据传输安全,UDP没有拥塞控制,不限流,即使网络拥堵,也不会影响UDP的发送速率
  • TCP流式协议,没有边界,但保证顺序和可靠,UDP是一个包一个包发的,有边界,但是会丢包和乱序
  • UDP实时性好,适用于游戏领域,TCP延迟ACK

1.2、TCP状态图

网络编程(10) : 从connect到三次握手建立连接,再从close到四次挥手断开连接_第1张图片

  • 三次握手状态:
    • CLOSED:关闭状态,一般在四次挥手之后的状态
    • SYN_SENT: 返送连接请求后,表示已经发送连接请求
    • ESATABLISHED: 客户端收到SYN+ACK、或者服务端收到ACK后,表示已经建立TCP连接,可以开始通信了
    • SYN_RECV: 服务端收到客户端的SYN连接请求后,此时需要向客户端也发起连接请求并响应回复SYN+ACK
    • LISTEN: 服务端开启侦听之后,可以接收客户端发起的连接请求
  • 四次挥手状态:
    • FIN_WAIT1: 客户端发送FIN报文之后,客户端状态
    • CLOSE_WAIT: 服务器接收FIN报文之后,服务器状态
    • FIN_WAIT2: 客户端接收服务器对FIN报文的应答之后客户端状态
    • TIME_WAIT: 客户端接收到服务器FIN报文且做出应答ACK之后,客户端状态
    • LAST_ACK: 服务器发送FIN报文后,服务器状态
    • CLOSED: 服务器接收到客户端ACK报文后的状态,客户端TIEM_WAIT结束后的状态

1.3 TCP报文格式

网络编程(10) : 从connect到三次握手建立连接,再从close到四次挥手断开连接_第2张图片

  • 源端口和目标端口就是从哪个应用发给哪个应用(断开用于标识应用)
  • 序号seq: 用于标识TCP发送出去的字节流,解决包乱序
  • 确认号ack:对已收到包进行确认,ack=seq+1,解决丢包问题
  • 标志为:SYN发起连接、ACK回复确认应答、RSTTCP连接出现异常需要强制断开、FIN表示后面不再发送数据,希望断开连接、URG紧急指针有效、PSH接收方应该尽快将这个报文交给引用层,TCP是面向连接的,双方通过TCP报文修改各自的TCP状态
  • 窗口大小:通信双方要标识窗口大小,表示当前自己能接收多少数据,用于流量控制

2、从connect到三次握手

2.1、从connect到三次握手建立连接

网络编程(10) : 从connect到三次握手建立连接,再从close到四次挥手断开连接_第3张图片

  • 一开始客户端和服务端都处于CLOSED状态。
  • 服务端先要开启侦听,TCP状态从CLOSED变成LISTEN ,可以开始接收连接进行三次挥手

    开启侦听流程: int listenfd = socket() -> bind(listenfd,ip+port) -> listen(listenfd,backlog)

  • 第一次握手:客户端connect()系统调用之后,从用户态切换到内核态,TCP协议栈组织三次握手包,向服务器发送SYN报文连接,客户端从CLOSED状态进入SYN_SENT
  • 第二次握手:服务器收到SYN连接,并返回客户端ACK+SYN回复请求并也发起连接,服务器TCP状态从LISTEN转变到SYN_RECV,在这里会有一个半连接队列(SYN队列),存放着当前客户端和服务器的TCP连接
  • 第三次握手:客户端收到ACK+SYN,并对服务器报文返回ACK,客户端TCP状态从SYN_SENT转变为ESATABLISHED,服务器接收到ACK,TCP状态从SYN_RECV转变为ESATABLISHED,三次握手连接建立完成,服务器这里有个全连接队列(accept队列),将半连接队列中的TCP连接节点加入到全连接队列
  • 客户端处于ESATABLISHED时,connect返回0 表示连接成功,服务器处于ESATABLISHED时,accept全连接队列中取出一个TCP连接节点,并给这个连接节点分配一个clientfd用于和客户端通信

2.2 TIP:

  • accept发生在三次握手完成后,TCP连接节点在全连接队列,accept全连接队列中取出一个TCP连接节点,并给这个连接节点分配一个clientfd用于和客户端通信;

  • listen函数的第二个参数backlog=半连接队列数+全连接队列数,

  • listenbind为什么要单独设计? 接口越单一,后续的可调度性和可重复利用的概率越高

  • bind为什么绑定ip? 一台机器一般有多个网卡,一个网卡对应一个ip

  • 每次标志位携带ACK,就是对接收报文的应答,对应的确认号ack为接收到报文的序列号seq+1,表示自己已经就受到报文

  • 第一次握手丢失:

    • 服务器没有对SYN报文进行应答(可能是服务器没有收到),客户端会进行5-6次SYN(每次重传的seq是一样的),每隔一段时间进行尝试一次,直到超时返回-1,errno为ETIMEOUT,connect连接建立失败
  • 第二次握手丢失:

    • 客户端没有收到ACK,会觉得第一次握手丢失,重传SYN
    • 客户端不会对服务器的SYN+ACK报文进行应答,服务端收不到三次握手,服务端也会每隔一段重发SYN+ACK(重复五次),并且等待一段时间(SYN Timeout)之后丢弃这个没有完成的连接(断开连接)
  • 第三次握手丢失:

    • 客户端发起第三次握手,进入ESATABLISHED状态
    • 服务端接受不到第三次握手,会触发重传机制,直到收到第三次握手,或者是超时断开连接
    • 就算第三次握手的ACK丢掉了,只要客户端发送数据,服务器就会进入ESATABLISHED状态, 因为数据包中的ack和第三次握手的ack相同都是对第二次握手的确认
  • SYN Flood攻击: 大量客户端没有回应ack导致 半连接队列中的TCP连接溢出, 后续建立连接的请求会被内核丢弃

  • 如果避免SYN Flood攻击

    • 增大TCP半连接队列;
    • 开启tcp_syncookies:可以在不使用syn半连接队列的情况下成功建立连接,当syn半连接队列满了之后,后续的SYN包不会丢失,会根据算法计算出一个cookie值,cookie值会放到第二次握手的报文里面,服务器在收到ACK的时候会检查包的合法性,如果合法就放到accept全连接队列中
    • 减少SYN+ACK重传次数:加快断开处于SYN_RECV状态的TCP连接
  • 前两次握手不可以携带数据,第三次握手可以携带数据,因为客户端握手后,就进入了ESATABLISHED状态

  • 为什么是三次握手:

    • 为了防止旧的重复连接初始化造成混乱,客户端泵机,网络拥塞,导致第一个SYN报文没有送到, 客户端重启会重传SYN报文,但是拥塞的SYN报文会先到,而每次SYN报文序列号不一样,服务端对旧 SYN回复SYN+ACK, 客户端会直接回复RST, 服务端收到RST会释放旧连接,客户端只会对新的SYN+ACK报文进行ACK, 服务端收到ACK双方连接建立成功
    • 为什么两次握手无法解决历史SYN问题,服务端对旧连接的建立会浪费资源:如果服务端在一次握手的时候进入ESATABLISHED状态,说明连接建立(分配socket文件句柄), 服务端可以对客户端收发数据,但是服务端不知道这个是历史连接,在收到客户端会回复的RST前,服务端可以发送数据,从而导致资源浪费, 服务端收到RST才知道是历史连接从而断开连接(释放sock文件句柄,资源浪费),如果多次由于网络阻塞导致的历史SYN, 会导致服务器资源严重浪费
    • 三次握手可以保证双方序列号同步,服务端ACK确认客户端序列号,客户端ACK确认服务端序列号,而序列号可以保证TCP传输有序性的关键
    • 三次握手就可以建立可靠的连接,四次握手没有必要

2、从close到四次挥手

2.1 前置关于close和shutdown

  • close
    • 减少socket文件描述符的引用,当引用为0的时候,就会关闭连接(socket读写通道,并触发四次挥手发送FIN报文),收回socket文件句柄
  • shutdown
    • 可以只关闭socket读通道(SHUT_RD),或者只关闭写通道(SHUT_WR),或者同时关闭读写通道(SHUT_RDWR),调用完shutdown之后还需要调用close关闭socket文件句柄,但是shutdown没有资源计数的概念,会直接影响所有的sockfd
    • shutdown(sd, SHUT_WR)关闭socket写通道,触发四次挥手,发送FIN报文给对端,告诉对端我不再发数据了,但是可以读;
    • shutdown(sd, SHUT_RD):关闭socket读通道,TCP协议栈不给对端发送网络消息,本端进入read操作会返回错误
    • shutdown(sd, SHUT_RDWR): 同时关闭读写通道,也会触发四次挥手,TCP发送FIN报文

2.2 从close到四次挥手断开连接

四次挥手的客户端和服务器是相对的,先断开连接,谁就是客户端
网络编程(10) : 从connect到三次握手建立连接,再从close到四次挥手断开连接_第4张图片

  • 前提socket文件描述符的引用只有1,客户端close, 关闭连接, 回收句柄资源,触发TCP四次挥手
  • 第一次挥手:客户端调用close触发四次挥手,TCP协议栈发送FIN报文, 告诉服务端没有数据要发了,客户端进入FIN_WAIT1状态,等待服务端应答
  • 第二次挥手:服务端接收到客户端的FIN报文,向客户端发送ACK报文应答,进入CLOSE_WAIT状态,TCP协议栈会给FIN报文插入EOF文件结束符,触发读事件,服务器read的时候会返回0,从而知道对端关闭连接,客户端收到ACK报文,进入FIN_WAIT2状态,等待服务端第三次挥手(FIN报文)
  • 第三次挥手:服务器调用close(),也组织FIN报文发给客户端,服务器进入LAST_ACK等待客户端的最后的挥手ACK
  • 第四次握手:客户端收到服务器的FIN报文,发送最后挥手应答ACK,进入TIME_WAIT状态,服务器接收到ACK进入到CLOSED状态,服务端已经完成连接关闭,客户端需要等待2MSL进入CLOSED状态,这样客户端也完成连接关闭

2.3 TIP

  • ACK是TCP协议栈回复出去的,close是应用程序调用的,close的调用权在应用层

  • 为什么要四次挥手:

    • 客户端向服务器发送FIN报文,表示的是不在发送但是还可以接收数据
    • 服务器回复ACK之后可能还需要处理数据和发送,等服务器不需要发送的时候,才给客户端发送FIN指令,表示同意关关闭连接
    • 由于服务端可能还需要等待处理没有完成的数据,以至于服务端回复的ACKFIN是分开的,因此需要四次挥手
  • 四次挥手可以是三次嘛?:

    • close粗暴关闭:客户端读写对端全部关闭,服务端发送发ACK之后,还想发送数据,客户端协议栈会发送RST报文,服务器会直接关闭连接
    • shutdown优雅关闭:会经历完整的四次挥手,只关闭写,不关闭读,服务器在CLOSE_WAIT状态还是可以给对端发送数据的,客户端收到了会回复ACK, 当服务器数据处理完成没有要发的,就会调用close关闭连接挥手资源,发送FIN报文
    • TCP延迟确认机制(默认开启)会导致第二次握手和第三次握手合并,从而出现三次握手
  • 第一次挥手丢失:

    • 第一次挥手客户端向服务器发送FIN报文,并进入FIN_WAIT1状态,如果客户端收不到服务器的第二次挥手ACK, 客户端会重传FIN报文,每隔一段事件重传一次,超时没有收到ACK,客户端直接进入CLOSED状态
  • 第二次挥手丢失:

    • 服务器发送ACK, 进入CLOSE_WAIT状态,服务器发送的ACK丢失, 客户端收不到ACK会每隔段不停重传FIN,超时客户端直接进入CLOSED状态
  • 第三次挥手丢失:

    • 对于服务器而言:
      • 服务器发送FIN,进入LAST_ACK, FIN丢失,服务器收不到ACK, 服务器就会重发,超时断开连接
    • 对于客户端而言:
      • close状态下:客户端收到ACK之后进入FIN_WAIT2状态,这个状态只会维持60s, 超时没有等到服务器的FIN, 客户端直接关闭连接进入CLOSED状态
      • shutdown只关闭写通道状态下:还是可以读数据的,客户端会一直处于FIN_WAIT2状态死等服务器的FIN报文
  • 第四次挥手丢失:

    • 客户端发送ACK,进入TIME_WAIT状态, 如果服务器没有收到ACK, 依旧处于LAST_ACK状态,会重发FIN报文,如果四次挥手一直丢失,则服务器超过重传次数,就会直接关闭连接进入CLOSED状态,客户端在维持TIME_WAIT状态消失后也会进入CLOSED状态
    • 客户端的TIME_WAIT状态就是为了防止ACK丢失
  • TIME_WAIT状态:

    • 处于TIME_WAIT的客户端每次收到FIN指令都会重新发送ACK并重新计时
    • 可以确保服务端,正确的关闭,也就是防止ACK丢失
    • 防止旧连接数据,被新连接接收,新旧连接在相同端口
    • 缺点: 资源被占用、端口被占用
  • 出现大量close_wait

    • CLOSE_WAIT处于服务端返送完ACK之后到close之前,主要问题是没有及时close,可能是由于业务逻辑写的不对,close前面存在耗时操作
  • 出现大量TIME_WAIT

    • 服务端主动断开很多TCP连接才会出现大量TIME_WAIT
    • 一般出现在,大量HTTP短链接,服务器主动断开连接

参考文章:

https://xiaolincoding.com/network/3_tcp/tcp_interview.html#tcp-%E5%A4%B4%E6%A0%BC%E5%BC%8F%E6%9C%89%E5%93%AA%E4%BA%9B

你可能感兴趣的:(网络编程,网络)