本文目录导读:

我来为你提供一个完整的Netty聊天室案例,包含服务端和客户端的完整实现。
项目依赖 (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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>netty-chat</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Netty -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.86.Final</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<optional>true</optional>
</dependency>
<!-- Gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
</dependencies>
</project>
消息协议类
package com.example.chat.protocol;
import lombok.Data;
/**
* 聊天消息协议
*/
@Data
public class ChatMessage {
// 消息类型
public static final int TYPE_SYSTEM = 0; // 系统消息
public static final int TYPE_LOGIN = 1; // 登录
public static final int TYPE_LOGOUT = 2; // 登出
public static final int TYPE_CHAT = 3; // 聊天消息
public static final int TYPE_USER_LIST = 4; // 在线用户列表
private int type; // 消息类型
private String from; // 发送者
private String to; // 接收者(null表示广播)
private String content; // 消息内容
private String timestamp; // 时间戳
public ChatMessage() {}
public ChatMessage(int type, String from, String content) {
this.type = type;
this.from = from;
this.content = content;
this.timestamp = String.valueOf(System.currentTimeMillis());
}
public static ChatMessage systemMessage(String content) {
return new ChatMessage(TYPE_SYSTEM, "系统", content);
}
public static ChatMessage chatMessage(String from, String to, String content) {
ChatMessage msg = new ChatMessage(TYPE_CHAT, from, content);
msg.setTo(to);
return msg;
}
}
服务端实现
1 服务端主类
package com.example.chat.server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
import com.google.gson.Gson;
import com.example.chat.protocol.ChatMessage;
public class ChatServer {
private final int port;
private final Gson gson = new Gson();
public ChatServer(int port) {
this.port = port;
}
public void run() throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline()
.addLast("decoder", new StringDecoder(CharsetUtil.UTF_8))
.addLast("encoder", new StringEncoder(CharsetUtil.UTF_8))
.addLast("handler", new ChatServerHandler(gson));
}
});
System.out.println("Chat Server started on port " + port);
ChannelFuture future = bootstrap.bind(port).sync();
future.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws InterruptedException {
int port = args.length > 0 ? Integer.parseInt(args[0]) : 8080;
new ChatServer(port).run();
}
}
2 服务端处理器
package com.example.chat.server;
import com.google.gson.Gson;
import com.example.chat.protocol.ChatMessage;
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.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ChatServerHandler extends SimpleChannelInboundHandler<String> {
// 保存所有连接的Channel
private static final ChannelGroup CHANNEL_GROUP = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
// 保存用户和Channel的对应关系
private static final Map<String, Channel> USER_CHANNEL_MAP = new ConcurrentHashMap<>();
// 保存所有在线用户
private static final Map<String, String> ONLINE_USERS = new ConcurrentHashMap<>();
private final Gson gson;
public ChatServerHandler(Gson gson) {
this.gson = gson;
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) {
// 新Channel加入
CHANNEL_GROUP.add(ctx.channel());
System.out.println("[客户端] " + ctx.channel().remoteAddress() + " 加入连接");
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) {
// Channel移除
Channel channel = ctx.channel();
CHANNEL_GROUP.remove(channel);
// 从用户映射中移除
USER_CHANNEL_MAP.entrySet().removeIf(entry -> entry.getValue() == channel);
// 清除在线用户
String username = getUsernameByChannel(channel);
if (username != null) {
ONLINE_USERS.remove(username);
System.out.println("[客户端] " + username + " 断开连接");
// 广播用户离线消息
ChatMessage logoutMsg = ChatMessage.systemMessage(username + " 已离开聊天室");
broadcast(logoutMsg);
// 广播更新后的用户列表
broadcastUserList();
}
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
try {
ChatMessage chatMessage = gson.fromJson(msg, ChatMessage.class);
Channel channel = ctx.channel();
switch (chatMessage.getType()) {
case ChatMessage.TYPE_LOGIN:
handleLogin(channel, chatMessage);
break;
case ChatMessage.TYPE_CHAT:
handleChat(chatMessage);
break;
case ChatMessage.TYPE_LOGOUT:
handleLogout(channel, chatMessage);
break;
default:
System.out.println("Unknown message type: " + chatMessage.getType());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 处理登录
*/
private void handleLogin(Channel channel, ChatMessage msg) {
String username = msg.getFrom();
// 检查用户名是否已被占用
if (ONLINE_USERS.containsKey(username)) {
ChatMessage errorMsg = ChatMessage.systemMessage("用户名已被占用,请更换用户名");
channel.writeAndFlush(gson.toJson(errorMsg));
channel.close();
return;
}
// 保存用户信息
ONLINE_USERS.put(username, username);
USER_CHANNEL_MAP.put(username, channel);
// 发送登录成功通知给当前用户
ChatMessage successMsg = ChatMessage.systemMessage("登录成功!欢迎 " + username + " 加入聊天室");
channel.writeAndFlush(gson.toJson(successMsg));
// 广播用户加入消息
ChatMessage joinMsg = ChatMessage.systemMessage(username + " 加入了聊天室");
broadcast(joinMsg);
// 广播更新后的在线用户列表
broadcastUserList();
System.out.println("[用户登录] " + username);
}
/**
* 处理聊天消息
*/
private void handleChat(ChatMessage msg) {
String to = msg.getTo();
if (to != null && !to.isEmpty()) {
// 私聊
sendPrivateMessage(msg);
} else {
// 群发
broadcast(msg);
}
}
/**
* 发送私聊消息
*/
private void sendPrivateMessage(ChatMessage msg) {
String to = msg.getTo();
Channel targetChannel = USER_CHANNEL_MAP.get(to);
if (targetChannel != null) {
// 设置时间戳
msg.setTimestamp(getCurrentTime());
// 发送给接收者
targetChannel.writeAndFlush(gson.toJson(msg));
// 同时发送给发送者一份(回显)
Channel fromChannel = USER_CHANNEL_MAP.get(msg.getFrom());
if (fromChannel != null) {
fromChannel.writeAndFlush(gson.toJson(msg));
}
} else {
// 用户不在线
Channel fromChannel = USER_CHANNEL_MAP.get(msg.getFrom());
if (fromChannel != null) {
ChatMessage errorMsg = ChatMessage.systemMessage("用户 " + to + " 不在线");
fromChannel.writeAndFlush(gson.toJson(errorMsg));
}
}
}
/**
* 处理登出
*/
private void handleLogout(Channel channel, ChatMessage msg) {
String username = msg.getFrom();
CHANNEL_GROUP.remove(channel);
USER_CHANNEL_MAP.remove(username);
ONLINE_USERS.remove(username);
// 广播用户离线消息
ChatMessage logoutMsg = ChatMessage.systemMessage(username + " 已离开聊天室");
broadcast(logoutMsg);
// 广播更新后的用户列表
broadcastUserList();
channel.close();
}
/**
* 广播消息给所有在线用户
*/
private void broadcast(ChatMessage msg) {
msg.setTimestamp(getCurrentTime());
String json = gson.toJson(msg);
for (Channel channel : CHANNEL_GROUP) {
if (channel.isActive()) {
channel.writeAndFlush(json);
}
}
}
/**
* 广播在线用户列表
*/
private void broadcastUserList() {
ChatMessage msg = ChatMessage.systemMessage("在线用户列表");
msg.setType(ChatMessage.TYPE_USER_LIST);
msg.setContent(String.join(",", ONLINE_USERS.keySet()));
msg.setTimestamp(getCurrentTime());
String json = gson.toJson(msg);
for (Channel channel : CHANNEL_GROUP) {
if (channel.isActive()) {
channel.writeAndFlush(json);
}
}
}
/**
* 根据Channel获取用户名
*/
private String getUsernameByChannel(Channel channel) {
for (Map.Entry<String, Channel> entry : USER_CHANNEL_MAP.entrySet()) {
if (entry.getValue() == channel) {
return entry.getKey();
}
}
return null;
}
/**
* 获取当前时间
*/
private String getCurrentTime() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return sdf.format(new Date());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
System.out.println("发生异常: " + cause.getMessage());
ctx.close();
}
}
客户端实现
1 客户端主类
package com.example.chat.client;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
import com.google.gson.Gson;
import com.example.chat.protocol.ChatMessage;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class ChatClient {
private final String host;
private final int port;
private final String username;
private final Gson gson = new Gson();
public ChatClient(String host, int port, String username) {
this.host = host;
this.port = port;
this.username = username;
}
public void start() throws InterruptedException {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline()
.addLast("decoder", new StringDecoder(CharsetUtil.UTF_8))
.addLast("encoder", new StringEncoder(CharsetUtil.UTF_8))
.addLast("handler", new ChatClientHandler(gson));
}
});
ChannelFuture future = bootstrap.connect(host, port).sync();
Channel channel = future.channel();
// 发送登录消息
sendLogin(channel);
// 启动读取控制台输入的线程
startConsoleReader(channel);
channel.closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
/**
* 发送登录消息
*/
private void sendLogin(Channel channel) {
ChatMessage loginMsg = new ChatMessage(ChatMessage.TYPE_LOGIN, username, "登录");
channel.writeAndFlush(gson.toJson(loginMsg));
}
/**
* 启动控制台输入监听
*/
private void startConsoleReader(Channel channel) {
Thread thread = new Thread(() -> {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
String line;
System.out.println("聊天室已开启,输入 'quit' 退出,格式: /msg [用户名] [消息] 发送私聊");
while ((line = reader.readLine()) != null) {
if (line.equalsIgnoreCase("quit")) {
sendLogout(channel);
break;
} else if (line.startsWith("/msg")) {
// 私聊命令
processPrivateCommand(channel, line);
} else {
// 群聊消息
Channel currentChannel = channel;
ChatMessage msg = ChatMessage.chatMessage(username, null, line);
currentChannel.writeAndFlush(gson.toJson(msg));
}
}
channel.close();
} catch (Exception e) {
e.printStackTrace();
}
});
thread.setDaemon(true);
thread.start();
}
/**
* 处理私聊命令
*/
private void processPrivateCommand(Channel channel, String command) {
String[] parts = command.split("\\s+", 3);
if (parts.length >= 3) {
String targetUser = parts[1];
String content = parts[2];
ChatMessage msg = ChatMessage.chatMessage(username, targetUser, content);
channel.writeAndFlush(gson.toJson(msg));
} else {
System.out.println("使用格式: /msg [用户名] [消息]");
}
}
/**
* 发送登出消息
*/
private void sendLogout(Channel channel) {
ChatMessage logoutMsg = new ChatMessage(ChatMessage.TYPE_LOGOUT, username, "退出");
channel.writeAndFlush(gson.toJson(logoutMsg));
}
public static void main(String[] args) {
String host = args.length > 0 ? args[0] : "127.0.0.1";
int port = args.length > 1 ? Integer.parseInt(args[1]) : 8080;
String username = args.length > 2 ? args[2] : ("用户" + System.currentTimeMillis() % 1000);
try {
new ChatClient(host, port, username).start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2 客户端处理器
package com.example.chat.client;
import com.google.gson.Gson;
import com.example.chat.protocol.ChatMessage;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
public class ChatClientHandler extends SimpleChannelInboundHandler<String> {
private final Gson gson;
public ChatClientHandler(Gson gson) {
this.gson = gson;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
try {
ChatMessage chatMessage = gson.fromJson(msg, ChatMessage.class);
switch (chatMessage.getType()) {
case ChatMessage.TYPE_SYSTEM:
handleSystemMessage(chatMessage);
break;
case ChatMessage.TYPE_CHAT:
handleChatMessage(chatMessage);
break;
case ChatMessage.TYPE_USER_LIST:
handleUserList(chatMessage);
break;
default:
// 默认显示
System.out.println("收到消息: " + msg);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 处理系统消息
*/
private void handleSystemMessage(ChatMessage msg) {
System.out.println();
System.out.println("【系统】" + msg.getContent());
System.out.print("> ");
}
/**
* 处理聊天消息
*/
private void handleChatMessage(ChatMessage msg) {
String from = msg.getFrom();
String to = msg.getTo();
String content = msg.getContent();
if (to != null && !to.isEmpty()) {
// 私聊消息
System.out.println();
System.out.println("【私聊】" + from + " -> " + to + ": " + content);
} else {
// 群聊消息
System.out.println();
System.out.println("【" + from + "】: " + content);
}
System.out.print("> ");
}
/**
* 处理用户列表
*/
private void handleUserList(ChatMessage msg) {
String users = msg.getContent();
System.out.println();
System.out.println("【当前在线用户】: " + users);
System.out.print("> ");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
System.out.println("连接异常: " + cause.getMessage());
ctx.close();
}
}
运行说明
启动服务端:
java com.example.chat.server.ChatServer 8080
启动客户端(开启多个终端):
java com.example.chat.client.ChatClient localhost 8080 Alice java com.example.chat.client.ChatClient localhost 8080 Bob java com.example.chat.client.ChatClient localhost 8080 Charlie
使用说明:
- 连接后自动登录,用户名不可重复
- 直接输入内容发送群聊消息
- 使用
/msg 用户名 消息发送私聊 - 输入
quit退出聊天室
功能总结
这个聊天室实现了以下功能:
- ✅ 支持多客户端同时在线
- ✅ 群聊功能(广播消息)
- ✅ 私聊功能(指定用户发送)
- ✅ 在线用户列表显示
- ✅ 用户上线/下线通知
- ✅ 用户名重复检查
- ✅ 断开连接自动清理
- ✅ 格式化消息显示
可以继续扩展的功能:
- 消息持久化存储
- 消息重发机制
- 文件传输功能
- 消息过滤和敏感词检测
- 用户头像和表情
- 心跳检测和断线重连
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播