TCP/IP

2019/04/01

四层协议

应用层

常见的Telnet(远程登录)、FTP(文件传输)、HTTP、DNS(域名到IP地址的转换)都属于应用层协议和服务。还有Ping(检查网络)使用的是网络层的ICMP协议。通过/etc/services可以看到知名应用层协议使用的端口号。

四层里面只有应用层属于用户空间,运行在用户进程,应用层以下都是在内核空间,需要通过系统调用才能使用。

传输层

常见协议:TCP、UDP、SCTP协议都属于传输层。

TCP协议下面单独介绍

UDP协议

UDP 用户数据报协议,适用于广播和多播(目标是多个主机地址)。

  1. 不可靠传输:不保证数据一定发送到目的地,需要应用程序自己实现数据确认、超时重传等逻辑;
  2. 无连接:通信双方不需要保持长连接;
  3. 基于数据报套接字:每个UPD数据包都有一个长度,接收到必须以该长度一次性读取出该数据包,否则数据将被截断。

SCTP协议

SCTP 流控制传输协议,可靠传输。

网络层

常见的协议:IP、ICMP协议都属于网络层。负责数据包的转发和路由。

IP协议

  1. 提供无状态、无连接、不可靠的服务。
  2. 将数据直接上交给传输层,由TCP自己来处理乱序、重复等处理。
  3. 如果有IP分片,将先执行重组。

    当IP数据报长度超过帧的MTU,将被分片传输。分片可能发生在发送端,也可能发生到中转路由器上。

ICMP协议

主要用于ping命令,检查网络连接,内部也是使用同层的IP协议。

物理层

常见的协议:ARP协议、RARP协议都属于物理层。

数据包

  1. 应用程序数据:内核缓冲区数据;
  2. TCP数据报:TCP头部 + 内核缓冲区数据;

    MSS 最大报文段限制

  3. IP数据报:IP头部 + TCP头部 + 内核缓冲区数据;
  4. 帧:以太网头部 + IP头部 + TCP头部 + 内核缓冲区数据 + 以太网尾部。
    • MTU 帧的最大传输单元,即最多携带多少上层协议数据 1500字节
    • 过长IP数据报可能需要被分片(fragment)传输
    • 帧是最终在物理网络上传输的字节序列
    • 分用:帧到达目的主机后,各层协议逐一解析本层负责的头部数据,最终将数据交给目标应用程序。
    • 使用2字节的类型字段来区分不同的协议

Socket与TCP/IP协议族的关系

定义:数据链路层、网络层、传输层都是在内核中实现的,因此操作系统通过了一组系统调用API供应用程序调用,即Socket。

  • socket是实现tcp/ip协议的接口
  • Linux 内核将TCP/IP等协议通过Socket API的方式提供给用户编程使用。Socket可以支持不同的传输协议,比如TCP或UDP等。

socket的功能

  1. 将应用程序数据从用户缓冲区复制到TCP内核发送缓冲区,以交付内核来发送数据(send函数);或从内核TCP接收缓冲区将数据复制到用户缓冲区,以读取数据。
  2. 应用程序可以通过socket来修改内核中各层协议的某些头部信息或其他数据结构。

socket是一套通用的网络编程接口,不仅可以访问内核的TCP/IP协议,还可以访问其他网络协议。

TCP协议

1. 面向连接的

1.1 通信双方必须先建立TCP连接。

三次握手

  1. 客户端connect主动连接服务端,发送SYN同步分节(不携带数据),告诉服务端发送数据的初始序列号。
  2. 服务端accept接收到SYN后,必须确认ACK客户端的SYN+1,同时也发送一个自己发送数据的初始序列号SYN。
  3. 客户端接收确认,往服务端发送SYN+1。

三次握手中的两个队列

  • syn队列

    队列满直接丢弃后续的请求

  • accept队列

    队列满根据系统设置,0表示直接丢弃;2表示返回RST复位报文段。客户端会收到read timeout或connection reset by peer。

三次握手只会处理SYN,ACK,RST三种类型报文,其他的不会处理,比如FIN报文段。

四次挥手

  1. 客户端主动调用close方法,发送FIN分节到服务端,客户端状态进入FIN_WAIT_1;
  2. 服务端接收到客户端发来的FIN分节,回复ACK分节,服务端状态进入CLOSE_WAIT状态;客户端接收到ACK分节,进入FIN_WAIT_2状态;
  3. 服务端也调用close方法,发送FIN分节给客户端,服务端状态进入LAST_ACK状态;
  4. 客户端接收FIN分节,进入TIME_WAIT状态,并回复ACK分节;服务端接收到ACK分节,进入CLOSE状态,关闭连接。客户端等待2MSL时间后,也进入CLOSE状态。

