TCP是面向连接的、可靠的,基于字节流的传输层通信协议。
图片来源小林coding
源地址+源端口+目的地址+目标端口
最 T C P 连接数 = 客户端 I P 数 ∗ 客户端端口数 最TCP连接数=客户端IP数*客户端端口数 最TCP连接数=客户端IP数∗客户端端口数
当然,服务端最大TCP连接数远远不能达到理论上限,会受到以下因素影响:
- 内存限制: 每个 TCP 连接都要占用一定内存,操作系统的内存是有限的,如果内存资源被占满后,会发生 OOM。
- 文件描述符限制:每个 TCP 连接都是一个文件,如果文件描述符被占满了,会发生 Too many open files。Linux 对可打开的文件描述符的数量分别作了三个方面的限制:
- 系统级:当前系统可打开的最大数量,通过 cat /proc/sys/fs/file-max 查看;
- 用户级:指定用户可打开的最大数量,通过 cat /etc/security/limits.conf 查看;
- 进程级:单个进程可打开的最大数量,通过 cat /proc/sys/fs/nr_open 查看;
0-1023为知名端口号,比如其中HTTP是80,FTP是20(数据端口)、21(控制端口)
UDP和TCP报头使用两个字节存放端口号,所以端口号的有效范围是从0到65535。动态端口的范围是从1024到65535
提供无连接的,尽最大努力的数据传输服务(不保证数据传输的可靠性)。
当我们发送的UDP数据大于1472的时候会怎样呢? 这也就是说IP数据报大于1500字节,大于MTU.这个时候发送方IP层就需要分片(fragmentation). 把数据报分成若干片,使每一片都小于MTU.而接收方IP层则需要进行数据报的重组. 这样就会多做许多事情,而更严重的是,由于UDP的特性,当某一片数据传送中丢失时,接收方便 无法重组数据报.将导致丢弃整个UDP数据报。
因此,在普通的局域网环境下,我建议将UDP的数据控制在1472字节以下为好.
一个完整的业务可能会被TCP拆分成多个包进行发送,这是拆包;发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾,这是粘包。
1、应用程序写入数据的字节大小大于套接字发送缓冲区的大小.
2、进行MSS大小的TCP分段。( MSS=TCP报文段长度-TCP首部长度)
3、以太网的payload大于MTU进行IP分片。( MTU指:一种通信协议的某一层上面所能通过的最大数据包大小。)
4、TCP连接复用导致粘包
5、TCP默认使用nagle算法,这个算法会导致粘包问题
6、流量控制、拥塞控制导致粘包
7、接收方不及时接收缓冲区的包,造成多个包接收
1、应用层发送数据时定长发送。
2、在包尾部增加回车或者空格符等特殊字符(例如\n\r,\t)进行分割
3、将消息分为消息头和消息尾,头部标记分步接收。在TCP报文的头部加上表示数据长度。
4、使用其它复杂的协议,如RTMP协议等。
5、nagle导致的需要结合应用场景适当关闭该算法
有限制。chrome最多允许对同一个Host建立6个TCP连接。不同的浏览器会有一些区别。
如果图片都是HTPS连接,并且在同一个域名下,那么浏览器在SSL握手之后会和服务器商量能不能用HTTP2,能的话就使用multiplexing功能在这个连接上进行多路传输。不过也未必会所有挂在这个域名的资源都会使用一个TCP连接去获取,但是可以确定的是multiplexing很有可能会被用到。
如果用不到HTTP2或者HTTPS(现实中的 HTTP2 都是在 HTTPS 上实现的,所以也就是只能使用 HTTP/1.1)。那浏览器就会在一个HOST上面建立多个TCP连接,连接的最大数量取决于浏览器设置,这些连接会在空闲的时候被浏览器用来发送新的请求。如果所有的连接都正在发送请求,那其他的请求就只能再等待一段时间。
深入探究“在浏览器输入URL到渲染页面”(上)过程剖析
用户在浏览器地址栏输入url并回车
1、浏览器处理用户输入,把处理后的url发送至网络进程
2、 网络进程收到url请求之后查看本地是否缓存了这个资源(强缓存和协商缓存),如果有则该资源返回给浏览器进程
3、如果没有,网络进程向服务器发起HTTP请求(网络请求)以请求资源:
TCP四次挥手
浏览器进程检查当前url是否和之前打开的渲染进程根域名是否相同,如果相同,则复用原来的进程,如果不同,则开启新的渲染进程。
渲染进程准备好之后,需要先向渲染进程提交页面数据,这个是提交文档阶段
渲染进程接收完文档信息之后,便开始解析页面和加载子资源,完成页面渲染。
三次握手就是建立一个TCP连接的时候,需要客户端和服务器总共发送3个包,进行三次握手的主要作用是为了确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传输做准备。
实质上其实就是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号,交换TCP窗口大小信息。
在 HTTP/1.0 中,一个服务器在发送完一个 HTTP 响应后,会断开 TCP 链接。但是这样每次请求都会重新建立和断开 TCP 连接,代价过大。
所以虽然标准中没有设定,某些服务器对 Connection: keep-alive 的 Header 进行了支持。意思是说,完成这个 HTTP 请求之后,不要断开 HTTP 请求使用的 TCP 连接。这样的好处是连接可以被重新使用,之后发送 HTTP 请求的时候不需要重新建立 TCP 连接,以及如果维持连接,那么 SSL 的开销也可以避免。
持久连接:HTTP/1.1 把 Connection 头写进标准,并且默认开启持久连接,除非请求中写明 Connection: close,那么浏览器和服务器之间是会维持一段时间的 TCP 连接,不会一个请求结束就断掉。
默认情况下建立 TCP 连接不会断开,只有在请求报头中声明 Connection: close 才会在请求完成后关闭连接。
刚开始客户端处于closed的状态,服务端处于listen状态,进行三次握手:
在socket编程中,客户端执行connect()时,将触发三次握手。
ISN = M + F(localhost, localport, remotehost, remoteport)(M为计数器),ISN应该由这个公式确定,F为哈希算法,不是一个简单计数器。
这样选择序号的目的在于防止在网络中被延迟的分组在以后又被传送,而导致某个连接的一方对它做错误的解释。如果 ISN是固定的,攻击者很容易猜出之后的确认号,所以是动态生成的。
不可以,因为如果有人要恶意攻击服务器,那他每次都在第一次握手中的 SYN 报文中放入大量的数据。因为攻击者根本就不理服务器的接收、发送能力是否正常,然后疯狂着重复发 SYN 报文的话,这会让服务器花费很多时间、内存空间来接收这些报文。
第三次握手的时候,客户端已经是establishd状态了,已经建立起连接了并且知道服务器的接收、发送能力是正常的了。
如果客户端发送请求,但是请求保温丢失了,然后客户端重传一次连接请求。客户端一共发送了两次连接请求报文段,第一个在网络中长时间滞留,第二个到达服务器。连接释放之后,第一个也到达服务端了,结果服务器以为这个是一个新的连接请求,然后就又建立了新连接,此时客户端忽略服务端发来的确认,也不发送数据,则服务端一直等待客户端发送数据,浪费资源。
客户端超时重传3次SYN报文之后,由于假设tcp_syn_retries为3(在 Linux 里,客户端的 SYN 报文最大重传次数由 tcp_syn_retries内核参数控制,这个参数是可以自定义的,默认值一般是 5),已经达到最大重传次数,于是再等待一段时间,事件时间为上一次超时时间的两倍,如果还是没有收到服务端的第二次握手,那么客户端就会断开连接
当服务端超时重传 2 次 SYN-ACK 报文后,由于假设 tcp_synack_retries 为 2,已达到最大重传次数,于是再等待一段时间(时间为上一次超时时间的 2 倍),如果还是没能收到客户端的第三次握手(ACK 报文),那么服务端就会断开连接。
服务器第一次收到客户端的SYN之后,就会处于SYN_RCVD状态,此时双方还没有完全建立连接,服务器会把此种状态下请求连接放在一个队列里面,这种队列就是半连接队列。
还有一个全连接队列,就是已经完成三次握手,建立起连接的就会放在全连接队列中,如果队列满了就有可能会出现丢包现象。
客户端你想服务端发送请求链接数据包,服务端向客户端发送确认数据包,客户端不向服务端发送确认数据包,服务器一直等待来自客户端的确认,没有彻底根治的方法。
是一种典型的DDos攻击。SYN攻击就是Client在短时间内伪造大量不存在的IP地址,并向Server不断地发送SYN包,Server则回复确认包,并等待Client确认,由于源地址不存在,因此Server需要不断重发直至超时,这些伪造的SYN包将长时间占用未连接队列,导致正常的SYN请求因为队列满而被丢弃,从而引起网络拥塞甚至系统瘫痪。
检测 SYN 攻击非常的方便,当你在服务器上看到大量的半连接状态时,特别是源IP地址是随机的,基本上可以断定这是一次SYN攻击。可以通过netstats来检测SYN攻击
netstat -n -p TCP | grep SYN_RECV
防御SYN攻击
net.core.netdev_max_backlog = 10000
net.ipv4.tcp_max_syn_backlog
listen() 函数中的 backlog
net.core.somaxconn
当SYN队列满了之后,后续收到SYN包,不会丢弃,而是根据算法计算出一个cookie值;放到第二次握手报文的序列号里,然后服务端返回给客户端;服务端接受客户端的应答报文的时候会检查这个ACK包的合法性,如果合法就放到accept队列;最后程序通过调用accept接口从accept取出连接。
通过设置,0是关闭功能,2是无条件开启,1是SYN队列放不下就启用
echo 1 > /proc/sys/net/ipv4/tcp_syncookies
$ echo 2 > /proc/sys/net/ipv4/tcp_synack_retries
一个报文段最大生存时间为MSL,因为TCP报文以IP数据报在网络内传输,而IP数据包则有限制其生存时间的TTL字段。
前面介绍的方法都是试图越过 TIME_WAIT状态的,这样其实不太好。虽然 TIME_WAIT 状态持续的时间是有一点长,显得很不友好,但是它被设计来就是用来避免发生乱七八糟的事情。
《UNIX网络编程》一书中却说道:TIME_WAIT 是我们的朋友,它是有助于我们的,不要试图避免这个状态,而是应该弄清楚它。如果服务端要避免过多的 TIME_WAIT 状态的连接,就永远不要主动断开连接,让客户端去断开,由分布在各处的客户端去承受 TIME_WAIT。
CLOSE_WAIT是被动关闭方才会有的状态,如果被动关闭方没有调用close函数关闭连接,那么就无法发出FIN报文,无法使得CLOSE_WAIT状态转变为LAST_ACK状态。
还有一种原因:服务器的父进程派生出子进程,子进程继承了socket,收到FIN的时候子进程处理但父进程没有处理该信号,导致socket的引用不为0无法回收。
参考小林coding
一个普通的 TCP 服务端的流程:
总结来说,就是1、停止应用程序;2、修改程序里的bug。