Java NIO实战:开发高性能聊天室(原生NIO+Netty优化版)
·
Java NIO(New I/O)是Java高性能网络编程的核心,而Netty是基于NIO的业界最流行的高性能网络应用框架——它解决了原生NIO的空轮询Bug、代码复杂、半包/粘包处理麻烦等痛点,被广泛应用于聊天室、RPC框架、游戏服务器等场景。
本文从零开始,先实现原生NIO版聊天室(理解NIO核心思想),再实现Netty优化版聊天室(解决原生NIO痛点,生产级可用),涵盖:群聊/私聊、用户上线/下线通知、编解码器解决半包/粘包、心跳检测、断线重连。
一、原生NIO核心组件回顾
原生NIO的核心是Selector(选择器)、Channel(通道)、Buffer(缓冲区),三者配合实现非阻塞I/O:
- Selector:一个线程可以监控多个Channel的事件(连接、读、写),避免了多线程的资源开销;
- Channel:双向通道,可以读也可以写,非阻塞;
- Buffer:缓冲区,数据的载体,Channel读写数据都要经过Buffer。
二、实战1:原生NIO版聊天室(理解核心思想)
我们先实现一个简化版原生NIO聊天室,功能:用户连接、群聊、用户上线/下线通知。
1. 项目结构
nio-chatroom
├── src/main/java/com/example/nio
│ ├── NioChatServer.java # 服务端
│ └── NioChatClient.java # 客户端
└── pom.xml
2. 原生NIO服务端(NioChatServer.java)
package com.example.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* 原生NIO聊天室服务端
*/
public class NioChatServer {
private static final int PORT = 8888;
// 存储所有在线的Channel(ConcurrentHashMap保证线程安全)
private static final ConcurrentHashMap<SocketChannel, String> onlineUsers = new ConcurrentHashMap<>();
private Selector selector;
private ServerSocketChannel serverSocketChannel;
public static void main(String[] args) {
new NioChatServer().start();
}
public void start() {
try {
// 1. 打开Selector
selector = Selector.open();
// 2. 打开ServerSocketChannel
serverSocketChannel = ServerSocketChannel.open();
// 3. 绑定端口
serverSocketChannel.bind(new InetSocketAddress(PORT));
// 4. 设置为非阻塞模式
serverSocketChannel.configureBlocking(false);
// 5. 将ServerSocketChannel注册到Selector,监听ACCEPT事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("[NIO服务端] 启动成功,监听端口:" + PORT);
// 6. 循环处理Selector上的事件
while (true) {
// 阻塞等待事件发生(原生NIO有空轮询Bug,Netty解决了这个问题)
int readyChannels = selector.select();
if (readyChannels == 0) continue;
// 获取所有就绪的SelectionKey
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectedKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
// 处理完后必须移除,否则会重复处理
iterator.remove();
try {
// 处理ACCEPT事件(新连接)
if (key.isAcceptable()) {
handleAccept(key);
}
// 处理READ事件(读取消息)
else if (key.isReadable()) {
handleRead(key);
}
} catch (IOException e) {
// 客户端异常断开
handleDisconnect(key);
}
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭资源
try {
if (selector != null) selector.close();
if (serverSocketChannel != null) serverSocketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 处理ACCEPT事件:接受新连接
*/
private void handleAccept(SelectionKey key) throws IOException {
ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
SocketChannel clientChannel = serverChannel.accept();
clientChannel.configureBlocking(false);
// 注册到Selector,监听READ事件
clientChannel.register(selector, SelectionKey.OP_READ);
// 给新用户分配一个用户名(简化版:User+时间戳)
String username = "User" + System.currentTimeMillis();
onlineUsers.put(clientChannel, username);
System.out.println("[新连接] 用户 " + username + " 上线了");
// 广播用户上线通知
broadcastMessage("[系统] 用户 " + username + " 上线了", null);
}
/**
* 处理READ事件:读取客户端消息并广播
*/
private void handleRead(SelectionKey key) throws IOException {
SocketChannel clientChannel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = clientChannel.read(buffer);
if (bytesRead == -1) {
// 客户端正常断开
handleDisconnect(key);
return;
}
// 切换Buffer为读模式
buffer.flip();
String message = StandardCharsets.UTF_8.decode(buffer).toString().trim();
String username = onlineUsers.get(clientChannel);
System.out.println("[收到消息] " + username + ": " + message);
// 广播消息
broadcastMessage(username + ": " + message, clientChannel);
}
/**
* 处理客户端断开
*/
private void handleDisconnect(SelectionKey key) {
SocketChannel clientChannel = (SocketChannel) key.channel();
String username = onlineUsers.remove(clientChannel);
if (username != null) {
System.out.println("[断开连接] 用户 " + username + " 下线了");
// 广播用户下线通知
broadcastMessage("[系统] 用户 " + username + " 下线了", null);
}
// 取消SelectionKey
key.cancel();
try {
clientChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 广播消息给所有在线用户
* @param excludeChannel 排除的Channel(比如发送者自己)
*/
private void broadcastMessage(String message, SocketChannel excludeChannel) {
ByteBuffer buffer = StandardCharsets.UTF_8.encode(message + "\n");
for (SocketChannel channel : onlineUsers.keySet()) {
if (channel != excludeChannel && channel.isOpen()) {
try {
channel.write(buffer);
// 重置Buffer的position,否则下一个Channel读不到数据
buffer.rewind();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
3. 原生NIO客户端(NioChatClient.java)
package com.example.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
/**
* 原生NIO聊天室客户端
*/
public class NioChatClient {
private static final String SERVER_HOST = "localhost";
private static final int SERVER_PORT = 8888;
private SocketChannel socketChannel;
public static void main(String[] args) {
new NioChatClient().start();
}
public void start() {
try {
// 1. 打开SocketChannel
socketChannel = SocketChannel.open();
// 2. 连接服务器
socketChannel.connect(new InetSocketAddress(SERVER_HOST, SERVER_PORT));
socketChannel.configureBlocking(false);
System.out.println("[NIO客户端] 已连接到服务器");
// 3. 启动一个线程读取服务器消息
new Thread(this::readServerMessages).start();
// 4. 主线程读取用户输入并发送
Scanner scanner = new Scanner(System.in);
while (true) {
String message = scanner.nextLine();
if ("exit".equalsIgnoreCase(message)) {
break;
}
sendMessage(message);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (socketChannel != null) socketChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 读取服务器消息
*/
private void readServerMessages() {
ByteBuffer buffer = ByteBuffer.allocate(1024);
try {
while (socketChannel.isOpen()) {
int bytesRead = socketChannel.read(buffer);
if (bytesRead == -1) {
System.out.println("[客户端] 服务器断开连接");
break;
}
if (bytesRead > 0) {
buffer.flip();
String message = StandardCharsets.UTF_8.decode(buffer).toString().trim();
System.out.println(message);
buffer.clear();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 发送消息到服务器
*/
private void sendMessage(String message) {
try {
ByteBuffer buffer = StandardCharsets.UTF_8.encode(message);
socketChannel.write(buffer);
} catch (IOException e) {
e.printStackTrace();
}
}
}
4. 原生NIO的痛点(为什么要用Netty?)
- 空轮询Bug:Selector的
select()方法在某些情况下会立即返回0,导致CPU 100%; - 代码复杂:需要自己处理SelectionKey、Buffer的读写切换、异常断开;
- 半包/粘包问题:原生NIO没有自带编解码器,需要自己处理(比如消息长度前缀、分隔符);
- 线程模型复杂:需要自己设计线程模型,主从Reactor多线程模型实现起来很麻烦;
- Buffer难用:NIO的Buffer是固定大小的,读写切换麻烦,没有Netty的ByteBuf好用。
三、实战2:Netty优化版聊天室(生产级可用)
Netty是基于NIO的高性能网络应用框架,完美解决了原生NIO的痛点,我们用Netty实现一个功能完整的聊天室:群聊/私聊、用户上线/下线通知、编解码器解决半包/粘包、心跳检测、断线重连。
1. 引入依赖(pom.xml)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>netty-chatroom</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<netty.version>4.1.104.Final</netty.version>
</properties>
<dependencies>
<!-- Netty核心 -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>${netty.version}</version>
</dependency>
</dependencies>
</project>
2. 项目结构
netty-chatroom
├── src/main/java/com/example/netty
│ ├── protocol # 协议(消息类型)
│ ├── codec # 编解码器
│ ├── handler # ChannelHandler
│ ├── NettyChatServer.java # 服务端
│ └── NettyChatClient.java # 客户端
└── pom.xml
3. 协议设计(消息类型)
我们用简单的文本协议,消息格式:[消息类型]|[内容],比如:
LOGIN|User123:用户登录;GROUP|大家好:群聊;PRIVATE|User456|你好:私聊;SYSTEM|用户User123上线了:系统通知;HEARTBEAT|:心跳包。
package com.example.netty.protocol;
/**
* 消息类型枚举
*/
public enum MessageType {
LOGIN, // 登录
GROUP, // 群聊
PRIVATE, // 私聊
SYSTEM, // 系统通知
HEARTBEAT // 心跳
}
/**
* 消息实体
*/
public class ChatMessage {
private MessageType type;
private String from; // 发送者
private String to; // 接收者(私聊用)
private String content; // 内容
public ChatMessage() {}
public ChatMessage(MessageType type, String from, String to, String content) {
this.type = type;
this.from = from;
this.to = to;
this.content = content;
}
// Getter和Setter
public MessageType getType() { return type; }
public void setType(MessageType type) { this.type = type; }
public String getFrom() { return from; }
public void setFrom(String from) { this.from = from; }
public String getTo() { return to; }
public void setTo(String to) { this.to = to; }
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
// 序列化为字符串:[类型]|[发送者]|[接收者]|[内容]
public String serialize() {
return type.name() + "|" + (from == null ? "" : from) + "|" + (to == null ? "" : to) + "|" + (content == null ? "" : content);
}
// 反序列化
public static ChatMessage deserialize(String str) {
String[] parts = str.split("\\|", 4);
ChatMessage message = new ChatMessage();
message.setType(MessageType.valueOf(parts[0]));
message.setFrom(parts[1].isEmpty() ? null : parts[1]);
message.setTo(parts[2].isEmpty() ? null : parts[2]);
message.setContent(parts[3].isEmpty() ? null : parts[3]);
return message;
}
}
4. 编解码器(解决半包/粘包)
Netty自带LineBasedFrameDecoder(基于换行符的帧解码器)和StringEncoder/StringDecoder,我们用换行符作为消息分隔符,解决半包/粘包问题。
5. Netty服务端(NettyChatServer.java)
package com.example.netty;
import com.example.netty.codec.ChatMessageDecoder;
import com.example.netty.codec.ChatMessageEncoder;
import com.example.netty.handler.ChatServerHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
/**
* Netty聊天室服务端
* 主从Reactor多线程模型:
* - bossGroup:负责接受新连接
* - workerGroup:负责处理连接的I/O事件
*/
public class NettyChatServer {
private static final int PORT = 8888;
public static void main(String[] args) {
// 1. 创建bossGroup和workerGroup
EventLoopGroup bossGroup = new NioEventLoopGroup(1); // bossGroup只有1个线程
EventLoopGroup workerGroup = new NioEventLoopGroup(); // workerGroup默认CPU核心数*2
try {
// 2. 创建ServerBootstrap
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class) // 指定Channel类型
.option(ChannelOption.SO_BACKLOG, 1024) // 服务端接受连接的队列大小
.childOption(ChannelOption.SO_KEEPALIVE, true) // 开启TCP Keep-Alive
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
// 1. 空闲状态检测:60秒没有读事件,触发IdleStateEvent
pipeline.addLast(new IdleStateHandler(60, 0, 0, TimeUnit.SECONDS));
// 2. 基于换行符的帧解码器:解决半包/粘包
pipeline.addLast(new LineBasedFrameDecoder(1024 * 10));
// 3. String编解码器
pipeline.addLast(new StringDecoder(StandardCharsets.UTF_8));
pipeline.addLast(new StringEncoder(StandardCharsets.UTF_8));
// 4. 自定义业务Handler
pipeline.addLast(new ChatServerHandler());
}
});
// 3. 绑定端口,启动服务
ChannelFuture future = bootstrap.bind(PORT).sync();
System.out.println("[Netty服务端] 启动成功,监听端口:" + PORT);
// 4. 等待服务端Channel关闭
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 5. 优雅关闭EventLoopGroup
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
6. 服务端业务Handler(ChatServerHandler.java)
package com.example.netty.handler;
import com.example.netty.protocol.ChatMessage;
import com.example.netty.protocol.MessageType;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 服务端业务Handler
*/
public class ChatServerHandler extends SimpleChannelInboundHandler<String> {
// ChannelGroup:管理所有在线的Channel,方便广播
private static final ChannelGroup onlineChannels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
// 存储用户名和Channel的映射
private static final Map<String, Channel> userChannelMap = new ConcurrentHashMap<>();
private static final Map<Channel, String> channelUserMap = new ConcurrentHashMap<>();
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
ChatMessage message = ChatMessage.deserialize(msg);
switch (message.getType()) {
case LOGIN:
handleLogin(ctx, message);
break;
case GROUP:
handleGroup(ctx, message);
break;
case PRIVATE:
handlePrivate(ctx, message);
break;
case HEARTBEAT:
// 心跳包,不做处理
break;
}
}
/**
* 处理登录
*/
private void handleLogin(ChannelHandlerContext ctx, ChatMessage message) {
String username = message.getContent();
if (userChannelMap.containsKey(username)) {
// 用户名已存在
ChatMessage errorMsg = new ChatMessage(MessageType.SYSTEM, "系统", null, "用户名已存在");
ctx.writeAndFlush(errorMsg.serialize() + "\n");
return;
}
// 登录成功
Channel channel = ctx.channel();
onlineChannels.add(channel);
userChannelMap.put(username, channel);
channelUserMap.put(channel, username);
System.out.println("[登录] 用户 " + username + " 上线了");
// 广播用户上线通知
ChatMessage systemMsg = new ChatMessage(MessageType.SYSTEM, "系统", null, "用户 " + username + " 上线了");
broadcastMessage(systemMsg, null);
}
/**
* 处理群聊
*/
private void handleGroup(ChannelHandlerContext ctx, ChatMessage message) {
String username = channelUserMap.get(ctx.channel());
if (username == null) return;
message.setFrom(username);
System.out.println("[群聊] " + username + ": " + message.getContent());
broadcastMessage(message, ctx.channel());
}
/**
* 处理私聊
*/
private void handlePrivate(ChannelHandlerContext ctx, ChatMessage message) {
String username = channelUserMap.get(ctx.channel());
if (username == null) return;
message.setFrom(username);
Channel toChannel = userChannelMap.get(message.getTo());
if (toChannel != null && toChannel.isActive()) {
System.out.println("[私聊] " + username + " -> " + message.getTo() + ": " + message.getContent());
toChannel.writeAndFlush(message.serialize() + "\n");
} else {
// 对方不在线
ChatMessage errorMsg = new ChatMessage(MessageType.SYSTEM, "系统", null, "用户 " + message.getTo() + " 不在线");
ctx.writeAndFlush(errorMsg.serialize() + "\n");
}
}
/**
* 广播消息
*/
private void broadcastMessage(ChatMessage message, Channel excludeChannel) {
String msgStr = message.serialize() + "\n";
for (Channel channel : onlineChannels) {
if (channel != excludeChannel && channel.isActive()) {
channel.writeAndFlush(msgStr);
}
}
}
@Override
public void channelInactive(ChannelHandlerContext ctx) {
// 客户端断开连接
String username = channelUserMap.remove(ctx.channel());
if (username != null) {
userChannelMap.remove(username);
onlineChannels.remove(ctx.channel());
System.out.println("[断开] 用户 " + username + " 下线了");
// 广播用户下线通知
ChatMessage systemMsg = new ChatMessage(MessageType.SYSTEM, "系统", null, "用户 " + username + " 下线了");
broadcastMessage(systemMsg, null);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
7. Netty客户端(NettyChatClient.java)
package com.example.netty;
import com.example.netty.handler.ChatClientHandler;
import com.example.netty.protocol.ChatMessage;
import com.example.netty.protocol.MessageType;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;
/**
* Netty聊天室客户端
*/
public class NettyChatClient {
private static final String SERVER_HOST = "localhost";
private static final int SERVER_PORT = 8888;
private static Channel channel;
private static String username;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("请输入用户名:");
username = scanner.nextLine().trim();
// 启动客户端
new NettyChatClient().start(username);
// 读取用户输入并发送
while (true) {
String input = scanner.nextLine().trim();
if ("exit".equalsIgnoreCase(input)) {
channel.close();
break;
}
if (input.startsWith("@")) {
// 私聊:@User456 你好
int spaceIndex = input.indexOf(" ");
if (spaceIndex != -1) {
String toUser = input.substring(1, spaceIndex);
String content = input.substring(spaceIndex + 1);
ChatMessage message = new ChatMessage(MessageType.PRIVATE, username, toUser, content);
sendMessage(message);
}
} else {
// 群聊
ChatMessage message = new ChatMessage(MessageType.GROUP, username, null, input);
sendMessage(message);
}
}
}
public void start(String username) {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
// 1. 空闲状态检测:30秒没有写事件,发送心跳包
pipeline.addLast(new IdleStateHandler(0, 30, 0, TimeUnit.SECONDS));
// 2. 基于换行符的帧解码器
pipeline.addLast(new LineBasedFrameDecoder(1024 * 10));
// 3. String编解码器
pipeline.addLast(new StringDecoder(StandardCharsets.UTF_8));
pipeline.addLast(new StringEncoder(StandardCharsets.UTF_8));
// 4. 自定义业务Handler
pipeline.addLast(new ChatClientHandler(username));
}
});
// 连接服务器
ChannelFuture future = bootstrap.connect(SERVER_HOST, SERVER_PORT).sync();
channel = future.channel();
System.out.println("[Netty客户端] 已连接到服务器");
// 发送登录消息
ChatMessage loginMsg = new ChatMessage(MessageType.LOGIN, null, null, username);
sendMessage(loginMsg);
// 等待客户端Channel关闭
channel.closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
group.shutdownGracefully();
}
}
public static void sendMessage(ChatMessage message) {
if (channel != null && channel.isActive()) {
channel.writeAndFlush(message.serialize() + "\n");
}
}
}
8. 客户端业务Handler(ChatClientHandler.java)
package com.example.netty.handler;
import com.example.netty.NettyChatClient;
import com.example.netty.protocol.ChatMessage;
import com.example.netty.protocol.MessageType;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
/**
* 客户端业务Handler
*/
public class ChatClientHandler extends SimpleChannelInboundHandler<String> {
private final String username;
public ChatClientHandler(String username) {
this.username = username;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
ChatMessage message = ChatMessage.deserialize(msg);
switch (message.getType()) {
case SYSTEM:
System.out.println("[系统] " + message.getContent());
break;
case GROUP:
System.out.println("[群聊] " + message.getFrom() + ": " + message.getContent());
break;
case PRIVATE:
System.out.println("[私聊] " + message.getFrom() + ": " + message.getContent());
break;
}
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
if (event.state() == IdleState.WRITER_IDLE) {
// 30秒没有写事件,发送心跳包
ChatMessage heartbeatMsg = new ChatMessage(MessageType.HEARTBEAT, null, null, null);
ctx.writeAndFlush(heartbeatMsg.serialize() + "\n");
}
}
}
@Override
public void channelInactive(ChannelHandlerContext ctx) {
System.out.println("[客户端] 与服务器断开连接,尝试重连...");
// 断线重连:5秒后重连
ctx.channel().eventLoop().schedule(() -> {
new NettyChatClient().start(username);
}, 5, TimeUnit.SECONDS);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
四、Netty的核心优化点
- 主从Reactor多线程模型:bossGroup负责接受新连接,workerGroup负责处理I/O事件,性能最优;
- ByteBuf:比NIO的Buffer好用,可读写、可扩容、引用计数复用;
- 编解码器:自带
LineBasedFrameDecoder、LengthFieldBasedFrameDecoder,解决半包/粘包问题; - 心跳检测:
IdleStateHandler实现心跳检测,防止连接假死; - 断线重连:客户端断线后自动重连;
- ChannelGroup:管理所有在线Channel,方便广播;
- 优雅关闭:
shutdownGracefully()优雅关闭EventLoopGroup,不丢失请求。
五、总结
本文从零开始,先实现了原生NIO版聊天室(理解NIO核心思想:Selector、Channel、Buffer),再实现了Netty优化版聊天室(解决原生NIO痛点,生产级可用),涵盖了:
- 群聊/私聊;
- 用户上线/下线通知;
- 编解码器解决半包/粘包;
- 心跳检测防止连接假死;
- 断线重连;
- 主从Reactor多线程模型。
Netty是Java高性能网络编程的事实标准,希望这篇文章能帮你深入理解Netty的核心思想和实战应用,有问题可以在评论区交流!
更多推荐

所有评论(0)