1.2 并在内核中为该连接维持一些必要的数据结构,比如连接的状态、读写缓冲区,以及诸多定时器等。

  • TCP内核发送缓冲区
  • TCP内核接收缓冲区

1.3 通信结束,双方必须关闭连接以释放这些内核数据。

1.4 握手过程中宕机?

  1. 客户端第一次握手成功,此时服务端宕机(或者服务端不ACK确认,进行第二次握手)?

    客户端等待一定时间,会重传SYN,超过总尝试时间后报错。

  2. 服务端发出第二次握手,客户端不确认?

    超时时间内,重复发送SYN+ACK(默认重发5次,5次后关闭该连接)。

  3. 客户端发出第三次握手,此时服务端宕机(或者服务端没收到)?

    客户端TCP状态为ESTABLISHED完成握手,此时服务端会超时等待(与2情况类似)重发第二次握手SYN+ACK,客户端重新确认第三次握手ACK。

  4. (3.1 第3个问题的延伸)客户端完成三次握手,服务端还未接收到最后一个ACK,此时客户端发送数据报文,服务端能够正常接收吗?

    正常情况下,不能,服务端会返回RST报文段,告知连接异常,重新连接。

TCP fastopen 选项

应用场景:同一个IP连续多次创建TCP连接的复用场景,避免每次都三次握手。只要第一次三次握手成功,后续只要在第一次握手中就携带数据。– 优化握手过程

原理:第一次建立TCP连接时,服务端根据用户IP加密生成cookie,与第二次握手一起返回给客户端。客户端在之后的TCP连接建立过程中,在第一次握手的SYN报文段里携带这个cookie和数据,服务端校验通过后,直接确认数据,不再需要第二/三次握手。

1.5 握手过程中的攻击问题?

  1. 客户端只发第一次握手,不作第三次握手确认?此时情况是怎么样的?

    这叫SYN FLOOD攻击,会把服务端的“半连接队列”占满,导致无法接收其它连接。解决方案:SYN Cookie。

  2. “半连接队列”满了,如何处理?

    直接丢弃。

    • 额外参数1:linux可以配置“半连接队列”的大小 tcp_max_syn_backlog
    • 额外参数2:配置“半连接队列”的ACK等待时间(即等待第三次握手ACK确认的时间)或者SYN+ACK的重发次数 tcp_synack_retries
  3. “已完成连接队列”满了,如何处理?

    直接丢弃或发送RST报文。

1.6 挥手过程中的宕机?

  1. 主动断开连接,发送第一次挥手FIN,FIN丢失?

    状态为FIN_WAIT_1,会重传,超过次数,直接关闭。

  2. 服务端接收到第一次挥手FIN,发送第二次挥手ACK,但ACK包丢失,此时状态以及情况是怎么样的?

    客户端执行1步骤,超时重传FIN,状态还是FIN_WAIT_1;服务端不做处理,状态为CLOSE_WAIT。

  3. 客户端接收到第二次挥手FIN+ACK,此时服务端宕机或者不再发送第三次挥手FIN,客户端是什么情况?

    客户端将一直停留在FIN_WAIT_2状态,直到超时直接关闭。tcp_fin_timeout参数设置这个超时时间,默认60s。

  4. 服务端发出第三次挥手FIN,FIN包丢失,情况是怎么样?

    对于客户端,还是停留在FIN_WAIT_2状态,服务端状态为LAST_ACK,超时重传,超过了直接关闭。

  5. 客户端接收到第三次挥手FIN,发送第四次ACK,包丢失?

    服务端超时重传第三次挥手FIN包,状态为LAST_ACK;客户端状态为TIME_WAIT会等待2MSL时间,再关闭。

1.7 数据交互过程中的宕机?

  1. ACK包会重传吗?

    ACK不会重传,当时发送端会以为包没收到,就会重传丢失包,这样接收端就会再次发送ACK包。

  2. 数据传输过程中,对方突然宕机或则CLOSED掉,此处情况是怎么样的?

    RST报文。

  3. 只要接收到RST复位报文段,连接都是直接关闭(表示连接异常)?

    对,直接关闭。

1.8 TCP/IP 三次握手中的一些Queue

每个TCP维护两条backlog队列,未连接队列(net.ipv4.tcp_max_syn_backlog)和已连接队列(net.core.somaxconn)。

server接收到sny的时候会进入到一个syn queue队列, 当server端最终收到ack时转换到accept queue队列。只有server端执行了accept后才会从这个队列中移除这个连接。这个值的大小是受somaxconn影响的, 因为是取的它们两者的最小值, 所以如果要调大的话必需修改内核的somaxconn值.。

