Netty 多人聊天室服务器和客户端代码示例
Netty 多人聊天室示例代码
本示例代码使用 Netty 框架实现了一个简单的多人聊天室,包含服务器端和客户端。
服务器端代码
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.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');
}
}
/*
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
while (true) {
String line = in.readLine();
if (line == null) {
break;
}
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;
//MultiChatClientHandler类,实现了客户端的消息处理逻辑,包括接收服务器端的消息和向服务器端发送消息。
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;
//MultiChatServerHandler类,实现服务器端的消息处理逻辑,包括用户登录、聊天室列表、消息发送和私聊功能。
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;
//查看聊天室列表:在客户端中输入 `LIST`,按下回车键查看当前可用的聊天室列表。服务器端会返回当前可用的聊天室列表
case 'LIST':
handleList(ctx);
break;
//发送消息:在客户端中输入 `MSG|聊天室名|消息内容`,
// 例如 `MSG|room1|Hello, world!`,按下回车键发送消息。
// 服务器端会将消息发送给指定的聊天室内的所有用户。
case 'MSG':
handleMessage(ctx, tokens[1], tokens[2]);
break;
//私聊:在客户端中输入 `PRIVATE|用户名|消息内容`,
// 例如 `PRIVATE|Bob|How are you?`,
// 按下回车键发送私聊消息。服务器端会将消息发送给指定的用户。
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 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');
}
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();
}
}
运行说明
- 运行服务器端的
MultiChatServer类,它会在8080端口上启动一个聊天服务器。 - 运行客户端的
MultiChatClient类,它会连接到服务器。 - 在客户端中输入
LOGIN|用户名|密码,例如LOGIN|Bob|123456,按下回车键进行登录。 - 登录成功后,可以输入以下指令进行操作:
LIST:查看当前可用的聊天室列表。MSG|聊天室名|消息内容:向指定聊天室发送消息,例如MSG|room1|Hello, world!。PRIVATE|用户名|消息内容:向指定用户发送私密消息,例如PRIVATE|Alice|How are you?。QUIT:退出聊天室。
注意:在发送消息时,需要确保聊天室名和用户名是正确的,否则会发送失败。另外,服务器端会将消息发送给指定聊天室内的所有用户,所以所有在同一聊天室的用户都能看到发送的消息。
功能介绍
- 用户登录:用户可以通过输入
LOGIN|用户名|密码进行登录,服务器会校验用户名和密码,并保存用户和上下文信息。 - 聊天室列表:用户可以通过输入
LIST查看当前可用的聊天室列表。 - 发送消息:用户可以通过输入
MSG|聊天室名|消息内容向指定的聊天室发送消息。 - 私聊:用户可以通过输入
PRIVATE|用户名|消息内容向指定用户发送私密消息。
代码说明
- 服务器端使用
ConcurrentHashMap保存用户和上下文的映射关系,以便快速查找用户。 - 服务器端使用
EventExecutorGroup来处理并发任务。 - 客户端使用
ChannelHandlerContext保存连接信息,以便发送和接收消息。 - 客户端和服务器端都使用
StringDecoder和StringEncoder进行字符串编码和解码。
扩展功能
- 可以添加用户注册功能。
- 可以添加聊天室创建功能。
- 可以添加用户分组功能。
- 可以添加消息历史记录功能。
- 可以添加消息加密功能。
本示例代码只是一个简单的多人聊天室实现,您可以根据自己的需要进行扩展和修改。
原文地址: http://www.cveoy.top/t/topic/f3rj 著作权归作者所有。请勿转载和采集!