赞
踩
从本篇文章开始,总结TCP协议相关的知识点。TCP协议相关内容可以分为5个部分:TCP报文,TCP连接,Socket编程,可靠传输机制和流量控制,拥塞控制。本篇文章记录TCP报文、TCP连接和Socket编程三个部分的内容。
TCP协议是面向连接的、可靠的、基于字节流的数据传输协议。TCP协议在协议栈中位于IP协议的上一层,属于传输层协议。
TCP首部占20到60个字节,即如果选项部分长度为0,TCP首部占20个字节。
一个TCP连接由一对Socket(四元组:客户端ip:客户端port,服务器ip:服务器port)唯一标识。建立一个TCP连接是需要客户端与服务器达成三个信息的共识,包括socket、序列号、窗口大小。
源地址和目的地址字段(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连接都会占用一定内存,而服务器内存是有限的。
建立一个TCP连接是需要客户端和服务器达成三个信息的共识,包括Socket、序列号和窗口大小。关于TCP三次握手可以简要描述为:客户端发送SYN,服务端发送SYN+ACK,客户端发送ACK。
值得注意的一点是,第三次握手之前有了初始序列号,所以第三次握手时可以携带数据;前两次握手没有初始序列号,不能携带数据。总结来说,三次握手的过程就是客户端与服务器 初始化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队列被占满。
关于TCP四次挥手可以简要描述为:客户端发送FIN,服务端发送ACK,服务端发送FIN,客户端发送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
。
服务端
客户端
注意: 服务端调用accept时,连接成功,会返回一个已经完成连接的socket,用来传输数据。
问题一: 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成功返回是在三次握手成功后。
TCP和UDP是TCP/IP体系结构中运输层的两个协议。UDP全称是用户数据报协议,TCP全称是传输控制协议。
子网掩码是用来划分子网的,ip&子网掩码=网络地址
Host ID 为全1 的IP 地址为广播地址。广播地址可以向该网段内所有主机发送消息。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。