如果应用层不调用accept()函数处理一个连接,或者处理不及时的话,将会导致已连接队列堆满。已连接队列已满的话会导致未连接队列在处理完3次握手之后无法进入已连接队列,最终也导致未连接队列堆满。在服务器看到处于未连接队列中的连接状态为SYN_RECV。

2. 可靠的传输

2.1 数据确认、超时重传

  • 两端必须先建立连接
  • 一端发送数据,对端必须返回一个确认。否则自动重传数据。

    TCP模块为每个TCP报文段都维护一个重传定时器,在报文段第一次发生时启动。

  • 排序 每个字节关联一个序列号 对端根据序列号重新排序
  • 对端收到重复数据,根据序列号判断,重复就直接丢弃
  • 流量控制 RWND 接收通告窗口 任意时刻能够接收多少字节的数据

    接收缓冲区当前可用容量

  • ACK延迟确认机制,一般与Nagle算法一起使用

2.2 超时

测量条件:RTT 动态估算两端的往返时间 round-trip time

2.3 每个连接,TCP管理4个不同的定时器

  1. 重传定时器:等待ACK
  2. 坚持定时器:窗口大小
  3. 保活定时器:keepalive 空闲连接
  4. 2MSL定时器:TIME_WAIT 状态时间

2.4 拥塞控制?

四个部分

  1. 慢启动算法
    • 刚开始发送数据不知道网络的实际情况,需要一种试探的方式平滑地增加CWND的大小。
    • 按照指数级形式扩大
    • 慢启动门限,当CWND超过这个值时进入拥塞避免
  2. 拥塞避免算法
    • 按照线性方式增加 ssthresh 慢启动门限值
  3. 快速重传
    • 定义:不等超时重传定时器,直接补发未确认报文段
    • 重发报文段:由接收方先发起,如果发现报文段是失序的,就会连续发三次重复的ACK;发送方收到三次重复的ACK后,就会立即补发丢失报文段。
  4. 快速恢复
    • 发送方接收到三个重复ACK后,就立即调整拥塞窗口大小。
    • 调整拥塞窗口大小:将拥塞窗口值设置为门限值的一半,然后执行拥塞避免算法,使拥塞窗口线性增加。

几个窗口

  1. SWND 发送窗口
    • 能够连续发生TCP报文段的数量 SMSS(发送者最大段大小 SMSS)
    • 如何判断拥塞发现?
      1. 传输超时,或者TCP重传定时器溢出。仍然使用慢启动和拥塞避免
      2. 接收到重复的确认报文段。使用快速重传和快速恢复
  2. RWND 接收窗口

    接收端能够接收的最大报文长度。

  3. CWND 拥塞窗口

    实际的SWND为RWND和CWND窗口中的较小者

    • CWNS 拥塞窗口是发送方使用的流量控制
    • RWND 接收窗口(通告窗口)是接收方使用的流量控制

2.5 Nagle算法

  1. 延迟ACK 延迟确认
    • 服务端不马上确认上次收到的数据,而是在一段延迟时间后查看本端是否有数据需要发送,如果有,则和确认信息一起发出。
    • 减少发送TCP报文段的数量
  2. Nagle算法

    减少广域网小分组数组,一个tcp连接上最多只能有一个未被确认的未完成的小分组。

3. 基于流服务

  • 基于流的数据没有边界(长度)限制
  • 可以逐个字节向数据流写入/读取数据
  • 全双工字节流

TCP报文头

  • 32位序列号
  • 32位确认号
  • 6位标志位
    1. URG 紧急指针是否有效
    2. ACK 确认号是否有效,表示确认报文段
    3. PSH 接收应用程序应立即将数据取走
    4. RST 要求对方重新建立连接,表示复位报文段

      出现的几种场景以及对应情况如何处理?

      1. 目标(服务端)端口未打开,即无监听;
      2. 配置SO_LINGER参数,直接关闭连接
      3. 半连接状态,发送数据会直接返回RST
    5. SYN 请求建立一个连接,表示同步报文段
    6. FIN 通知对方本方要关闭连接,表示结束报文段
  • 16位窗口大小 流量控制

    接收通告窗口,告诉对方本窗口TCP接收缓冲区还能容纳多少字节的数据。

  • 16位校验和

    由发送端填充,接收端对TCP报文段执行CRC算法校验

TCP状态转移

