MultiChat 协议:基于 Netty 的多人聊天室实现

MultiChat 协议是一种基于 TCP/IP 协议的应用层协议,用于实现多人聊天室的数据连接和传输。该协议初步设想包含以下功能:

  1. 用户登录
  2. 聊天室列表
  3. 消息发送
  4. 私聊功能

通过使用 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。

服务端会自动打印收到的消息和处理的结果。客户端会自动打印收到的消息和聊天室列表。

MultiChat 协议:基于 Netty 的多人聊天室实现

原文地址: https://www.cveoy.top/t/topic/jjQX 著作权归作者所有。请勿转载和采集!

免费AI点我,无需注册和登录