96SEO 2026-02-23 14:36 14
UDP协议、Socket、Socket长连接、Socket连接池等字眼然而它们之间的关系、区别及原理并不是所有人都能理解清楚这篇文章就从网络协议基础开始到Socket连接池一步一步解释他们之间的关系。

Interconnection)模型。
自下往上分为物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。
所有有关通信的都离不开它下面这张图片介绍了各层所对应的一些协议和硬件
通过上图我知道IP协议对应于网络层TCP、UDP协议对应于传输层而HTTP协议对应于应用层OSI并没有Socket那什么是Socket后面我们将结合代码具体详细介绍。
关于传输层TCP、UDP协议可能我们平时遇见的会比较多有人说TCP是安全的UDP是不安全的UDP传输比TCP快那为什么呢我们先从TCP的连接建立的过程开始分析然后解释UDP和TCP的区别。
我们知道TCP建立连接需要经过三次握手而断开连接需要经过四次分手那三次握手和四次分手分别做了什么和如何进行的。
第一次握手建立连接。
客户端发送连接请求报文段将SYN位置为1Sequence
Number为x然后客户端进入SYN_SEND状态等待服务器的确认第二次握手服务器收到客户端的SYN报文段需要对这个SYN报文段进行确认设置Acknowledgment
Number1)同时自己自己还要发送SYN请求信息将SYN位置为1Sequence
Number为y服务器端将上述所有信息放到一个报文段即SYNACK报文段中一并发送给客户端此时服务器进入SYN_RECV状态第三次握手客户端收到服务器的SYNACK报文段。
然后将Acknowledgment
Number设置为y1向服务器发送ACK报文段这个报文段发送完毕以后客户端和服务器端都进入ESTABLISHED状态完成TCP三次握手。
完成了三次握手客户端和服务器端就可以开始传送数据。
以上就是TCP三次握手的总体介绍。
通信结束客户端和服务端就断开连接需要经过四次分手确认。
第一次分手主机1可以使客户端也可以是服务器端设置Sequence
Number向主机2发送一个FIN报文段此时主机1进入FIN_WAIT_1状态这表示主机1没有数据要发送给主机2了第二次分手主机2收到了主机1发送的FIN报文段向主机1回一个ACK报文段Acknowledgment
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也可以关闭连接了。
可以看到一次tcp请求的建立及关闭至少进行7次通信这还不包过数据的通信而UDP不需3次握手和4次分手。
1、TCP是面向链接的虽然说网络的不安全不稳定特性决定了多少次握手都不能保证连接的可靠性但TCP的三次握手在最低限度上(实际上也很大程度上保证了)保证了连接的可靠性;而UDP不是面向连接的UDP传送数据前并不与对方建立连接对接收到的数据也不发送确认信号发送端不知道数据是否会正确接收当然也不用重发所以说UDP是无连接的、不可靠的一种数据传输协议。
2、也正由于1所说的特点使得UDP的开销更小数据传输速率更高因为不必进行收发数据的确认所以UDP的实时性更好。
知道了TCP和UDP的区别就不难理解为何采用TCP传输协议的MSN比采用UDP的QQ传输文件慢了但并不能说QQ的通信是不安全的因为程序员可以手动对UDP的数据收发进行验证比如发送方对每个数据包进行编号然后由接收方进行验证啊什么的即使是这样UDP因为在底层协议的封装上没有采用类似TCP的“三次握手”而实现了TCP所无法达到的传输效率。
关于TCP服务器最大并发连接数有一种误解就是“因为端口号上限为65535,所以TCP服务器理论上的可承载的最大并发连接数也是65535”。
首先需要理解一条TCP连接的组成部分客户端IP、客户端端口、服务端IP、服务端端口。
所以对于TCP服务端进程来说他可以同时连接的客户端数量并不受限于可用端口号理论上一个服务器的一个端口能建立的连接数是全球的IP数*每台机器的端口数。
实际并发连接数受限于linux可打开文件数这个数是可以配置的可以非常大所以实际上受限于系统性能。
通过#ulimit
2为什么TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态
这是因为虽然双方都同意关闭连接了而且握手的4个报文也都协调和发送完毕按理可以直接回到CLOSED状态就好比从SYN_SEND状态到ESTABLISH状态那样但是因为我们必须要假想网络是不可靠的你无法保证你最后发送的ACK报文会一定被对方收到因此对方处于LAST_ACK状态下的Socket可能会因为超时未收到ACK报文而重发FIN报文所以这个TIME_WAIT状态的作用就是用来重发可能丢失的ACK报文。
3TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态会产生什么问题
通信双方建立TCP连接后主动关闭连接的一方就会进入TIME_WAIT状态TIME_WAIT状态维持时间是两个MSL时间长度也就是在1-4分钟Windows操作系统就是4分钟。
进入TIME_WAIT状态的一般情况下是客户端一个TIME_WAIT状态的连接就占用了一个本地端口。
一台机器上端口号数量的上限是65536个如果在同一台机器上进行压力测试模拟上万的客户请求并且循环与服务端进行短连接通信那么这台机器将产生4000个左右的TIME_WAIT
connect的异常如果使用Nginx作为方向代理也需要考虑TIME_WAIT状态发现系统存在大量TIME_WAIT状态的连接通过调整内核参数解决。
Cookies。
当出现SYN等待队列溢出时启用cookies来处理可防范少量SYN攻击默认为0表示关闭
C网络面试题TCP/UDP应用场景分析UDP如何实现可靠性设计
Linux服务器架构师学习资料加qun812855908获取资料包括C/CLinuxgolang技术NginxZeroMQMySQLRedisfastdfsMongoDBZK流媒体CDNP2PK8SDockerTCP/IP协程DPDKffmpeg等免费分享
关于TCP/IP和HTTP协议的关系网络有一段比较容易理解的介绍“我们在传输数据时可以只使用(传输层)TCP/IP协议但是那样的话如果没有应用层便无法识别数据内容。
如果想要使传输的数据有意义则必须使用到应用层协议。
应用层协议有很多比如HTTP、FTP、TELNET等也可以自己定义应用层协议。
)是Web联网的基础也是手机联网常用的协议之一WEB使用HTTP协议作应用层协议以封装HTTP文本信息然后使用TCP/IP做传输层协议将它发到网络上。
由于HTTP在每次请求结束后都会主动释放连接因此HTTP连接是一种“短连接”要保持客户端程序的在线状态需要不断地向服务器发起连接请求。
通常
的做法是即时不需要获得任何数据客户端也保持每隔一段固定的时间向服务器发送一次“保持连接”的请求服务器在收到该请求后对客户端进行回复表明知道
客户端“在线”。
若服务器长时间无法收到客户端的请求则认为客户端“下线”若客户端长时间无法收到服务器的回复则认为网络已经断开。
现在我们了解到TCP/IP只是一个协议栈就像操作系统的运行机制一样必须要具体实现同时还要提供对外的操作接口。
就像操作系统会提供标准的编程接口比如Win32编程接口一样TCP/IP也必须对外提供编程接口这就是Socket。
现在我们知道Socket跟TCP/IP并没有必然的联系。
Socket编程接口在设计的时候就希望也能适应其他的网络协议。
所以Socket的出现只是可以更方便的使用TCP/IP协议栈而已其对TCP/IP进行了抽象形成了几个最基本的函数接口。
比如createlistenacceptconnectread和write等等。
不同语言都有对应的建立Socket服务端和客户端的库下面举例Nodejs如何创建服务端和客户端服务端
方法client.write(Bye!\n);//client.end();
所谓长连接指在一个TCP连接上可以连续发送多个数据包在TCP连接保持期间如果没有数据包发送需要双方发检测包以维持此连接(心跳包)一般需要自己做在线维持。
短连接是指通信双方有数据交互时就建立一个TCP连接数据发送完成后则断开此TCP连接。
比如Http的只是连接、请求、关闭过程时间较短,服务器若是一段时间内没有收到请求即可关闭连接。
其实长连接是相对于通常的短连接而说的也就是长时间保持客户端与服务端的连接状态。
连接→数据传输→保持连接(心跳)→数据传输→保持连接(心跳)→……→关闭连接
长连接多用于操作频繁点对点的通讯而且连接数不能太多情况。
每个TCP连接都需要三步握手这需要时间如果每个操作都是先连接再操作的话那么处理
速度会降低很多所以每个操作完后都不断开次处理时直接发送数据包就OK了不用建立TCP连接。
例如数据库的连接用长连接
如果用短连接频繁的通信会造成Socket错误而且频繁的Socket创建也是对资源的浪费。
心跳包就是在客户端和服务端间定时通知对方自己状态的一个自己定义的命令字按照一定的时间间隔发送类似于心跳所以叫做心跳包。
网络中的接收和发送数据都是使用Socket进行实现。
但是如果此套接字已经断开比如一方断网了那发送数据和接收数据的时候就一定会有问题。
可是如何判断这个套接字是否还可以使用呢这个就需要在系统中创建心跳机制。
其实TCP中已经为我们实现了一个叫做心跳的机制。
如果你设置了心跳那TCP就会在一定的时间比如你设置的是3秒钟内发送你设置的次数的心跳比如说2次并且此信息不会影响你自己定义的协议。
也可以自己定义所谓“心跳”就是定时发送一个自定义的结构体心跳包或心跳帧让对方知道自己“在线”,以确保链接的有效性。
实现
client.remotePort);clientList.push(client);client.on(data,
{console.log(收到客户端end);clientList.splice(clientList.indexOf(client),
{clientList.splice(clientList.indexOf(client),
是否可写clientList[i].write(heartbeat);}
{console.log(一个无效的客户端);cleanup.push(clientList[i]);
的方法销毁。
clientList[i].destroy();}}//Remove
cleanup[i].name);clientList.splice(clientList.indexOf(cleanup[i]),
Date().toUTCString());client.write(new
如果想要使传输的数据有意义则必须使用到应用层协议比如Http、Mqtt、Dubbo等。
基于TCP协议上自定义自己的应用层的协议需要解决的几个问题
心跳包格式的定义及处理报文头的定义就是你发送数据的时候需要先发送报文头报文里面能解析出你将要发送的数据长度你发送数据包的格式是json的还是其他序列化的方式
client.name);clientList.push(client)let
chunk.toString();console.log(content:,
parseInt(content.substring(7,20));console.log(length,
Buffer.concat(chunks);console.log(heap.length,
JSON.parse(heap.toString()));let
Buffer.from(JSON.stringify(data));let
getHeader(dataBuff.length)client.write(header);client.write(dataBuff);}
{console.log(数据解析失败);}}}})client.on(end,
{console.log(收到客户端end);clientList.splice(clientList.indexOf(client),
{clientList.splice(clientList.indexOf(client),
clientList[i].write(heartBeat);
{console.log(一个无效的客户端)cleanup.push(clientList[i])
的方法销毁。
clientList[i].destroy();}}//
cleanup[i].name);clientList.splice(clientList.indexOf(cleanup[i]),
chunk.toString();console.log(content:,
parseInt(content.substring(7,20));console.log(length,
Buffer.concat(chunks);console.log(heap.length,
Buffer.from(JSON.stringify(data));let
getHeader(dataBuff.length);client.write(header);client.write(dataBuff);
length:0000000000060服务端的数据数据:\Tue,
客户端定时发送自定义协议数据到服务端先发送头数据在发送内容数据另外一个定时器发送心跳数据服务端判断是心跳数据再判断是不是头数据再是内容数据然后解析后再发送数据给客户端。
从日志的打印可以看出客户端先后writeheader和data数据服务端可能在一个data事件里面接收到。
这里可以看到一个客户端在同一个时间内处理一个请求可以很好的工作但是想象这么一个场景如果同一时间内让同一个客户端去多次调用服务端请求发送多次头数据和内容数据服务端的data事件收到的数据就很难区别哪些数据是哪次请求的比如两次头数据同时到达服务端服务端就会忽略其中一次而后面的内容数据也不一定就对应于这个头的。
所以想复用长连接并能很好的高并发处理服务端请求就需要连接池这种方式了。
什么是Socket连接池,池的概念可以联想到是一种资源的集合所以Socket连接池就是维护着一定数量Socket长连接的集合。
它能自动检测Socket长连接的有效性剔除无效的连接补充连接池的长连接的数量。
从代码层次上其实是人为实现这种功能的类一般一个连接池包含下面几个属性
空闲可使用的长连接队列正在运行的通信的长连接队列等待去获取一个空闲长连接的请求的队列无效长连接的剔除功能长连接资源池的数量配置长连接资源的新建功能
一个请求过来首先去资源池要求获取一个长连接资源如果空闲队列里面有长连接就获取到这个长连接Socket,并把这个Socket移到正在运行的长连接队列。
如果空闲队列里面没有且正在运行的队列长度小于配置的连接池资源的数量就新建一个长连接到正在运行的队列去如果正在运行的不下于配置的资源池长度则这个请求进入到等待队列去。
当一个正在运行的Socket完成了请求就从正在运行的队列移到空闲的队列并触发等待请求队列去获取空闲资源如果有等待的情况。
这里简单介绍Nodejs的Socket连接池generic-pool模块的源码。
主要文件目录结构
|————DoublyLinkedListIterator.js-
最大连接池保持的长连接数量evictionRunIntervalMillis:
设置了下面几个参数才起效果numTestsPerEvictionRun:
net.Socket();socket.setKeepAlive(true);socket.connect(conifg.port,
);resolve(socket);});socket.on(close,
事件再close事件console.log(socket_pool,
err);reject(err);});});},//销毁连接destroy:
如果有message会触发error事件resolve();});},validate:
genericPool.createPool(factory,
options);pool.on(factoryCreateError,
pool._waitingClientsQueue.dequeue();if
{clientResourceRequest.reject(err);}});return
0;client.setTimeout(timeout);client.removeAllListeners(error);client.on(error,
{client.removeAllListeners(error);client.removeAllListeners(data);client.removeAllListeners(timeout);pool.destroyed(client);reject(err);});client.on(timeout,
{client.removeAllListeners(error);client.removeAllListeners(data);client.removeAllListeners(timeout);//
应该销毁以防下一个req的data事件监听才返回数据pool.destroy(client);//
pool.release(client);reject(socket
getHeader(requestDataBuff.length);client.write(header);client.write(requestDataBuff);client.on(data,
chunk.toString();console.log(content,
parseInt(content.substring(7,20));console.log(length,
Buffer.concat(chunks);console.log(heap.length,
{pool.release(client);client.removeAllListeners(error);client.removeAllListeners(data);client.removeAllListeners(timeout);try
JSON.parse(heap.toString()));resolve(JSON.parse(heap.toString()));}
{reject(err);console.log(数据解析失败);}}});});
request(Buffer.from(JSON.stringify({a:
{console.log(收到服务的数据,data)}).catch(err
{console.log(err);});request(Buffer.from(JSON.stringify({b:
{console.log(收到服务的数据,data)}).catch(err
{console.log(err);});setTimeout(function
有没有建立新的连接request(Buffer.from(JSON.stringify({c:
{console.log(收到服务的数据,data)}).catch(err
{console.log(err);});request(Buffer.from(JSON.stringify({d:
{console.log(收到服务的数据,data)}).catch(err
length:0000000000040服务端的数据数据:{\a\:\a\}
length:0000000000040服务端的数据数据:{\b\:\b\}
length:0000000000040服务端的数据数据:{\d\:\d\}
connect定时器结束后重新发起两个请求就没有建立新的Socket连接了直接从连接池里面获取Socket连接资源。
{super();factoryValidator(factory);
this._config.Promise;this._factory
{PriorityQueue}*/this._waitingClientsQueue
PriorityQueue(this._config.priorityRange);
{Set}*/this._factoryCreateOperations
{Set}*/this._factoryDestroyOperations
{Deque}*/this._availableObjects
{Set}*/this._testOnBorrowResources
{Set}*/this._testOnReturnResources
{Set}*/this._validationOperations
{DequeIterator}*/this._evictionIterator
this._availableObjects.iterator();
{(number|null)}*/this._scheduledEviction
可以看到包含之前说的空闲的资源队列正在请求的资源队列正在等待的请求队列等。
(this._config.maxWaitingClients
this._waitingClientsQueue.length
this._config.maxWaitingClients)
ResourceRequest(this._config.acquireTimeoutMillis,
console.log(resourceRequest)this._waitingClientsQueue.enqueue(resourceRequest,
最终会触发resourceRequest.promise的resolve(client)
返回的是一个promise对象resolve却是在其他地方触发}
this._waitingClientsQueue.length;
各个优先级的总和console.log(numWaitingClients,
4console.log(_potentiallyAllocableResourceCount,
this._potentiallyAllocableResourceCount)
this._potentiallyAllocableResourceCount;
大于0表示需要新建长连接的数量console.log(spareResourceCapacity,
actualNumberOfResourcesToCreate
Math.min(this.spareResourceCapacity,
表示需要新建但是这新建的数量不能超过spareResourceCapacity最多可创建的console.log(actualNumberOfResourcesToCreate,
actualNumberOfResourcesToCreate)
如果actualNumberOfResourcesToCreate
actualNumberOfResourcesToCreate
desiredNumberOfResourcesToMoveIntoTest
this._testOnBorrowResources.size;//
actualNumberOfResourcesToMoveIntoTest
Math.min(this._availableObjects.length,
3desiredNumberOfResourcesToMoveIntoTest
actualNumberOfResourcesToMoveIntoTest
actualNumberOfResourcesToDispatch
Math.min(this._availableObjects.length,numWaitingClients);for
actualNumberOfResourcesToDispatch
开始分发资源this._dispatchResource();}}}/***
[description]*/_dispatchResource()
this._availableObjects.shift();
从可以资源池里面取出一个this._dispatchPooledResourceToNextWaitingClient(pooledResource);
[description]*/_dispatchPooledResourceToNextWaitingClient(pooledResource)
this._waitingClientsQueue.dequeue();
取出一个等待的queneconsole.log(clientResourceRequest.state,
clientResourceRequest.state);if
back.this._addPooledResourceToAvailableObjects(pooledResource);
this._resourceLoans.set(pooledResource.obj,
就是socket本身pooledResource.allocate();
标识资源的状态是正在被使用clientResourceRequest.resolve(pooledResource.obj);
acquire方法返回的promise对象的resolve在这里执行的return
上面的代码就按种情况一直走下到最终获取到长连接的资源其他更多代码大家可以自己去深入了解。
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback