1. 使用MINA实现一个完整的聊天室服务器
答案:
这是一个包含用户管理、消息广播、私聊功能的完整聊天室实现。
服务端实现
java
// 消息协议
public class ChatMessage implements Serializable {
private String type; // LOGIN, LOGOUT, PUBLIC, PRIVATE
private String sender;
private String receiver; // 私聊接收者
private String content;
private long timestamp;
// 构造函数和getter/setter省略
}
// 聊天室服务器
public class ChatServer {
private static final int PORT = 9123;
private static final Map<String, IoSession> users =
new ConcurrentHashMap<>();
public static void main(String[] args) throws Exception {
NioSocketAcceptor acceptor = new NioSocketAcceptor();
// 配置
acceptor.getSessionConfig().setReadBufferSize(2048);
acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 300);
// 过滤器链
acceptor.getFilterChain().addLast("logger",
new LoggingFilter());
acceptor.getFilterChain().addLast("codec",
new ProtocolCodecFilter(
new ObjectSerializationCodecFactory()));
// 业务处理器
acceptor.setHandler(new ChatServerHandler());
acceptor.bind(new InetSocketAddress(PORT));
System.out.println("聊天服务器启动在端口: " + PORT);
}
// 聊天服务器处理器
static class ChatServerHandler extends IoHandlerAdapter {
@Override
public void sessionCreated(IoSession session) throws Exception {
System.out.println("新连接: " + session.getRemoteAddress());
}
@Override
public void messageReceived(IoSession session, Object message)
throws Exception {
ChatMessage msg = (ChatMessage) message;
switch (msg.getType()) {
case "LOGIN":
handleLogin(session, msg);
break;
case "LOGOUT":
handleLogout(session, msg);
break;
case "PUBLIC":
handlePublicMessage(session, msg);
break;
case "PRIVATE":
handlePrivateMessage(session, msg);
break;
default:
session.write(createErrorMessage("未知消息类型"));
}
}
private void handleLogin(IoSession session, ChatMessage msg) {
String username = msg.getSender();
// 检查用户名是否已存在
if (users.containsKey(username)) {
session.write(createErrorMessage("用户名已存在"));
return;
}
// 注册用户
users.put(username, session);
session.setAttribute("username", username);
// 通知所有用户
ChatMessage notification = new ChatMessage();
notification.setType("SYSTEM");
notification.setContent(username + " 加入了聊天室");
notification.setTimestamp(System.currentTimeMillis());
broadcast(notification, null);
// 发送欢迎消息
ChatMessage welcome = new ChatMessage();
welcome.setType("SYSTEM");
welcome.setContent("欢迎 " + username + "! 当前在线: "
+ users.size() + " 人");
session.write(welcome);
// 发送在线用户列表
sendUserList(session);
}
private void handleLogout(IoSession session, ChatMessage msg) {
String username = (String) session.getAttribute("username");
if (username != null) {
users.remove(username);
ChatMessage notification = new ChatMessage();
notification.setType("SYSTEM");
notification.setContent(username + " 离开了聊天室");
notification.setTimestamp(System.currentTimeMillis());
broadcast(notification, session);
}
}
private void handlePublicMessage(IoSession session, ChatMessage msg) {
String username = (String) session.getAttribute("username");
if (username == null) {
session.write(createErrorMessage("请先登录"));
return;
}
msg.setSender(username);
msg.setTimestamp(System.currentTimeMillis());
broadcast(msg, null);
}
private void handlePrivateMessage(IoSession session, ChatMessage msg) {
String username = (String) session.getAttribute("username");
if (username == null) {
session.write(createErrorMessage("请先登录"));
return;
}
IoSession receiverSession = users.get(msg.getReceiver());
if (receiverSession == null) {
session.write(createErrorMessage("用户 " + msg.getReceiver()
+ " 不在线"));
return;
}
msg.setSender(username);
msg.setTimestamp(System.currentTimeMillis());
receiverSession.write(msg);
// 给发送者确认
ChatMessage confirm = new ChatMessage();
confirm.setType("SYSTEM");
confirm.setContent("已发送给 " + msg.getReceiver());
session.write(confirm);
}
private void broadcast(ChatMessage message, IoSession exclude) {
for (IoSession session : users.values()) {
if (session != exclude && session.isConnected()) {
session.write(message);
}
}
}
private void sendUserList(IoSession session) {
ChatMessage userList = new ChatMessage();
userList.setType("USER_LIST");
userList.setContent(String.join(",", users.keySet()));
session.write(userList);
}
private ChatMessage createErrorMessage(String error) {
ChatMessage msg = new ChatMessage();
msg.setType("ERROR");
msg.setContent(error);
msg.setTimestamp(System.currentTimeMillis());
return msg;
}
@Override
public void sessionClosed(IoSession session) throws Exception {
String username = (String) session.getAttribute("username");
if (username != null) {
users.remove(username);
ChatMessage notification = new ChatMessage();
notification.setType("SYSTEM");
notification.setContent(username + " 断开连接");
notification.setTimestamp(System.currentTimeMillis());
broadcast(notification, session);
}
}
@Override
public void sessionIdle(IoSession session, IdleStatus status)
throws Exception {
System.out.println("连接空闲,关闭: " + session.getRemoteAddress());
session.closeNow();
}
@Override
public void exceptionCaught(IoSession session, Throwable cause)
throws Exception {
cause.printStackTrace();
session.closeNow();
}
}
}
客户端实现
java
public class ChatClient {
private IoSession session;
private String username;
public void connect(String host, int port, String username)
throws Exception {
this.username = username;
NioSocketConnector connector = new NioSocketConnector();
connector.getFilterChain().addLast("codec",
new ProtocolCodecFilter(
new ObjectSerializationCodecFactory()));
connector.setHandler(new ChatClientHandler());
ConnectFuture future = connector.connect(
new InetSocketAddress(host, port)
);
future.awaitUninterruptibly();
session = future.getSession();
// 发送登录消息
ChatMessage loginMsg = new ChatMessage();
loginMsg.setType("LOGIN");
loginMsg.setSender(username);
session.write(loginMsg);
}
public void sendPublicMessage(String content) {
ChatMessage msg = new ChatMessage();
msg.setType("PUBLIC");
msg.setContent(content);
session.write(msg);
}
public void sendPrivateMessage(String receiver, String content) {
ChatMessage msg = new ChatMessage();
msg.setType("PRIVATE");
msg.setReceiver(receiver);
msg.setContent(content);
session.write(msg);
}
public void disconnect() {
if (session != null) {
ChatMessage logoutMsg = new ChatMessage();
logoutMsg.setType("LOGOUT");
session.write(logoutMsg).awaitUninterruptibly();
session.closeNow();
}
}
// 客户端处理器
class ChatClientHandler extends IoHandlerAdapter {
@Override
public void messageReceived(IoSession session, Object message)
throws Exception {
ChatMessage msg = (ChatMessage) message;
switch (msg.getType()) {
case "SYSTEM":
System.out.println("[系统] " + msg.getContent());
break;
case "PUBLIC":
System.out.println("[" + msg.getSender() + "] "
+ msg.getContent());
break;
case "PRIVATE":
System.out.println("[私聊-" + msg.getSender() + "] "
+ msg.getContent());
break;
case "USER_LIST":
System.out.println("[在线用户] " + msg.getContent());
break;
case "ERROR":
System.out.println("[错误] " + msg.getContent());
break;
}
}
@Override
public void exceptionCaught(IoSession session, Throwable cause)
throws Exception {
cause.printStackTrace();
}
}
}
功能特点:
- 用户登录/登出管理
- 公共消息广播
- 私聊功能
- 在线用户列表
- 连接空闲检测
- 异常处理
2. 如何在生产环境中优化MINA服务器的性能?
答案:
1. 线程池优化
java
// 自定义IoProcessor线程池
int processorCount = Runtime.getRuntime().availableProcessors() * 2;
NioSocketAcceptor acceptor = new NioSocketAcceptor(processorCount);
// 添加业务线程池,避免阻塞I/O线程
ThreadPoolExecutor businessExecutor = new ThreadPoolExecutor(
20, // 核心线程数
100, // 最大线程数
60L, // 空闲超时
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1000), // 队列大小
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
ExecutorFilter executorFilter = new ExecutorFilter(businessExecutor);
acceptor.getFilterChain().addLast("executor", executorFilter);
2. 缓冲区优化
java
SocketSessionConfig config = acceptor.getSessionConfig();
// 优化缓冲区大小
config.setReadBufferSize(8192); // 8KB读缓冲
config.setReceiveBufferSize(65536); // 64KB接收缓冲
config.setSendBufferSize(65536); // 64KB发送缓冲
// 启用TCP_NODELAY,减少延迟
config.setTcpNoDelay(true);
// 启用SO_KEEPALIVE
config.setKeepAlive(true);
// 设置SO_REUSEADDR
config.setReuseAddress(true);
3. 内存管理优化
java
// 使用直接内存
IoBuffer.setUseDirectBuffer(true);
// 启用内存池
IoBuffer.setAllocator(new CachedBufferAllocator());
// 自定义缓冲区分配器
public class CustomBufferAllocator implements IoBufferAllocator {
private final Queue<IoBuffer> pool = new ConcurrentLinkedQueue<>();
private final int bufferSize = 8192;
private final int maxPoolSize = 1000;
@Override
public IoBuffer allocate(int capacity, boolean direct) {
if (capacity == bufferSize) {
IoBuffer buffer = pool.poll();
if (buffer != null) {
buffer.clear();
return buffer;
}
}
return IoBuffer.allocate(capacity, direct);
}
public void release(IoBuffer buffer) {
if (buffer.capacity() == bufferSize && pool.size() < maxPoolSize) {
pool.offer(buffer);
}
}
}
4. 编解码优化
java
// 使用高效的序列化方式
// 避免使用Java原生序列化,推荐Protobuf、Kryo等
// Kryo编解码器示例
public class KryoCodecFactory implements ProtocolCodecFactory {
@Override
public ProtocolEncoder getEncoder(IoSession session) {
return new KryoEncoder();
}
@Override
public ProtocolDecoder getDecoder(IoSession session) {
return new KryoDecoder();
}
}
class KryoEncoder implements ProtocolEncoder {
private final Kryo kryo = new Kryo();
@Override
public void encode(IoSession session, Object message,
ProtocolEncoderOutput out) throws Exception {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Output output = new Output(baos);
kryo.writeClassAndObject(output, message);
output.close();
byte[] bytes = baos.toByteArray();
IoBuffer buffer = IoBuffer.allocate(bytes.length + 4);
buffer.putInt(bytes.length);
buffer.put(bytes);
buffer.flip();
out.write(buffer);
}
@Override
public void dispose(IoSession session) {}
}
5. 连接管理优化
java
// 限制最大连接数
public class ConnectionLimitFilter extends IoFilterAdapter {
private final AtomicInteger count = new AtomicInteger(0);
private final int maxConnections = 10000;
@Override
public void sessionCreated(NextFilter nextFilter, IoSession session)
throws Exception {
if (count.incrementAndGet() > maxConnections) {
count.decrementAndGet();
session.closeNow();
return;
}
nextFilter.sessionCreated(session);
}
@Override
public void sessionClosed(NextFilter nextFilter, IoSession session)
throws Exception {
count.decrementAndGet();
nextFilter.sessionClosed(session);
}
}
// 添加连接限制
acceptor.getFilterChain().addFirst("connectionLimit",
new ConnectionLimitFilter());
6. 监控和日志优化
java
// 使用异步日志
acceptor.getFilterChain().addLast("logger", new LoggingFilter() {
@Override
public void sessionCreated(NextFilter nextFilter, IoSession session)
throws Exception {
// 异步记录日志
asyncLog("Session created: " + session.getId());
nextFilter.sessionCreated(session);
}
});
// 定期输出统计信息
ScheduledExecutorService scheduler =
Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(() -> {
IoServiceStatistics stats = acceptor.getStatistics();
System.out.println("当前连接数: " + acceptor.getManagedSessionCount());
System.out.println("累计读取: " + stats.getReadBytes() + " bytes");
System.out.println("累计写入: " + stats.getWrittenBytes() + " bytes");
System.out.println("平均读取速率: " +
stats.getReadBytesThroughput() + " bytes/s");
}, 0, 60, TimeUnit.SECONDS);
7. JVM优化
bash
# JVM启动参数
java -server \
-Xms4g -Xmx4g \ # 堆内存
-XX:+UseG1GC \ # 使用G1垃圾收集器
-XX:MaxGCPauseMillis=200 \ # GC停顿时间目标
-XX:+UseStringDeduplication \ # 字符串去重
-XX:+AlwaysPreTouch \ # 预分配内存
-XX:MaxDirectMemorySize=2g \ # 直接内存大小
-XX:+HeapDumpOnOutOfMemoryError \ # OOM时dump堆
-Dio.netty.leakDetectionLevel=advanced \
-jar mina-server.jar
性能优化建议总结:
- 合理配置线程池大小
- 使用直接内存和内存池
- 选择高效的序列化方式
- 启用TCP_NODELAY减少延迟
- 实施连接数限制和流量控制
- 使用异步日志
- 定期监控和分析性能指标
- 压力测试找出瓶颈
3. MINA服务器的压测方案和性能指标
答案:
压测工具选择
1. Apache JMeter
xml
<!-- JMeter测试计划配置 -->
<TestPlan>
<ThreadGroup>
<num_threads>1000</num_threads> <!-- 并发用户数 -->
<ramp_time>60</ramp_time> <!-- 启动时间 -->
<duration>300</duration> <!-- 测试持续时间 -->
</ThreadGroup>
</TestPlan>
2. 自定义压测客户端
java
public class LoadTestClient {
private static final String HOST = "localhost";
private static final int PORT = 9123;
private static final int CLIENT_COUNT = 1000;
private static final int MESSAGES_PER_CLIENT = 1000;
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(
CLIENT_COUNT
);
CountDownLatch latch = new CountDownLatch(CLIENT_COUNT);
AtomicLong totalTime = new AtomicLong(0);
AtomicLong successCount = new AtomicLong(0);
AtomicLong failureCount = new AtomicLong(0);
long startTime = System.currentTimeMillis();
// 启动客户端
for (int i = 0; i < CLIENT_COUNT; i++) {
final int clientId = i;
executor.submit(() -> {
try {
runClient(clientId, totalTime, successCount,
failureCount);
} catch (Exception e) {
e.printStackTrace();
failureCount.incrementAndGet();
} finally {
latch.countDown();
}
});
}
// 等待所有客户端完成
latch.await();
long endTime = System.currentTimeMillis();
long duration = endTime - startTime;
// 输出统计结果
System.out.println("========== 压测结果 ==========");
System.out.println("客户端数量: " + CLIENT_COUNT);
System.out.println("总请求数: " +
(CLIENT_COUNT * MESSAGES_PER_CLIENT));
System.out.println("成功数: " + successCount.get());
System.out.println("失败数: " + failureCount.get());
System.out.println("总耗时: " + duration + "ms");
System.out.println("TPS: " +
(successCount.get() * 1000 / duration));
System.out.println("平均响应时间: " +
(totalTime.get() / successCount.get()) + "ms");
executor.shutdown();
}
private static void runClient(int clientId, AtomicLong totalTime,
AtomicLong successCount, AtomicLong failureCount)
throws Exception {
NioSocketConnector connector = new NioSocketConnector();
connector.getFilterChain().addLast("codec",
new ProtocolCodecFilter(new TextLineCodecFactory()));
CountDownLatch msgLatch = new CountDownLatch(MESSAGES_PER_CLIENT);
connector.setHandler(new IoHandlerAdapter() {
@Override
public void messageReceived(IoSession session, Object message) {
msgLatch.countDown();
}
});
ConnectFuture future = connector.connect(
new InetSocketAddress(HOST, PORT)
);
future.awaitUninterruptibly();
if (!future.isConnected()) {
failureCount.addAndGet(MESSAGES_PER_CLIENT);
return;
}
IoSession session = future.getSession();
// 发送消息
for (int i = 0; i < MESSAGES_PER_CLIENT; i++) {
long start = System.currentTimeMillis();
session.write("Client-" + clientId + ": Message-" + i);
if (msgLatch.await(5, TimeUnit.SECONDS)) {
totalTime.addAndGet(System.currentTimeMillis() - start);
successCount.incrementAndGet();
} else {
failureCount.incrementAndGet();
}
}
session.closeNow();
connector.dispose();
}
}
关键性能指标
1. TPS (Transactions Per Second)
java
public class TPSMonitor {
private AtomicLong counter = new AtomicLong(0);
private long lastTime = System.currentTimeMillis();
public void recordRequest() {
counter.incrementAndGet();
}
public double getCurrentTPS() {
long current = System.currentTimeMillis();
long count = counter.getAndSet(0);
long duration = current - lastTime;
lastTime = current;
return (count * 1000.0) / duration;
}
}
2. 响应时间监控
java
public class ResponseTimeMonitor {
private final ConcurrentLinkedQueue<Long> responseTimes =
new ConcurrentLinkedQueue<>();
public void recordResponseTime(long time) {
responseTimes.offer(time);
// 保持最近1000个样本
while (responseTimes.size() > 1000) {
responseTimes.poll();
}
}
public Statistics getStatistics() {
List<Long> times = new ArrayList<>(responseTimes);
Collections.sort(times);
double avg = times.stream()
.mapToLong(Long::longValue)
.average()
.orElse(0);
long p50 = times.get(times.size() / 2);
long p95 = times.get((int) (times.size() * 0.95));
long p99 = times.get((int) (times.size() * 0.99));
return new Statistics(avg, p50, p95, p99);
}
}
3. 资源使用监控
java
public class ResourceMonitor {
public void printSystemInfo() {
Runtime runtime = Runtime.getRuntime();
System.out.println("========== 系统资源 ==========");
System.out.println("总内存: " +
(runtime.totalMemory() / 1024 / 1024) + "MB");
System.out.println("空闲内存: " +
(runtime.freeMemory() / 1024 / 1024) + "MB");
System.out.println("已用内存: " +
((runtime.totalMemory() - runtime.freeMemory()) / 1024 / 1024)
+ "MB");
System.out.println("最大内存: " +
(runtime.maxMemory() / 1024 / 1024) + "MB");
System.out.println("可用处理器: " +
runtime.availableProcessors());
ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
System.out.println("活跃线程数: " + threadBean.getThreadCount());
System.out.println("峰值线程数: " + threadBean.getPeakThreadCount());
}
}
压测建议:
- 逐步增加并发,找到系统临界点
- 长时间运行测试,发现内存泄漏
- 监控CPU、内存、网络I/O
- 分析慢查询和瓶颈点
- 进行极限压测,测试系统稳定性
4. MINA项目中常见的坑和解决方案
答案:
坑1: IoBuffer未flip导致数据读取失败
问题:
java
// 错误示例
IoBuffer buffer = IoBuffer.allocate(100);
buffer.put("Hello".getBytes());
// 忘记flip,导致读取失败
out.write(buffer);
解决方案:
java
// 正确做法
IoBuffer buffer = IoBuffer.allocate(100);
buffer.put("Hello".getBytes());
buffer.flip(); // 必须flip,切换到读模式
out.write(buffer);
坑2: Session属性内存泄漏
问题:
java
// 在Session中存储大对象,忘记清理
session.setAttribute("largeData", new byte[10 * 1024 * 1024]);
// Session关闭后,属性没有被清理
解决方案:
java
@Override
public void sessionClosed(IoSession session) throws Exception {
// 清理所有属性
session.removeAttribute("largeData");
session.removeAttribute("userData");
// 或者清空所有属性
Set<Object> keys = session.getAttributeKeys();
for (Object key : keys) {
session.removeAttribute(key);
}
}
坑3: 过滤器顺序错误
问题:
java
// 错误:编解码器在日志过滤器之前
acceptor.getFilterChain().addLast("codec", codecFilter);
acceptor.getFilterChain().addLast("logger", loggingFilter);
// 导致日志记录的是编码后的字节,而不是业务对象
解决方案:
java
// 正确顺序
acceptor.getFilterChain().addLast("logger", loggingFilter);
acceptor.getFilterChain().addLast("codec", codecFilter);
acceptor.getFilterChain().addLast("executor", executorFilter);
// SSL必须在最前面
acceptor.getFilterChain().addFirst("ssl", sslFilter);
坑4: 阻塞操作放在IoHandler中
问题:
java
@Override
public void messageReceived(IoSession session, Object message) {
// 错误:数据库查询阻塞I/O线程
User user = database.queryUser(userId); // 可能很慢
session.write(user);
}
解决方案:
java
// 方案1:使用ExecutorFilter
acceptor.getFilterChain().addLast("executor",
new ExecutorFilter(Executors.newCachedThreadPool()));
// 方案2:手动异步处理
@Override
public void messageReceived(IoSession session, Object message) {
executor.submit(() -> {
try {
User user = database.queryUser(userId);
session.write(user);
} catch (Exception e) {
session.write("查询失败");
}
});
}
坑5: 解码器中粘包处理不当
问题:
java
// 错误:没有保存解码状态
@Override
protected boolean doDecode(IoSession session, IoBuffer in,
ProtocolDecoderOutput out) {
if (in.remaining() < 4) {
return false; // 数据丢失,下次进来又从头开始
}
int length = in.getInt();
// ...
}
解决方案:
java
// 正确:使用mark和reset
@Override
protected boolean doDecode(IoSession session, IoBuffer in,
ProtocolDecoderOutput out) {
in.mark(); // 标记当前位置
if (in.remaining() < 4) {
in.reset(); // 恢复到标记位置
return false;
}
int length = in.getInt();
if (in.remaining() < length) {
in.reset(); // 数据不够,恢复位置
return false;
}
byte[] data = new byte[length];
in.get(data);
out.write(data);
return true;
}
坑6: 忘记处理异常
问题:
java
// 没有实现exceptionCaught
public class MyHandler extends IoHandlerAdapter {
// 异常被吞掉,难以排查问题
}
解决方案:
java
@Override
public void exceptionCaught(IoSession session, Throwable cause) {
// 记录日志
logger.error("Session " + session.getId() + " error", cause);
// 根据异常类型处理
if (cause instanceof IOException) {
// 网络异常,关闭连接
session.close