TCP协议
从本篇文章开始,总结TCP协议相关的知识点。TCP协议相关内容可以分为5个部分:TCP报文,TCP连接,Socket编程,可靠传输机制和流量控制,拥塞控制。本篇文章记录TCP报文、TCP连接和Socket编程三个部分的内容。
1. TCP报文
TCP协议是面向连接的、可靠的、基于字节流的数据传输协议。TCP协议在协议栈中位于IP协议的上一层,属于传输层协议。
1.1. TCP首部
TCP首部占20到60个字节,即如果选项部分长度为0,TCP首部占20个字节。
2. TCP连接
一个TCP连接由一对Socket(四元组:客户端ip:客户端port,服务器ip:服务器port)唯一标识。建立一个TCP连接是需要客户端与服务器达成三个信息的共识,包括socket、序列号、窗口大小。
- Socket:由IP地址和端口号组成
源地址和目的地址字段(32位)在IP头部,作用是通过ip协议发送报文给对方主机。
源端口和目的端口字段(16位)在TCP头部,作用是通过TCP协议把报文发送给目标主机的指定进程。
问题:一个服务器监听了一个端口,它TCP的最大连接数是多少?
对ipv4来说,客户端最多 2 32 2^{32} 232个ip,端口号最多 2 16 2^{16} 216个,也就是说服务端单机最大TCP连接数为 2 48 2^{48} 248个。当然,服务端最大TCP连接数远远不能达到这个理论上限。
原因可归纳为两点:1. 首先主要是文件描述符的限制,socket都是文件,可以在ulimit文件中配置文件描述符的个数。
2. 另一个是内存限制,每个TCP连接都会占用一定内存,而服务器内存是有限的。
- 序列号:用来解决乱序问题。
- 窗口大小:用来做流量控制。
2.1. 三次握手
建立一个TCP连接是需要客户端和服务器达成三个信息的共识,包括Socket、序列号和窗口大小。关于TCP三次握手可以简要描述为:客户端发送SYN,服务端发送SYN+ACK,客户端发送ACK。
- 客户端向服务器端发送请求报文,请求建立连接标志位SYN=1、发送序列号seq=x(随机),客户端进入SYN_SENT状态,等待服务器确认
- 服务器收到客户端请求,同意连接,发送确认报文,SYN=1,ACK=1,ack=x+1,序列号seq = y,服务器进入SYN_RECV状态。
- 客户端收到服务端报文,标志位ACK=1,发送确认号ack=y+1,seq= x+1,客户端和服务器进入ESTABLISHED状态。
值得注意的一点是,第三次握手之前有了初始序列号,所以第三次握手时可以携带数据;前两次握手没有初始序列号,不能携带数据。总结来说,三次握手的过程就是客户端与服务器 初始化socket、序列号、窗口大小,并建立TCP连接的过程。
问题1:为什么是三次握手,而不是两次、四次握手?
该问题可以分两个方面来回答,第一、为什么要使用三次握手。第二、为什么不能用两次握手。
- 首先回答第一个问题,三次握手的首要原因是为了防止旧的重复连接初始化造成混乱(阻塞的SYN报文发送到服务端);同步双方初始序列号;避免浪费资源。
- 再回答问什么不能用两次握手,因为两次握手不能完成上述三个问题。
- 还有一点,两次握手容易形成死锁。如果服务器发送给客户端的确认信息丢失了,此时客户端以为还未建立连接,只会等待连接确认的应答分组,服务器发送的的分组超时后,又会发送同样的分组,就会形成死锁。
- 至于为什么不是四次握手,实际上四次握手也可以建立可靠连接,只是没有必要。
问题2:既然IP层会分片,为什么TCP层还需要MSS?
- IP层有一个超过MTU大小的数据要发送,那么IP层要进行分片,每个分片都会小于MTU。如果有一个IP分片丢失,整个IP报文所有分片都得重传,这是因为IP层没有超时重传机制,它是由传输层的TCP来负责超时和重传的。
- 当接收方发现TCP报文的某一片丢失后,则不会响应ACK给对方,那么发送TCP超时后,就会重发整个报文,因此可得出IP层进行分片传输效率并不高。
- TCP协议建立连接时,通常要协商双方MSS,TCP层发现数据超过MSS时,会先进行分片,MSS形成的IP包也不会大于MTU。TCP分片机制,进行重发时也是以MSS为单位的。
问题3:什么是SYN攻击?如何避免SYN攻击?
攻击者伪造不同IP地址的SYN报文,服务端每接收到一个SYN报文,就进入SYN_RCVD状态,但服务端发送出去的ACK+SYN报文,无法得到未知IP主机的ACK应答,久而久之就会占满服务器的SYN接收队列,使得服务器不能为正常用户服务。
问题4:Linux内核SYN队列和Accept队列工作原理
- 当服务端接收到客户端的SYN报文时,会将其加入内核SYN队列。
- 接着发送SYN+ACK给客户端,等待客户端回应ACK报文。
- 服务端接收到ACK报文后,从SYN队列移除,放入Accept队列。 应用通过调用accept(),从Accept队列取出连接,如果应用程序过慢时,会导致Accept队列被占满。
2.2. 四次挥手
关于TCP四次挥手可以简要描述为:客户端发送FIN,服务端发送ACK,服务端发送FIN,客户端发送ACK。
- 客户端发送连接释放报文,表示请求断开连接。断开连接标志位FIN=1、序列号为seq=u,客户端进入FIN_WAIT_1阶段。
- 服务端收到释放连接报文,发送确认报文。确认标志ACK=1、ack=u+1,带上序列号seq=v,服务器进入CLOSED_WAIT状态。客户端收到服务器端的ACK回复以后,与服务器进行最后的数据传输,而这段数据传输的时间,在客户端处于FIN_WAIT_2状态。
- 服务器将最后数据发送完毕,发出连接释放报文,FIN=1、ACK=1,ack=u+1,由于期间服务器又发送了一些数据seq=w,服务器进入LAST_ACK状态。
- 客户端收到服务器的连接释放报文后,确认标志ACK=1,ack=w+1,自己序列号为u+1。同时自己进入一个TIME_WAIT的等待时间,等待结束后客户端关闭连接,服务器收到客户端的ACK后会关闭连接。
问题1:为什么建立连接要三次挥手而断开连接要四次挥手?
建立连接时ACK和SYN是放在一个报文里来发送。关闭连接时,被关闭的一方可能还有未发完的数据需要先发送ACK,等数据发送完,再发送FIN报文表示同意断开连接,因此这里的ACK报文和FIN报文是分开发送的。
问题2:TIME_WAIT状态
- 主动关闭连接的,才有TIME_WAIT状态。
- MSL(Maximum Segment Lifetime),报文最大生存时间,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。
- TIME_WAIT为2MSL的合理解释在于,网络中可能存在来自发送方的数据包,当这些发送方的数据包被接收方处理后又会向对方发送响应,一来一回需等待2倍时间,Linux系统中2MSL默认是60秒。
问题3: 为什么最后一次挥手后还要等待2MSL的时间?
- 保证客户端发送的最后一个ACK报文能够到达服务器,因为这个ACK报文可能丢失,站在服务器的角度看来,我已经发送了FIN+ACK报文请求断开了,客户端还没有给我回应,应该是我发送的请求断开报文它没有收到,于是服务器又会重新发送一次,而客户端就能在这个2MSL时间段内收到这个重传的报文,接着给出回应报文,并且会重启2MSL计时器。
- 防止旧连接的数据包出现在新连接中。客户端发送完最后一个确认报文后,在这个2MSL时间中,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样新的连接中不会出现旧连接的请求报文。
问题4:优化TIME_WAIT
- 一个四元组唯一标识一个TCP连接,理论上服务端可以建立很多连接。服务端确实只监听一个端口,但是会把连接扔给处理线程,而线程池可能处理不了那么多一直不断开的连接。所以系统资源被占满时,服务端出现大量TIME_WAIT,会导致处理不过来新的连接。
- 优化方式。第一,
net.ipv4.tcp_tw_reuse
和tcp_timestamps
;第二,net.ipv4.tcp_max_tw_buckets
;第三,使用SO_LINGER
。
3. Socket编程
3.1. 步骤
服务端
- 初始化socket,得到文件描述符。
- 调用bind,绑定ip地址和端口号。
- listen,进行监听。
- accept,等待客户端连接。
客户端
- connect,向服务器端的地址和端口号发起连接请求。
- 客户端拿到服务器调用accept返回的文件描述符。
- 客户端调用write写入数据,服务端调用read读取数据。
- 客户端断开连接时调用close关闭连接;服务端调用read读数据时,会读到EOF,处理完数据后,调用close关闭连接。
注意: 服务端调用accept时,连接成功,会返回一个已经完成连接的socket,用来传输数据。
- 监听和真正用来传输数据的是两个socket,一个叫作监听socket,一个叫作已完成连接的socket。
- 成功建立连接后,双方通过read和write读写数据,就像是往一个文件流里写内容一样。
问题一: listen函数
Linux内核中会维护两个队列:一个是未完成连接的队列(SYN队列),即处于SYN_RCVD状态的队列;另一个是已完成连接的队列(Accept队列),即处于ESTABLISHED状态的队列。
int listen(sockfd, backlog)
函数中backlog参数在早期linux系统中,通常指SYN队列大小;在Linux内核2.2之后,backlog变成accept队列,现在通常认为backlog是Accept队列。
问题二:connect函数和accept函数
- 客户端connect成功返回是在第二次握手后。
- 服务端accept成功返回是在三次握手成功后。
4. TCP和UPD的区别
TCP和UDP是TCP/IP体系结构中运输层的两个协议。UDP全称是用户数据报协议,TCP全称是传输控制协议。
- 是否基于连接?
TCP是面向连接的协议,UDP是无连接的协议。也就是说,UDP在发送数据前无需建立连接。 - 是否可靠?
TCP是可靠的,UDP是不可靠的。TCP通过校验和,重传控制,滑动窗口,确认应答,可靠传输等机制保证了消息的按序到达,尽可能保证消息的可靠性;UDP是尽最大努力交付,数据包可能会以任何可能的顺序到达。 - 实时性
UDP具有很好的实时性,工作效率比TCP高。 - 协议首部
TCP首部消耗20字节,UDP首部消耗8字节。 - 是否支持一对多
TCP连接只能是点对点的;UDP支持一对一,一对多,多对一,多对多。 - 应用场景
TCP应用在HTTP,HTTPS,FTP,SMTP,Telnets;
UDP用在DNS,DHCP,SNMP,RIP,VOIP等。
5. 子网掩码
5.1. 子网掩码的作用是什么?
子网掩码是用来划分子网的,ip&子网掩码=网络地址
Host ID 为全1 的IP 地址为广播地址。广播地址可以向该网段内所有主机发送消息。