【深入理解Java IO流0x0A】NIO实战-网络聊天室

本篇博客将进行NIO编程实战,实现一个简单聊天室。
我们来通过 SocketChannel 和 ServerSocketChannel 实现一个 0.1 版的聊天室,先说一下需求,比较简单,服务端启动监听客户端请求,当客户端向服务器端发送信息后,服务器端接收到后把客户端消息回显给客户端,比较呆瓜,但可以先来看一下。
image.png
来看服务端代码:

@Slf4j
public class ChatServer {
    private Selector selector;
    private ServerSocketChannel serverSocketChannel;
    private static final int PORT = 8080;

    public static void main(String[] args) {
        new ChatServer().start();
    }

    public ChatServer(){
        try {
            // 创建一个 ServerSocketChannel,并将其绑定到指定端口
            serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.socket().bind(new InetSocketAddress(PORT));
            // 设置为非阻塞模式
            serverSocketChannel.configureBlocking(false);
            // 创建一个Selector,并将ServerSocketChannel注册到它上面
            // 监听OP_ACCEPT事件(等待客户端连接)
            selector = Selector.open();
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            log.info("聊天室服务端启动了:"+PORT);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void start(){
        try {
            // 无限循环,等待连接
            while(true){
                // 等待已注册的通道中有事件发生
                if(selector.select()>0){
                    Iterator<SelectionKey> iterator = selector.selectedKeys()
                            .iterator();
                    while(iterator.hasNext()){
                        // 获取到发生事件的通道的SelectionKey
                        SelectionKey key = iterator.next();
                        iterator.remove();
                        handleKey(key);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 判断SelectionKey的事件类型
    private void handleKey(SelectionKey key) throws IOException{
        // 如果是OP_ACCEPT事件,说明有新的客户端连接进来。
        // 接受新的连接,并将新连接的SocketChannel注册到 Selector上,监听OP_READ事件
        if(key.isAcceptable()){
            SocketChannel socketChannel = serverSocketChannel.accept();
            socketChannel.configureBlocking(false);
            socketChannel.register(selector,SelectionKey.OP_READ);
            log.info("客户端连接上了:"+socketChannel.getRemoteAddress());
        }else if(key.isReadable()){  // 如果是OP_READ事件,说明客户端发送了消息。读取客户端发送的消息,并将其返回给客户端
            SocketChannel socketChannel = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int len = socketChannel.read(buffer);
            if(len>0){
                buffer.flip();
                String message = new String(buffer.array(),0,len);
                log.info("客户端说:"+message);
                String responseMsg = "服务端回复:"+message;
                socketChannel.write(ByteBuffer.wrap(responseMsg.getBytes()));
            }
        }
    }
}

接下来是客户端:

@Slf4j
public class ChatClient {
    private Selector selector;
    private SocketChannel socketChannel;
    private static final String HOST = "localhost";
    private static final int PORT = 8080;

    public static void main(String[] args) {
        new ChatClient().start();
    }

    public ChatClient(){
        try {
            selector = Selector.open();

            // 创建一个 SocketChannel,并连接到指定的服务器地址和端口
            socketChannel = SocketChannel.open(new InetSocketAddress(HOST,PORT));
            socketChannel.configureBlocking(false); // 设置为非阻塞模式
            // 将SocketChannel注册到selector上面,监听OP_READ事件(等待接收服务器的消息)
            socketChannel.register(selector, SelectionKey.OP_READ);
            log.info("连接到聊天室了");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void start() {
        // 启动一个新线程用于
        new Thread(()->{
            try {
                while(true){
                    // 等待已注册的通道中有事件发生
                    if(selector.select()>0){
                        // 获取到发生事件的通道的 SelectionKey
                        for(SelectionKey key:selector.selectedKeys()){
                            selector.selectedKeys().remove(key);
                            // 如果是 OP_READ 事件,说明服务器发送了消息。读取服务器发送的消息,并在控制台显示。
                            if(key.isReadable()){
                                readMessage();
                            }
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }).start();

        try(BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))){
            String input;
            while((input=reader.readLine())!=null){
                sendMessage(input);
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }

    private void sendMessage(String message) throws IOException{
        if(message!=null && !message.trim().isEmpty()){
            ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
            socketChannel.write(buffer);
        }
    }

    private void readMessage() throws IOException{
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        int len = socketChannel.read(buffer);
        if(len>0){
            buffer.flip();
            String message = new String(buffer.array(),0,len);
            log.info(message);
        }
    }
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值