如何使用NIO构建一个简易聊天室?

wen java案例 74

本文目录导读:

如何使用NIO构建一个简易聊天室?

  1. 整体架构
  2. 服务器端实现
  3. 客户端实现
  4. 用户输入处理
  5. 使用方法
  6. 关键特性说明
  7. 扩展建议

这是一个使用Java NIO构建的简易聊天室实现方案,我将为你提供完整的代码结构和核心实现逻辑。

整体架构

ChatRoom
├── ChatServer.java    // 服务器端
├── ChatClient.java    // 客户端
└── UserInputHandler.java  // 用户输入处理

服务器端实现

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;
public class ChatServer {
    private static final int PORT = 8888;
    private static final Charset CHARSET = Charset.forName("UTF-8");
    public void start() throws IOException {
        // 1. 创建Selector
        Selector selector = Selector.open();
        // 2. 创建ServerSocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.socket().bind(new InetSocketAddress(PORT));
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("服务器启动,监听端口:" + PORT);
        // 3. 事件循环
        while (true) {
            int readyChannels = selector.select();
            if (readyChannels == 0) continue;
            Set<SelectionKey> selectedKeys = selector.selectedKeys();
            Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
            while (keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();
                keyIterator.remove();
                if (!key.isValid()) continue;
                // 处理新连接
                if (key.isAcceptable()) {
                    acceptConnection(selector, key);
                }
                // 处理读事件
                else if (key.isReadable()) {
                    readMessage(selector, key);
                }
            }
        }
    }
    private void acceptConnection(Selector selector, SelectionKey key) throws IOException {
        ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
        SocketChannel clientChannel = serverChannel.accept();
        clientChannel.configureBlocking(false);
        clientChannel.register(selector, SelectionKey.OP_READ);
        // 广播新用户加入消息
        System.out.println("用户 [" + clientChannel.getRemoteAddress() + "] 加入聊天室");
        broadcastMessage(selector, clientChannel, 
                        "系统消息:新用户 [" + clientChannel.getRemoteAddress() + "] 加入聊天室");
    }
    private void readMessage(Selector selector, SelectionKey key) throws IOException {
        SocketChannel clientChannel = (SocketChannel) key.channel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        StringBuilder message = new StringBuilder();
        try {
            int bytesRead = clientChannel.read(buffer);
            if (bytesRead == -1) {
                // 客户端断开连接
                System.out.println("用户 [" + clientChannel.getRemoteAddress() + "] 离开聊天室");
                broadcastMessage(selector, clientChannel,
                                "系统消息:用户 [" + clientChannel.getRemoteAddress() + "] 离开聊天室");
                key.cancel();
                clientChannel.close();
                return;
            }
            buffer.flip();
            message.append(CHARSET.decode(buffer).toString().trim());
            String msg = message.toString();
            if (msg.length() > 0) {
                System.out.println("收到消息:" + msg);
                // 广播消息给所有其他客户端
                broadcastMessage(selector, clientChannel, 
                                "[" + clientChannel.getRemoteAddress() + "]: " + msg);
            }
        } catch (IOException e) {
            // 异常处理:关闭连接
            key.cancel();
            clientChannel.close();
        }
    }
    private void broadcastMessage(Selector selector, SocketChannel sender, String message) 
            throws IOException {
        ByteBuffer buffer = ByteBuffer.wrap(message.getBytes(CHARSET));
        for (SelectionKey key : selector.keys()) {
            Channel channel = key.channel();
            if (channel instanceof SocketChannel && channel != sender) {
                SocketChannel target = (SocketChannel) channel;
                target.write(buffer);
                buffer.rewind();
            }
        }
    }
    public static void main(String[] args) throws IOException {
        new ChatServer().start();
    }
}

客户端实现

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;
public class ChatClient {
    private static final String SERVER_HOST = "localhost";
    private static final int SERVER_PORT = 8888;
    private static final Charset CHARSET = Charset.forName("UTF-8");
    public void start() throws IOException {
        // 创建Selector
        Selector selector = Selector.open();
        // 创建SocketChannel并连接服务器
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(false);
        socketChannel.connect(new InetSocketAddress(SERVER_HOST, SERVER_PORT));
        socketChannel.register(selector, SelectionKey.OP_CONNECT);
        // 启动用户输入线程
        new Thread(new UserInputHandler(socketChannel)).start();
        // 事件循环
        while (true) {
            selector.select();
            Set<SelectionKey> selectedKeys = selector.selectedKeys();
            Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
            while (keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();
                keyIterator.remove();
                if (!key.isValid()) continue;
                // 处理连接事件
                if (key.isConnectable()) {
                    connectToServer(key);
                }
                // 处理读事件
                else if (key.isReadable()) {
                    readFromServer(key);
                }
            }
        }
    }
    private void connectToServer(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();
        if (channel.isConnectionPending()) {
            channel.finishConnect();
        }
        channel.configureBlocking(false);
        channel.register(key.selector(), SelectionKey.OP_READ);
        System.out.println("已连接到聊天服务器");
    }
    private void readFromServer(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        StringBuilder message = new StringBuilder();
        int bytesRead = channel.read(buffer);
        if (bytesRead == -1) {
            System.out.println("服务器连接断开");
            key.cancel();
            channel.close();
            return;
        }
        buffer.flip();
        message.append(CHARSET.decode(buffer).toString().trim());
        if (message.length() > 0) {
            System.out.println(message.toString());
        }
    }
    public static void main(String[] args) throws IOException {
        new ChatClient().start();
    }
}

用户输入处理

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
public class UserInputHandler implements Runnable {
    private SocketChannel socketChannel;
    private static final Charset CHARSET = Charset.forName("UTF-8");
    public UserInputHandler(SocketChannel socketChannel) {
        this.socketChannel = socketChannel;
    }
    @Override
    public void run() {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) {
            String input;
            while ((input = reader.readLine()) != null) {
                if ("/quit".equalsIgnoreCase(input)) {
                    System.out.println("退出聊天室");
                    System.exit(0);
                }
                ByteBuffer buffer = ByteBuffer.wrap(input.getBytes(CHARSET));
                socketChannel.write(buffer);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

使用方法

  1. 启动服务器

    javac ChatServer.java
    java ChatServer
  2. 启动多个客户端

    javac ChatClient.java UserInputHandler.java
    java ChatClient

关键特性说明

非阻塞I/O

  • 所有操作都是非阻塞的,一个线程可以处理多个连接
  • 使用Selector实现事件驱动

事件处理

  • OP_ACCEPT: 处理新客户端连接
  • OP_READ: 读取客户端消息
  • OP_CONNECT: 客户端连接服务器

消息广播

  • 服务器收到消息后广播给所有其他客户端
  • 使用ByteBuffer进行数据传输

用户交互

  • 输入 /quit 退出聊天室
  • 消息格式:[客户端地址]: 消息内容

扩展建议

  1. 添加用户名:连接时发送用户名
  2. 私聊功能:支持 @用户 消息 格式
  3. 消息队列:使用消息队列处理并发消息
  4. 心跳检测:定期检测客户端连接状态
  5. 日志记录:记录聊天历史

这个简易聊天室实现了基本的NIO聊天功能,你可以根据需求进行扩展和优化。

抱歉,评论功能暂时关闭!