这个项目模拟了一个 TCP 服务器-客户端系统,其中服务器使用线程池来处理多个客户端的连接。服务器通过队列机制管理等待中的客户端,当达到最大并发连接数时,使用超时处理机制丢弃超过最大等待时间的客户端连接。
- TCP 服务器:处理多个客户端的连接,最多支持 2 个客户端并发。
- 线程池:服务器使用
ThreadPoolExecutor
来管理客户端处理线程。 - 队列管理:当达到最大连接数时,客户端会被放入等待队列,直到有空闲线程可用。
- 超时处理:等待队列中的客户端如果超过指定等待时间(6 秒),会被丢弃。
TcpServer.java
:服务器端代码,负责处理客户端连接、线程池管理和等待队列的操作。TcpClient1.java
:客户端 1 代码,连接到服务器,发送消息并接收响应。TcpClient2.java
:客户端 2 代码,连接到服务器,发送消息并接收响应。TcpClient3.java
:客户端 3 代码,连接到服务器,发送消息并接收响应。
- 线程池:服务器使用
ThreadPoolExecutor
来处理最大 2 个并发客户端。MAX_CLIENTS
:服务器最多同时处理的客户端数量(设置为 2)。WAIT_TIME
:客户端在等待队列中等待的最大时间(单位:秒),超时则丢弃该连接。
- 客户端处理:每个客户端由一个独立的线程(
ClientHandler
)来处理,处理客户端的请求并发送响应。 - 等待队列:当服务器达到最大连接数时,新的客户端连接会被放入等待队列中。
- 服务器会定期检查队列中的客户端,并在有空闲线程时开始处理队列中的客户端。
- 如果客户端等待超过了
WAIT_TIME
设置的最大时间,连接将被丢弃。
- 服务器初始化:
- 服务器监听端口
8888
,等待客户端连接。 - 如果当前活跃连接数少于
MAX_CLIENTS
,服务器会立即处理新的客户端连接。 - 如果活跃连接数已满,则新的客户端会被放入等待队列。
- 服务器监听端口
- 客户端处理:
- 服务器与客户端进行通信,读取客户端发送的消息,并发送适当的响应。
- 处理完请求后,服务器会发送
END
消息,表示通信结束。
- 等待机制:
- 当服务器无法立即处理客户端时,客户端会进入等待队列。
- 服务器每 100 毫秒检查一次队列,看看是否有空闲线程可以处理等待中的客户端。
- 如果客户端等待时间超过了
WAIT_TIME
,则服务器会丢弃该连接。
- 超时处理:
- 如果客户端在等待队列中的等待时间超过了
WAIT_TIME
,连接会被丢弃,并会在控制台输出超时信息。
- 如果客户端在等待队列中的等待时间超过了
-
客户端 3 的处理
- 当服务器的线程池已经有 2 个客户端连接时,客户端 3 将被放入等待队列中,直到有空闲线程。
- 如果客户端 3 在队列中等待超过
WAIT_TIME
(即 6 秒),则会被丢弃,并会在控制台输出超时信息。 - 如果客户端 3 在等待期间有空闲线程可用,服务器会从队列中取出客户端并开始处理。
- 连接超时:客户端在尝试连接服务器时设置了 3 秒的超时限制。
- 消息发送:客户端发送固定消息给服务器。
- 监听响应:客户端会一直监听服务器的响应,直到收到
END
消息。
- 连接服务器:
- 客户端连接到服务器,服务器地址默认为
localhost:8888
。
- 客户端连接到服务器,服务器地址默认为
- 发送消息:
- 客户端向服务器发送消息
"我是客户端1"
(或"我是客户端2"
、"我是客户端3"
)。
- 客户端向服务器发送消息
- 接收服务器响应:
- 客户端持续监听服务器的响应。如果收到
END
消息,客户端将退出。
- 客户端持续监听服务器的响应。如果收到
- 关闭连接:
- 一旦通信结束,客户端会关闭与服务器的连接。
-
编译 Java 文件:
-
确保
TcpServer.java
、TcpClient1.java
、TcpClient2.java
和TcpClient3.java
文件位于同一目录下。 -
打开终端,导航到文件所在目录。
-
编译 Java 文件:
javac TcpServer.java javac TcpClient1.java javac TcpClient2.java javac TcpClient3.java
-
-
启动服务器:
-
在终端中运行以下命令启动服务器:
java TcpServer
-
服务器将开始监听端口
8888
,等待客户端连接。
-
-
启动客户端:
-
在另一个终端中运行客户端:
java TcpClient1 java TcpClient2 java TcpClient3
-
客户端将连接到服务器并发送消息。
-
- 端口:服务器监听的端口为
8888
。 - 最大客户端数:服务器最多同时处理 2 个客户端连接。
- 等待时间:客户端在等待队列中的最大等待时间为 6 秒,超过该时间后会被丢弃。
===========这是一个mysql服务器模拟===========
服务器启动,等待客户端连接...
当前连接数已满,客户端进入等待队列...
等待空闲处理槽...
等待空闲处理槽...
等待空闲处理槽...
等待空闲处理槽...
连接到服务器...
已发送消息: 我是客户端1
服务器响应: 服务器已收到您的消息。
正在处理客户端请求...
服务器响应: 服务器已收到您的消息:我是客户端1,处理完毕。
接收到结束通信标志,客户端即将退出。
如果客户端 3 等待时间超过 6 秒,则会出现以下消息:
当前连接数已满,客户端进入等待队列...
等待空闲处理槽...
等待空闲处理槽...
连接请求超时,丢弃客户端连接: /127.0.0.1
如果客户端 3 在等待期间有线程空闲,成功从队列中取出并处理,则输出如下:
当前连接数已满,客户端进入等待队列...
等待空闲处理槽...
等待空闲处理槽...
正在处理客户端请求...
服务器响应: 服务器已收到您的消息:我是客户端3,处理完毕。
接收到结束通信标志,客户端即将退出。
- 服务器使用
LinkedBlockingQueue
来管理等待队列和客户端连接。 - 服务器的
ThreadPoolExecutor
确保最多只有MAX_CLIENTS
客户端被并发处理,等待队列提供了有序的客户端连接管理。 - 连接超时机制确保当客户端等待过久时,会自动丢弃该连接,避免资源浪费。
如果没有线程池,服务器需要为每一个客户端连接创建一个独立的线程来处理。这种情况下可能会导致以下问题:
- 每次有一个新的客户端连接时,服务器都会创建一个新的线程。
- 线程的创建和销毁是有成本的,包括内存分配和 CPU 时间的消耗。
- 如果客户端连接数很多,会导致大量的线程被创建,增加了系统的资源开销。
- 线程数量是有限的,如果客户端连接数非常多,服务器可能会耗尽线程资源或超出操作系统对线程数量的限制。
- 这可能导致服务器无法响应新的客户端连接,甚至引发服务器崩溃。
- 操作系统需要为每个线程分配时间片,如果线程数量过多,CPU 会频繁进行线程的上下文切换。
- 上下文切换会带来性能损耗,导致实际的处理效率下降。
- 没有线程池的情况下,服务器无法限制并发处理的客户端数量。
- 如果突然有大量的客户端连接,服务器可能因为资源不足而被压垮。
-
服务器需要手动管理每个线程的生命周期,包括处理线程终止、异常等情况
-
代码的复杂性和维护成本会增加。
线程池能够有效避免上述问题:
-
线程复用
- 线程池会重复使用已经创建的线程,而不是为每个客户端创建新线程,从而减少了线程创建和销毁的开销。
-
限制并发
- 线程池可以通过设置最大线程数量来限制同时处理的客户端数量,避免资源耗尽。
-
避免频繁切换
- 通过限制线程数量,减少了线程之间的上下文切换,提高了处理效率。
-
简化管理
- 线程池自动管理线程的生命周期,开发者无需手动处理线程的创建和销毁,简化了代码逻辑。
如果没有线程池,服务器的并发性能会受到严重影响,可能会因为资源耗尽而导致崩溃。而线程池能够通过限制并发数量、复用线程和减少开销,显著提高服务器的性能和稳定性。因此,在需要处理大量客户端连接的情况下,线程池是服务器开发中的一个最佳实践。
该项目采用 MIT 许可证