Netty 多人聊天室实现:客户端和服务器端代码详解
Netty 多人聊天室实现:客户端和服务器端代码详解
本文将详细解析使用 Netty 框架构建多人聊天室的客户端和服务器端代码。从连接建立、消息编解码到用户登录、聊天室管理、消息发送等功能,逐步讲解代码的设计思路和实现细节,并提供代码示例和注释。
客户端代码
package org.example;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
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.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class MultiChatClient {
private final String host;
private final int port;
private ChannelHandlerContext ctx;
public MultiChatClient(String host, int port) {
this.host = host;
this.port = port;
}
public void run() throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringDecoder(), new StringEncoder(), new MultiChatClientHandler());
}
});
ChannelFuture f = b.connect(host, port).sync();
ctx = f.channel().pipeline().context(MultiChatClientHandler.class);
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
while (true) {
String line = in.readLine();
if (line == null) {
break;
}
if (line.equals('LIST')) {
String username = null;
ctx.writeAndFlush(line + '|' + username + '\n');
} else {
ctx.writeAndFlush(line + '\n');
}
}
} finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
String host = 'localhost';
int port = 8080;
new MultiChatClient(host, port).run();
}
}
服务器代码
package org.example;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
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.concurrent.DefaultEventExecutorGroup;
import io.netty.util.concurrent.EventExecutorGroup;
import java.util.concurrent.ConcurrentHashMap;
public class MultiChatServer {
private final int port;
private final ConcurrentHashMap<String, ChannelHandlerContext> userMap = new ConcurrentHashMap<>();
private final EventExecutorGroup group = new DefaultEventExecutorGroup(16);
public MultiChatServer(int port) {
this.port = port;
}
public void run() throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringDecoder(), new StringEncoder(), new MultiChatServerHandler(userMap, group));
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture f = b.bind(port).sync();
System.out.println('Server started on port ' + port);
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port = 8080;
new MultiChatServer(port).run();
}
}
客户端消息处理类
package org.example;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
public class MultiChatClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println(msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
服务器消息处理类
package org.example;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.concurrent.EventExecutorGroup;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class MultiChatServerHandler extends SimpleChannelInboundHandler<String> {
private final ConcurrentHashMap<String, ChannelHandlerContext> userMap;
private final EventExecutorGroup group;
public MultiChatServerHandler(ConcurrentHashMap<String, ChannelHandlerContext> userMap, EventExecutorGroup group) {
this.userMap = userMap;
this.group = group;
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println('Client connected: ' + ctx.channel().remoteAddress());
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println('Client disconnected: ' + ctx.channel().remoteAddress());
userMap.values().remove(ctx);
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
String[] tokens = msg.split('\\|');
String command = tokens[0];
System.out.println('Received message from client: ' + msg);
switch (command) {
case 'LOGIN':
handleLogin(ctx, tokens[1], tokens[2]);
break;
case 'LIST':
handleList(ctx);
break;
case 'MSG':
handleMessage(ctx, tokens[1], tokens[2]);
break;
case 'PRIVATE':
handlePrivateMessage(ctx, tokens[1], tokens[2]);
break;
default:
ctx.writeAndFlush('Invalid command: ' + command);
break;
}
}
private void handleLogin(ChannelHandlerContext ctx, String username, String password) {
if (userMap.containsKey(username)) {
ctx.writeAndFlush('User already logged in: ' + username);
} else {
userMap.put(username, ctx);
ctx.writeAndFlush('Login successful: ' + username);
}
}
private void handleList(ChannelHandlerContext ctx) {
StringBuilder sb = new StringBuilder();
sb.append('Available chat rooms:\n');
for (Map.Entry<String, ChannelHandlerContext> entry : userMap.entrySet()) {
sb.append(entry.getKey()).append('\n');
}
boolean username = false;
if (userMap.containsKey(username)) {
sb.append('Your information:\n');
sb.append(username).append('\n');
}
ctx.writeAndFlush(sb.toString());
}
private void handleMessage(ChannelHandlerContext ctx, String roomName, String message) {
StringBuilder sb = new StringBuilder();
sb.append('[').append(ctx.channel().remoteAddress()).append('] ').append(message).append('\n');
for (Map.Entry<String, ChannelHandlerContext> entry : userMap.entrySet()) {
if (entry.getKey().equals(roomName)) {
entry.getValue().writeAndFlush(sb.toString());
}
}
}
private void handlePrivateMessage(ChannelHandlerContext ctx, String recipient, String message) {
StringBuilder sb = new StringBuilder();
sb.append('[').append(ctx.channel().remoteAddress()).append('] (private) ').append(message).append('\n');
if (userMap.containsKey(recipient)) {
userMap.get(recipient).writeAndFlush(sb.toString());
} else {
ctx.writeAndFlush('User not found: ' + recipient);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
代码设计内容
上述代码实现了一个多人聊天室的客户端和服务器端。客户端代码中,通过 Netty 框架建立与服务器的连接,并通过控制台输入消息发送到服务器。服务器代码中,使用 Netty 框架创建服务器,接收客户端连接,并处理来自客户端的消息。服务器端使用 ConcurrentHashMap 存储用户和上下文,方便进行用户管理和消息发送。
功能特点
- 客户端连接到服务器
- 服务器接收客户端连接
- 客户端发送消息到服务器
- 服务器接收客户端消息并广播到所有连接的客户端
- 用户登录功能
- 聊天室列表查看功能
- 群发消息功能
- 私聊功能
代码实现细节
- 使用 Netty 框架进行网络通信
- 使用 StringDecoder 和 StringEncoder 对消息进行编解码
- 使用 ChannelHandlerContext 处理连接和消息
- 使用 EventLoopGroup 管理事件循环
- 使用 ConcurrentHashMap 存储用户和上下文
未来改进方向
- 添加用户认证功能,确保用户安全
- 支持多种消息类型,例如图片、文件等
- 实现私聊功能,允许用户之间私下聊天
- 优化代码,提高性能和可扩展性
总结
本文介绍了使用 Netty 框架构建多人聊天室的客户端和服务器端代码。代码示例包含了连接建立、消息编解码、用户登录、聊天室管理、消息发送等功能的实现细节。希望本文能帮助您更好地理解 Netty 框架的使用,并能够在此基础上构建更复杂和功能更丰富的应用程序。
原文地址: http://www.cveoy.top/t/topic/f3ou 著作权归作者所有。请勿转载和采集!