TCP连接及连接池管理
Posted JS魔法屋
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TCP连接及连接池管理相关的知识,希望对你有一定的参考价值。
2、TCPserver和TCP client如何建立连接
第一次握手:建立连接。客户端发送连接请求报文段,将SYN位置为1,Sequence Number为x;然后,客户端进入SYN_SEND状态,等待服务器的确认;
第二次握手:服务器收到客户端的SYN报文段,需要对这个SYN报文段进行确认,设置Acknowledgment Number为x+1(Sequence Number+1);同时,自己自己还要发送SYN请求信息,将SYN位置为1,Sequence Number为y;服务器端将上述所有信息放到一个报文段(即SYN+ACK报文段)中,一并发送给客户端,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK报文段。然后将Acknowledgment Number设置为y+1,向服务器发送ACK报文段,这个报文段发送完毕以后,客户端和服务器端都进入ESTABLISHED状态,完成TCP三次握手。
在一次 TCP 三次握手的过程中,客户端与服务端会分别提供一个套接字来形成一个链接。之后客户端与服务端通过这个链接来互相发送数据。
第一次分手:主机1(可以使客户端,也可以是服务器端),设置Sequence Number和Acknowledgment Number,向主机2发送一个FIN报文段;此时,主机1进入FIN_WAIT_1状态;这表示主机1没有数据要发送给主机2了;
第二次分手:主机2收到了主机1发送的FIN报文段,向主机1回一个ACK报文段,Acknowledgment Number为Sequence Number加1;主机1进入FIN_WAIT_2状态;主机2告诉主机1,我“同意”你的关闭请求;
第三次分手:主机2向主机1发送FIN报文段,请求关闭连接,同时主机2进入LAST_ACK状态;
第四次分手:主机1收到主机2发送的FIN报文段,向主机2发送ACK报文段,然后主机1进入TIME_WAIT状态;主机2收到主机1的ACK报文段以后,就关闭连接;此时,主机1等待2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,主机1也可以关闭连接了。
为什么TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态?
这是因为虽然双方都同意关闭连接了,而且握手的4个报文也都协调和发送完毕,按理可以直接回到CLOSED状态(就好比从SYN_SEND状态到ESTABLISH状态那样);但是因为我们必须要假想网络是不可靠的,你无法保证你最后发送的ACK报文会一定被对方收到,因此对方处于LAST_ACK状态下的Socket可能会因为超时未收到ACK报文,而重发FIN报文,所以这个TIME_WAIT状态的作用就是用来重发可能丢失的ACK报文。
TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态会产生什么问题
通信双方建立TCP连接后,主动关闭连接的一方就会进入TIME_WAIT状态,TIME_WAIT状态维持时间是两个MSL时间长度【MSL为最大报文段生存时间】,也就是在1-4分钟,Windows操作系统就是4分钟。进入TIME_WAIT状态的一般情况下是客户端,一个TIME_WAIT状态的连接就占用了一个本地端口。一台机器上端口号数量的上限是65536个,如果在同一台机器上进行压力测试模拟上万的客户请求,并且循环与服务端进行短连接通信,那么这台机器将产生4000个左右的TIME_WAIT Socket,后续的短连接就会产生address already in use : connect的异常,如果使用nginx作为方向代理也需要考虑TIME_WAIT状态,发现系统存在大量TIME_WAIT状态的连接,通过调整内核参数解决。
二、TCPserver和TCP client如何建立连接
基于node.js的Net 模块可以构建一个 TCP 服务,它提供了一些用于底层通信的接口,该模块可以用于创建基于流的 TCP 或 IPC 的服务器(net.createServer())与客户端(net.createConnection())。
可以使用 new net.Server 创建一个 TCP 服务端链接,也可以通过工厂函数 net.createServer() 的方式,createServer() 的内部实现也是内部调用了 Server 构造函数来创建一个 TCP 对象。
TCP 服务器事件:
listening: ,也就是 server.listen();
connection: 新链接建立时触发,也就是每次收到客户端回调,参数 socket 为 net.createServer 实例,也可以写在 net.createServer(function(socket) {}) 方法里
close:当 server 关闭的时候触发(server.close())。如果有连接存在,直到所有的连接结束才会触发这个事件
error:捕获错误,例如监听一个已经存在的端口就会报 Error: listen EADDRINUSE 错误
TCP 链接事件方法
data: 一端调用 write() 方法发送数据时,另一端会通过 socket.on('data') 事件接收到,可以理解为读取数据
end: 每次 socket 链接会出现一次,例如客户端发送消息之后执行 Ctrl + C 终端,就会收到
error: 监听 socket 的错误信息
close: socket 链接断开
通过net的socket创建一个client,请求上面的tcp服务
启动tcp服务,再启动一个客户端,连接成功后,会在服务上打印出当前连接的地址(127.0.0.1:13127)
上述demo中,每次通信都要建立一个连接,而建立一个tcp连接需要三次握手,而且还需要为对象分配系统资源和内存空间。所以创建一个tcp连接可以说是昂贵的,如果要频繁操作的话,不断开启关闭连接不仅对增加客户端io的压力,最重要的是大大增加了 tcp 服务器的压力。
使用长连接的话,可以在一个TCP连接上连续发送多个数据包,在TCP连接保持期间,如果没有数据包发送,需要双方发检测包以维持此连接(心跳包)。
短连接:每次请求完成后,断开连接;每次经过建立连接→数据传输→关闭连接;长连接:连接→数据传输→保持连接→数据传输→保持连接→……→关闭连接;
Socket连接池就是维护着一定数量Socket长连接的集合。它能自动检测Socket长连接的有效性,剔除无效的连接,补充连接池的长连接的数量。使用连接池最大的一个好处就是减少连接的创建和关闭,增加系统负载能力。
5. 当池中的连接一段时间没有被调用的时候,自动释放连接
2、基于Nodejs的Socket连接池管理(
generic-pool
)
generic-pool模块是nodejs的一个第三方模块,其作用为提供一个通用的连接池模块,可以通过generic-pool实现对tcp连接池或者mysql、mongodb、redis等数据库连接池的管理。
generic-pool
模块:
https://github.com/coopernurse/node-pool
fifo: true, // 是否优先使用老的资源
testOnBorrow: true, // 是否开启获取验证
acquireTimeoutMillis: 10 * 1000, // 获取的超时时间
autostart: true, // 自动初始化和释放调度启用
min: 10, // 连接池保持的长连接最小数量
evictionRunIntervalMillis:0, // 资源释放检验间隔检查设置了下面几个参数才起效果
numTestsPerEvictionRun: 3, // 每次释放资源数量
softIdleTimeoutMillis: -1, // 可用的超过了最小的min 且空闲时间时间达到释放
idleTimeoutMillis: 3000 // 强制释放如果一个线程3秒钟内没有被使用过就释放掉
1、创建一个连接池,连接池大小设置min:2,max:5,初始启动时会自动创建2个连接
2、首先发出2次请求,1秒后再发发出4个请求,可以看到,第一次发出的2个请求可以直接用连接池里的2个连接,请求完成后把连接归还给连接池,供后续请求使用;1秒后再发出4个请求,会首先复用之前创建的2个连接,但连接池里面的连接不够用了,此时连接池会再创建2个连接,给另外的两个请求用,此时连接池大小为4。
可
使用
jemeter
分别对比
TCP
短连接和长连接进行并发压测,对比其性能
以上是关于TCP连接及连接池管理的主要内容,如果未能解决你的问题,请参考以下文章
面试官:如何实现一个连接池,我当场懵了
HttpClient连接池的连接保持超时和失效机制
HttpClient连接池的连接保持超时和失效机制
一篇搞懂tcp,http,socket,socket连接池之间的关系
TCP连接与OKHTTP复用连接池
Golang 建立TCP时使用连接池