MultiChat 协议:基于 Netty 的多人聊天室实现
MultiChat 协议:基于 Netty 的多人聊天室实现
MultiChat 协议是一种基于 TCP/IP 协议的应用层协议,用于实现多人聊天室的数据连接和传输。该协议初步设想包含以下功能:
- 用户登录
- 聊天室列表
- 消息发送
- 私聊功能
通过使用 MultiChat 协议,多人聊天室可以实现高效、稳定的数据连接和传输。
基于 Netty 的实现
1. MultiChat 协议消息格式
MultiChat 协议消息格式采用 JSON 格式,包含以下字段:
- type:消息类型,取值为 'login'、'roomList'、'message'、'privateMessage'。
- username:用户名,用于标识发送方。
- roomName:聊天室名称,用于标识消息所属的聊天室。
- content:消息内容,对于不同的消息类型,内容格式不同。
示例:
{
'type': 'login',
'username': 'Alice',
'content': 'password'
}
{
'type': 'roomList'
}
{
'type': 'message',
'username': 'Bob',
'roomName': 'Room1',
'content': 'Hello, everyone!'
}
{
'type': 'privateMessage',
'username': 'Bob',
'content': 'Hi, Alice!',
'to': 'Alice'
}
2. MultiChat 协议服务端实现
MultiChat 协议服务端使用 Netty 实现,主要包含以下类:
- MultiChatServer:启动服务端,监听客户端连接,并处理客户端请求。
- MultiChatServerHandler:处理客户端请求,包括用户登录、聊天室列表、消息发送和私聊功能。
MultiChatServer 代码如下:
public class MultiChatServer {
private final int port;
public MultiChatServer(int port) {
this.port = port;
}
public void start() throws InterruptedException {
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 {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
pipeline.addLast(new MultiChatServerHandler());
}
});
ChannelFuture f = b.bind(port).sync();
System.out.println("MultiChat server started on port " + port);
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port = 8888;
if (args.length > 0) {
port = Integer.parseInt(args[0]);
}
new MultiChatServer(port).start();
}
}
MultiChatServerHandler 代码如下:
public class MultiChatServerHandler extends SimpleChannelInboundHandler<String> {
private static final Map<String, Channel> channels = new ConcurrentHashMap<>(); // 存储所有连接的客户端
private static final Map<String, Set<String>> rooms = new ConcurrentHashMap<>(); // 存储所有聊天室及其成员
@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());
String username = getUsername(ctx.channel());
if (username != null) {
removeUserFromRooms(username); // 从所有聊天室中移除用户
channels.remove(username); // 从所有连接的客户端中移除用户
}
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("Received message: " + msg);
JsonObject json = JsonParser.parseString(msg).getAsJsonObject();
String type = json.get("type").getAsString();
String username = json.get("username").getAsString();
switch (type) {
case "login":
String password = json.get("content").getAsString();
handleLogin(ctx, username, password);
break;
case "roomList":
handleRoomList(ctx);
break;
case "message":
String roomName = json.get("roomName").getAsString();
String content = json.get("content").getAsString();
handleMessage(ctx, username, roomName, content);
break;
case "privateMessage":
String to = json.get("to").getAsString();
String content2 = json.get("content").getAsString();
handlePrivateMessage(ctx, username, to, content2);
break;
default:
System.out.println("Unknown message type: " + type);
}
}
private void handleLogin(ChannelHandlerContext ctx, String username, String password) {
if (channels.containsKey(username)) {
ctx.writeAndFlush("Username already exists: " + username + "\n");
} else {
// TODO: 验证用户名和密码是否正确
channels.put(username, ctx.channel());
ctx.writeAndFlush("Login success: " + username + "\n");
}
}
private void handleRoomList(ChannelHandlerContext ctx) {
JsonObject json = new JsonObject();
json.addProperty("type", "roomList");
JsonArray roomsArray = new JsonArray();
for (String roomName : rooms.keySet()) {
roomsArray.add(new JsonPrimitive(roomName));
}
json.add("rooms", roomsArray);
ctx.writeAndFlush(json.toString() + "\n");
}
private void handleMessage(ChannelHandlerContext ctx, String username, String roomName, String content) {
if (!rooms.containsKey(roomName) || !rooms.get(roomName).contains(username)) {
ctx.writeAndFlush("You are not a member of the room: " + roomName + "\n");
} else {
JsonObject json = new JsonObject();
json.addProperty("type", "message");
json.addProperty("username", username);
json.addProperty("roomName", roomName);
json.addProperty("content", content);
for (String member : rooms.get(roomName)) {
Channel channel = channels.get(member);
if (channel != null) {
channel.writeAndFlush(json.toString() + "\n");
}
}
}
}
private void handlePrivateMessage(ChannelHandlerContext ctx, String username, String to, String content) {
if (!channels.containsKey(to)) {
ctx.writeAndFlush("User not found: " + to + "\n");
} else {
JsonObject json = new JsonObject();
json.addProperty("type", "privateMessage");
json.addProperty("username", username);
json.addProperty("content", content);
Channel channel = channels.get(to);
channel.writeAndFlush(json.toString() + "\n");
}
}
private String getUsername(Channel channel) {
for (String username : channels.keySet()) {
if (channels.get(username) == channel) {
return username;
}
}
return null;
}
private void removeUserFromRooms(String username) {
for (String roomName : rooms.keySet()) {
Set<String> members = rooms.get(roomName);
members.remove(username);
if (members.isEmpty()) {
rooms.remove(roomName);
}
}
}
}
3. MultiChat 协议客户端实现
MultiChat 协议客户端使用 Netty 实现,主要包含以下类:
- MultiChatClient:启动客户端,连接服务端,并发送用户登录请求。
- MultiChatClientHandler:处理服务端响应,包括聊天室列表、消息接收和私聊接收。
MultiChatClient 代码如下:
public class MultiChatClient {
private final String host;
private final int port;
private final String username;
private final String password;
public MultiChatClient(String host, int port, String username, String password) {
this.host = host;
this.port = port;
this.username = username;
this.password = password;
}
public void start() throws InterruptedException {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
pipeline.addLast(new MultiChatClientHandler(username));
}
});
ChannelFuture f = b.connect(host, port).sync();
System.out.println("Connected to MultiChat server: " + host + ":" + port);
JsonObject loginJson = new JsonObject();
loginJson.addProperty("type", "login");
loginJson.addProperty("username", username);
loginJson.addProperty("content", password);
f.channel().writeAndFlush(loginJson.toString() + "\n");
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
while (true) {
String line = in.readLine();
if (line == null) {
break;
}
line = line.trim();
if (line.isEmpty()) {
continue;
}
JsonObject json = new JsonObject();
if (line.startsWith("/join ")) {
String roomName = line.substring("/join ".length());
json.addProperty("type", "message");
json.addProperty("username", username);
json.addProperty("roomName", roomName);
json.addProperty("content", "joined the room");
} else if (line.startsWith("/leave ")) {
String roomName = line.substring("/leave ".length());
json.addProperty("type", "message");
json.addProperty("username", username);
json.addProperty("roomName", roomName);
json.addProperty("content", "left the room");
} else if (line.startsWith("/pm ")) {
String[] parts = line.substring("/pm ".length()).split(" ", 2);
String to = parts[0];
String content = parts[1];
json.addProperty("type", "privateMessage");
json.addProperty("username", username);
json.addProperty("to", to);
json.addProperty("content", content);
} else {
json.addProperty("type", "message");
json.addProperty("username", username);
json.addProperty("content", line);
}
f.channel().writeAndFlush(json.toString() + "\n");
}
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
String host = "localhost";
int port = 8888;
String username = "Alice";
String password = "password";
if (args.length > 0) {
host = args[0];
}
if (args.length > 1) {
port = Integer.parseInt(args[1]);
}
if (args.length > 2) {
username = args[2];
}
if (args.length > 3) {
password = args[3];
}
new MultiChatClient(host, port, username, password).start();
}
}
MultiChatClientHandler 代码如下:
public class MultiChatClientHandler extends SimpleChannelInboundHandler<String> {
private final String username;
public MultiChatClientHandler(String username) {
this.username = username;
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("Connected to MultiChat server: " + ctx.channel().remoteAddress());
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("Disconnected from MultiChat server: " + ctx.channel().remoteAddress());
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("Received message: " + msg);
JsonObject json = JsonParser.parseString(msg).getAsJsonObject();
String type = json.get("type").getAsString();
switch (type) {
case "roomList":
handleRoomList(json);
break;
case "message":
String username = json.get("username").getAsString();
String roomName = json.get("roomName").getAsString();
String content = json.get("content").getAsString();
handleMessage(username, roomName, content);
break;
case "privateMessage":
String from = json.get("username").getAsString();
String content2 = json.get("content").getAsString();
handlePrivateMessage(from, content2);
break;
default:
System.out.println("Unknown message type: " + type);
}
}
private void handleRoomList(JsonObject json) {
JsonArray roomsArray = json.get("rooms").getAsJsonArray();
System.out.println("Available rooms:");
for (JsonElement room : roomsArray) {
System.out.println("- " + room.getAsString());
}
}
private void handleMessage(String username, String roomName, String content) {
if (username.equals(this.username)) {
System.out.println("[" + roomName + "] " + content);
} else {
System.out.println("[" + roomName + "] " + username + ": " + content);
}
}
private void handlePrivateMessage(String from, String content) {
System.out.println("[PM] " + from + ": " + content);
}
}
4. MultiChat 协议使用示例
启动服务端:
MultiChatServer server = new MultiChatServer(8888);
server.start();
启动客户端:
MultiChatClient client = new MultiChatClient("localhost", 8888, "Alice", "password");
client.start();
客户端连接成功后,会自动发送用户登录请求。客户端可以输入以下命令:
- 发送消息:直接输入消息内容,例如 Hello。
- 加入聊天室:输入 /join 聊天室名称,例如 /join Room1。
- 离开聊天室:输入 /leave 聊天室名称,例如 /leave Room1。
- 发送私聊消息:输入 /pm 用户名 消息内容,例如 /pm Bob Hi。
服务端会自动打印收到的消息和处理的结果。客户端会自动打印收到的消息和聊天室列表。
原文地址: https://www.cveoy.top/t/topic/jjQX 著作权归作者所有。请勿转载和采集!