服务端

  1. CLOSED 状态
  2. 通过listen系统调用,进入LISTEN状态,被动等待客户端连接。一旦接受到客户端连接(同步报文段),就将该连接放入内核的等待队列中。并向该客户的连接发送SYN标志的确认报文段。
  3. 向客户端连接发送SYN标志的确认报文段,进入SYN_RCVD状态
  4. 接收到客户端发回的确认报文段,则进入ESTABLISHED状态。表示双方可以进行双向数据传输。
  5. 当客户端主动关闭连接(close或shutdown系统调用发送结束报文段),服务端返回确认报文段,进入CLOSE_WAIT状态。等待服务端应用程序关闭连接。
  6. 服务端接收到客户端发送的结束报文段,返回确认保报文段后。通常也会立即给客户端发送一个结束报文段来关闭连接。进入LAST_ACK状态。接收到客户端返回的确认报文段,连接彻底关闭。

客户端

  1. 发起connect系统调用,发送同步报文段,进入SYN_SEND状态。

1.1 失败:

1. 目标端口不存在或该端口处于TIME_WAIT状态,服务端返回复位报文段
2. 端口存在,但服务端返回超时
3. connect调用失败,返回到CLOSED状态

1.2 成功: 接收到服务端返回的同步报文段和确认,进入ESTABLISHED状态

  1. 发起关闭连接,发送结束报文段,进入FIN_WAIT_1状态。

2.1 成功:

a1. 接收到服务端返回的确认报文段,进入FIN_WAIT_2状态。此时,服务端处于CLOSE_WAIT状态。该状态下客户端可以继续接收数据,如果客户端直接关闭连接,未等服务端返回结束报文段。则该客户的连接将交由内核来接管,称为孤儿连接(类似孤儿进程)。Linux为了防止过多的孤儿连接,定义了变量来控制孤儿连接数量和孤儿的生存时间。

a2. 服务也关闭连接发送(发送结束报文段),客户端接收返回确认,进入TIME_WAIT状态。
    
    
b1. 直接收到服务端返回的结束报文段,直接进入TIME_WAIT状态。
    
TIME_WAIT状态,客户端进入该状态后会等待一段时间2MSL(报文段最大生存事件),才能完全关闭。可靠地终止TCP连接,保证让迟来的TCP报文段有足够的时间被识别并丢弃。

Socket编程

函数

  1. socket 返回套接字描述符
  2. connect 发起三次握手
  • 客户端没收到SYN确认,返回ETIMEOUT错误
  • 客户端收到RST响应(复位),返回ECONNREFUSED错误(服务端该端口没有进程等待连接)
  • destination unreachable 重发,返回EHOSTUNREACH或ENETUNREACH。
  • connect函数调用后,套接字状态从CLOSED转移到SYN_SENT状态;connect函数调用成功后,转移为ESTABLISHED
  1. bind 绑定IP地址
  2. listen 监听某个端口
  • 套接字状态从CLOSED转移为LISTEN
  • int listen(int sockfd, int backlog) 第二个参数规定套接字排队的最大连接个数-ESTABLISED状态的套接字。SYN_RCVD状态的排队数量由内核参数定义
  • 内核为被监听的套接字维护两个队列

    未完成连接队列

    1. 三次握手过程,套接字状态为SYN_RCVD。
    2. 队列项的留存时间为一个RTT
    3. 队列满,服务端忽略SYN分节,客户端重发。

已完成连接队列

  • 完成三次握手,套接字状态为ESTABLISHED。
  1. accept
  • 从已完成连接队列队头返回下一个已完成连接的套接字。如果队列为空,进程被投入睡眠。
  • 即使连接中的状态已变成CLOSE_WAIT也会返回,accept只是从连接队列取连接而已,不做任何判断。
  1. fork和exec

  2. close 不会立即关闭,但同时关闭读和写

    设置SO_LINGER选项,将会把发送缓冲区的数据发送给客户

  3. shutdown 立即关闭连接
  • 关闭读 直接丢弃接收缓冲区数据
  • 关闭写 将发送缓冲区数据全部发送完
  • 同时关闭读写

以上可能被阻塞的函数有:accept、send、recv、connect。

socket TCP选项参数

  • SO_REUSEADDR 能够立即使用TIME_WAIT状态的地址
  • SO_RCVBUF和SO_SNDBUF 发送和接收缓冲区大小
  • SO_RVCLOWAT和SO_SNDLOWAT 设置发送和接收缓冲区的低水位标记,I/O复用有用到,用来判断socket是否可读或可写
  • SO_LINGER

Post Directory

四层协议
  - 应用层
  - 传输层
  - 网络层
  - 物理层
数据包
Socket与TCP/IP协议族的关系
TCP协议
  - 1. 面向连接的
  - 2. 可靠的传输
  - 3. 基于流服务
  - TCP报文头
  - TCP状态转移
Socket编程
  - 函数
  - socket TCP选项参数