1. 异常处理
- 网络抖动处理:
- 设置合理的超时时间:在
ServerSocket
和Socket
层面设置合适的超时时间。例如,对于ServerSocket
接收连接的操作,通过serverSocket.setSoTimeout(timeout)
设置超时时间,当网络抖动导致长时间无连接请求时,能及时抛出SocketTimeoutException
,在捕获该异常后可以进行适当的日志记录并继续等待连接。
- 心跳机制:在客户端和服务器之间建立心跳机制。服务器端定时向已连接的客户端发送心跳包,例如通过在
OutputStream
中写入特定标识信息,如PrintWriter out = new PrintWriter(socket.getOutputStream(), true); out.println("HEARTBEAT");
。客户端接收到心跳包后响应,若服务器端在一定时间内未收到响应,则认为连接可能出现问题,关闭该连接并清理相关资源。
- NAT穿透异常处理:
- 使用UDP打洞:如果网络中存在NAT设备,可采用UDP打洞技术。服务器作为中间协调者,告知客户端彼此的公网IP和端口信息。客户端双方同时向对方的公网IP和端口发送UDP包,利用NAT设备的端口映射机制实现连接。在Java中,使用
DatagramSocket
类实现UDP通信,例如:
DatagramSocket socket = new DatagramSocket();
InetAddress address = InetAddress.getByName("对方公网IP");
byte[] sendData = "请求连接".getBytes();
DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, address,对方端口);
socket.send(sendPacket);
- STUN/TURN服务器辅助:可以借助STUN(Session Traversal Utilities for NAT)或TURN(Traversal Using Relays around NAT)服务器。STUN服务器帮助客户端获取自身的公网IP和端口;TURN服务器在NAT穿透失败时作为中继,转发客户端之间的数据。在Java中,可以使用开源的库如
ice4j
来与STUN/TURN服务器交互。
2. 连接复用
- 线程池管理连接:
- 创建一个线程池来处理客户端连接。使用
ThreadPoolExecutor
类,例如:
int corePoolSize = 10;
int maximumPoolSize = 100;
long keepAliveTime = 10L;
TimeUnit unit = TimeUnit.SECONDS;
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(100);
ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
- 当
ServerSocket
接收到新连接时,将处理该连接的任务提交到线程池。例如:
ServerSocket serverSocket = new ServerSocket(port);
while (true) {
Socket socket = serverSocket.accept();
executor.submit(new ClientHandler(socket));
}
ClientHandler
类实现Runnable
接口,在线程池中执行对客户端连接的处理逻辑,如读取和写入数据等。这样可以避免为每个连接创建新线程带来的资源开销,实现连接复用。
- 连接池技术:
- 对于数据库等外部资源的连接,可以使用连接池技术,如
HikariCP
。首先引入HikariCP
依赖,然后在服务器启动时初始化连接池:
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/yourdatabase");
config.setUsername("username");
config.setPassword("password");
HikariDataSource dataSource = new HikariDataSource(config);
- 在处理客户端请求需要访问数据库时,从连接池中获取连接,使用完毕后将连接归还到连接池,而不是频繁创建和销毁数据库连接,提高资源利用效率和服务器的稳定性。
3. 其他设计思路
- 日志记录:
- 使用日志框架如
Log4j
或SLF4J
记录服务器运行过程中的关键信息、异常情况等。例如,在捕获到网络异常时记录详细的异常堆栈信息,便于后续排查问题。配置Log4j
时,在log4j.properties
文件中设置日志输出级别、输出路径等,如:
log4j.rootLogger=info,stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
- 资源管理:
- 及时关闭不再使用的
Socket
连接、输入输出流等资源。在finally
块中确保资源的正确关闭,例如:
Socket socket = null;
InputStream inputStream = null;
try {
socket = serverSocket.accept();
inputStream = socket.getInputStream();
// 处理数据
} catch (IOException e) {
// 异常处理
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
// 关闭异常处理
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
// 关闭异常处理
}
}
}
- 对于其他资源如文件句柄等也遵循类似的资源管理原则,避免资源泄漏导致服务器运行不稳定。