四层协议
应用层
常见的Telnet(远程登录)、FTP(文件传输)、HTTP、DNS(域名到IP地址的转换)都属于应用层协议和服务。还有Ping(检查网络)使用的是网络层的ICMP协议。通过/etc/services可以看到知名应用层协议使用的端口号。
四层里面只有应用层属于用户空间,运行在用户进程,应用层以下都是在内核空间,需要通过系统调用才能使用。
传输层
常见协议:TCP、UDP、SCTP协议都属于传输层。
TCP协议下面单独介绍
UDP协议
UDP 用户数据报协议,适用于广播和多播(目标是多个主机地址)。
- 不可靠传输:不保证数据一定发送到目的地,需要应用程序自己实现数据确认、超时重传等逻辑;
- 无连接:通信双方不需要保持长连接;
- 基于数据报套接字:每个UPD数据包都有一个长度,接收到必须以该长度一次性读取出该数据包,否则数据将被截断。
SCTP协议
SCTP 流控制传输协议,可靠传输。
网络层
常见的协议:IP、ICMP协议都属于网络层。负责数据包的转发和路由。
IP协议
- 提供无状态、无连接、不可靠的服务。
- 将数据直接上交给传输层,由TCP自己来处理乱序、重复等处理。
- 如果有IP分片,将先执行重组。
当IP数据报长度超过帧的MTU,将被分片传输。分片可能发生在发送端,也可能发生到中转路由器上。
ICMP协议
主要用于ping命令,检查网络连接,内部也是使用同层的IP协议。
物理层
常见的协议:ARP协议、RARP协议都属于物理层。
数据包
- 应用程序数据:内核缓冲区数据;
- TCP数据报:TCP头部 + 内核缓冲区数据;
MSS 最大报文段限制
- IP数据报:IP头部 + TCP头部 + 内核缓冲区数据;
- 帧:以太网头部 + IP头部 + TCP头部 + 内核缓冲区数据 + 以太网尾部。
- MTU 帧的最大传输单元,即最多携带多少上层协议数据 1500字节
- 过长IP数据报可能需要被分片(fragment)传输
- 帧是最终在物理网络上传输的字节序列
- 分用:帧到达目的主机后,各层协议逐一解析本层负责的头部数据,最终将数据交给目标应用程序。
- 使用2字节的类型字段来区分不同的协议
Socket与TCP/IP协议族的关系
定义:数据链路层、网络层、传输层都是在内核中实现的,因此操作系统通过了一组系统调用API供应用程序调用,即Socket。
- socket是实现tcp/ip协议的接口
- Linux 内核将TCP/IP等协议通过Socket API的方式提供给用户编程使用。Socket可以支持不同的传输协议,比如TCP或UDP等。
socket的功能
- 将应用程序数据从用户缓冲区复制到TCP内核发送缓冲区,以交付内核来发送数据(send函数);或从内核TCP接收缓冲区将数据复制到用户缓冲区,以读取数据。
- 应用程序可以通过socket来修改内核中各层协议的某些头部信息或其他数据结构。
socket是一套通用的网络编程接口,不仅可以访问内核的TCP/IP协议,还可以访问其他网络协议。
TCP协议
1. 面向连接的
1.1 通信双方必须先建立TCP连接。
三次握手
- 客户端connect主动连接服务端,发送SYN同步分节(不携带数据),告诉服务端发送数据的初始序列号。
- 服务端accept接收到SYN后,必须确认ACK客户端的SYN+1,同时也发送一个自己发送数据的初始序列号SYN。
- 客户端接收确认,往服务端发送SYN+1。
三次握手中的两个队列
- syn队列
队列满直接丢弃后续的请求
- accept队列
队列满根据系统设置,0表示直接丢弃;2表示返回RST复位报文段。客户端会收到read timeout或connection reset by peer。
三次握手只会处理SYN,ACK,RST三种类型报文,其他的不会处理,比如FIN报文段。
四次挥手
- 客户端主动调用close方法,发送FIN分节到服务端,客户端状态进入FIN_WAIT_1;
- 服务端接收到客户端发来的FIN分节,回复ACK分节,服务端状态进入CLOSE_WAIT状态;客户端接收到ACK分节,进入FIN_WAIT_2状态;
- 服务端也调用close方法,发送FIN分节给客户端,服务端状态进入LAST_ACK状态;
- 客户端接收FIN分节,进入TIME_WAIT状态,并回复ACK分节;服务端接收到ACK分节,进入CLOSE状态,关闭连接。客户端等待2MSL时间后,也进入CLOSE状态。
1.2 并在内核中为该连接维持一些必要的数据结构,比如连接的状态、读写缓冲区,以及诸多定时器等。
- TCP内核发送缓冲区
- TCP内核接收缓冲区
1.3 通信结束,双方必须关闭连接以释放这些内核数据。
1.4 握手过程中宕机?
- 客户端第一次握手成功,此时服务端宕机(或者服务端不ACK确认,进行第二次握手)?
客户端等待一定时间,会重传SYN,超过总尝试时间后报错。
- 服务端发出第二次握手,客户端不确认?
超时时间内,重复发送SYN+ACK(默认重发5次,5次后关闭该连接)。
- 客户端发出第三次握手,此时服务端宕机(或者服务端没收到)?
客户端TCP状态为ESTABLISHED完成握手,此时服务端会超时等待(与2情况类似)重发第二次握手SYN+ACK,客户端重新确认第三次握手ACK。
- (3.1 第3个问题的延伸)客户端完成三次握手,服务端还未接收到最后一个ACK,此时客户端发送数据报文,服务端能够正常接收吗?
正常情况下,不能,服务端会返回RST报文段,告知连接异常,重新连接。
TCP fastopen 选项
应用场景:同一个IP连续多次创建TCP连接的复用场景,避免每次都三次握手。只要第一次三次握手成功,后续只要在第一次握手中就携带数据。– 优化握手过程
原理:第一次建立TCP连接时,服务端根据用户IP加密生成cookie,与第二次握手一起返回给客户端。客户端在之后的TCP连接建立过程中,在第一次握手的SYN报文段里携带这个cookie和数据,服务端校验通过后,直接确认数据,不再需要第二/三次握手。
1.5 握手过程中的攻击问题?
- 客户端只发第一次握手,不作第三次握手确认?此时情况是怎么样的?
这叫SYN FLOOD攻击,会把服务端的“半连接队列”占满,导致无法接收其它连接。解决方案:SYN Cookie。
- “半连接队列”满了,如何处理?
直接丢弃。
- 额外参数1:linux可以配置“半连接队列”的大小 tcp_max_syn_backlog
- 额外参数2:配置“半连接队列”的ACK等待时间(即等待第三次握手ACK确认的时间)或者SYN+ACK的重发次数 tcp_synack_retries
- “已完成连接队列”满了,如何处理?
直接丢弃或发送RST报文。
1.6 挥手过程中的宕机?
- 主动断开连接,发送第一次挥手FIN,FIN丢失?
状态为FIN_WAIT_1,会重传,超过次数,直接关闭。
- 服务端接收到第一次挥手FIN,发送第二次挥手ACK,但ACK包丢失,此时状态以及情况是怎么样的?
客户端执行1步骤,超时重传FIN,状态还是FIN_WAIT_1;服务端不做处理,状态为CLOSE_WAIT。
- 客户端接收到第二次挥手FIN+ACK,此时服务端宕机或者不再发送第三次挥手FIN,客户端是什么情况?
客户端将一直停留在FIN_WAIT_2状态,直到超时直接关闭。tcp_fin_timeout参数设置这个超时时间,默认60s。
- 服务端发出第三次挥手FIN,FIN包丢失,情况是怎么样?
对于客户端,还是停留在FIN_WAIT_2状态,服务端状态为LAST_ACK,超时重传,超过了直接关闭。
- 客户端接收到第三次挥手FIN,发送第四次ACK,包丢失?
服务端超时重传第三次挥手FIN包,状态为LAST_ACK;客户端状态为TIME_WAIT会等待2MSL时间,再关闭。
1.7 数据交互过程中的宕机?
- ACK包会重传吗?
ACK不会重传,当时发送端会以为包没收到,就会重传丢失包,这样接收端就会再次发送ACK包。
- 数据传输过程中,对方突然宕机或则CLOSED掉,此处情况是怎么样的?
RST报文。
- 只要接收到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个不同的定时器
- 重传定时器:等待ACK
- 坚持定时器:窗口大小
- 保活定时器:keepalive 空闲连接
- 2MSL定时器:TIME_WAIT 状态时间
2.4 拥塞控制?
四个部分
- 慢启动算法
- 刚开始发送数据不知道网络的实际情况,需要一种试探的方式平滑地增加CWND的大小。
- 按照指数级形式扩大
- 慢启动门限,当CWND超过这个值时进入拥塞避免
- 拥塞避免算法
- 按照线性方式增加 ssthresh 慢启动门限值
- 快速重传
- 定义:不等超时重传定时器,直接补发未确认报文段
- 重发报文段:由接收方先发起,如果发现报文段是失序的,就会连续发三次重复的ACK;发送方收到三次重复的ACK后,就会立即补发丢失报文段。
- 快速恢复
- 发送方接收到三个重复ACK后,就立即调整拥塞窗口大小。
- 调整拥塞窗口大小:将拥塞窗口值设置为门限值的一半,然后执行拥塞避免算法,使拥塞窗口线性增加。
几个窗口
- SWND 发送窗口
- 能够连续发生TCP报文段的数量 SMSS(发送者最大段大小 SMSS)
- 如何判断拥塞发现?
- 传输超时,或者TCP重传定时器溢出。仍然使用慢启动和拥塞避免
- 接收到重复的确认报文段。使用快速重传和快速恢复
- RWND 接收窗口
接收端能够接收的最大报文长度。
- CWND 拥塞窗口
实际的SWND为RWND和CWND窗口中的较小者
- CWNS 拥塞窗口是发送方使用的流量控制
- RWND 接收窗口(通告窗口)是接收方使用的流量控制
2.5 Nagle算法
- 延迟ACK 延迟确认
- 服务端不马上确认上次收到的数据,而是在一段延迟时间后查看本端是否有数据需要发送,如果有,则和确认信息一起发出。
- 减少发送TCP报文段的数量
- Nagle算法
减少广域网小分组数组,一个tcp连接上最多只能有一个未被确认的未完成的小分组。
3. 基于流服务
- 基于流的数据没有边界(长度)限制
- 可以逐个字节向数据流写入/读取数据
- 全双工字节流
TCP报文头
- 32位序列号
- 32位确认号
- 6位标志位
- URG 紧急指针是否有效
- ACK 确认号是否有效,表示确认报文段
- PSH 接收应用程序应立即将数据取走
- RST 要求对方重新建立连接,表示复位报文段
出现的几种场景以及对应情况如何处理?
- 目标(服务端)端口未打开,即无监听;
- 配置SO_LINGER参数,直接关闭连接
- 半连接状态,发送数据会直接返回RST
- SYN 请求建立一个连接,表示同步报文段
- FIN 通知对方本方要关闭连接,表示结束报文段
- 16位窗口大小 流量控制
接收通告窗口,告诉对方本窗口TCP接收缓冲区还能容纳多少字节的数据。
- 16位校验和
由发送端填充,接收端对TCP报文段执行CRC算法校验
TCP状态转移
服务端
- CLOSED 状态
- 通过listen系统调用,进入LISTEN状态,被动等待客户端连接。一旦接受到客户端连接(同步报文段),就将该连接放入内核的等待队列中。并向该客户的连接发送SYN标志的确认报文段。
- 向客户端连接发送SYN标志的确认报文段,进入SYN_RCVD状态
- 接收到客户端发回的确认报文段,则进入ESTABLISHED状态。表示双方可以进行双向数据传输。
- 当客户端主动关闭连接(close或shutdown系统调用发送结束报文段),服务端返回确认报文段,进入CLOSE_WAIT状态。等待服务端应用程序关闭连接。
- 服务端接收到客户端发送的结束报文段,返回确认保报文段后。通常也会立即给客户端发送一个结束报文段来关闭连接。进入LAST_ACK状态。接收到客户端返回的确认报文段,连接彻底关闭。
客户端
- 发起connect系统调用,发送同步报文段,进入SYN_SEND状态。
1.1 失败:
1. 目标端口不存在或该端口处于TIME_WAIT状态,服务端返回复位报文段
2. 端口存在,但服务端返回超时
3. connect调用失败,返回到CLOSED状态
1.2 成功: 接收到服务端返回的同步报文段和确认,进入ESTABLISHED状态
- 发起关闭连接,发送结束报文段,进入FIN_WAIT_1状态。
2.1 成功:
a1. 接收到服务端返回的确认报文段,进入FIN_WAIT_2状态。此时,服务端处于CLOSE_WAIT状态。该状态下客户端可以继续接收数据,如果客户端直接关闭连接,未等服务端返回结束报文段。则该客户的连接将交由内核来接管,称为孤儿连接(类似孤儿进程)。Linux为了防止过多的孤儿连接,定义了变量来控制孤儿连接数量和孤儿的生存时间。
a2. 服务也关闭连接发送(发送结束报文段),客户端接收返回确认,进入TIME_WAIT状态。
b1. 直接收到服务端返回的结束报文段,直接进入TIME_WAIT状态。
TIME_WAIT状态,客户端进入该状态后会等待一段时间2MSL(报文段最大生存事件),才能完全关闭。可靠地终止TCP连接,保证让迟来的TCP报文段有足够的时间被识别并丢弃。
Socket编程
函数
- socket 返回套接字描述符
- connect 发起三次握手
- 客户端没收到SYN确认,返回ETIMEOUT错误
- 客户端收到RST响应(复位),返回ECONNREFUSED错误(服务端该端口没有进程等待连接)
- destination unreachable 重发,返回EHOSTUNREACH或ENETUNREACH。
- connect函数调用后,套接字状态从CLOSED转移到SYN_SENT状态;connect函数调用成功后,转移为ESTABLISHED
- bind 绑定IP地址
- listen 监听某个端口
- 套接字状态从CLOSED转移为LISTEN
- int listen(int sockfd, int backlog) 第二个参数规定套接字排队的最大连接个数-ESTABLISED状态的套接字。SYN_RCVD状态的排队数量由内核参数定义
- 内核为被监听的套接字维护两个队列
未完成连接队列
- 三次握手过程,套接字状态为SYN_RCVD。
- 队列项的留存时间为一个RTT
- 队列满,服务端忽略SYN分节,客户端重发。
已完成连接队列
- 完成三次握手,套接字状态为ESTABLISHED。
- accept
- 从已完成连接队列队头返回下一个已完成连接的套接字。如果队列为空,进程被投入睡眠。
- 即使连接中的状态已变成CLOSE_WAIT也会返回,accept只是从连接队列取连接而已,不做任何判断。
-
fork和exec
- close
不会立即关闭,但同时关闭读和写
设置SO_LINGER选项,将会把发送缓冲区的数据发送给客户
- shutdown 立即关闭连接
- 关闭读 直接丢弃接收缓冲区数据
- 关闭写 将发送缓冲区数据全部发送完
- 同时关闭读写
以上可能被阻塞的函数有:accept、send、recv、connect。
socket TCP选项参数
- SO_REUSEADDR 能够立即使用TIME_WAIT状态的地址
- SO_RCVBUF和SO_SNDBUF 发送和接收缓冲区大小
- SO_RVCLOWAT和SO_SNDLOWAT 设置发送和接收缓冲区的低水位标记,I/O复用有用到,用来判断socket是否可读或可写
- SO_LINGER