NIO2-NIO聊天室,实现消息转发和下线监听功能

本文介绍了如何通过简单的命令行实现一个服务端,它监听客户端连接,接收并转发聊天消息,同时客户端能够发送信息并与服务端保持通信。展示了服务器端的监听逻辑和消息处理,以及客户端的连接、消息发送和接收过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述

本案例为简单的命令行实现,分为客户端和服务端,服务端实现监听客户端上线,接受客户端发送的聊天内容,并且把聊天内容转发给其他在线客户端(排除自己),客户端主要是发送消息给服务端,并且接受服务端转发过来的消息并且输出到控制台上(有图便于理解,代码里面大量注释)

服务端

服务端概述

public class Server {
    //选择器,通道,端口
    private Selector selector;
    private ServerSocketChannel ssChannel;
    private static final Integer PORT = 9999;

    /**
     * 构造器初始化
     */
    public Server(){
        try {
            selector = Selector.open();
            ssChannel = ServerSocketChannel.open();
            ssChannel.configureBlocking(false); //设置为非阻塞模式
            ssChannel.bind(new InetSocketAddress(PORT));
            ssChannel.register(selector,SelectionKey.OP_ACCEPT); //注册
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 监听--》读取消息--》转发消息
    private void listen()  {
        try{
            while (selector.select() > 0){   //一直监听,等待事件进入
                Iterator<SelectionKey> it = selector.selectedKeys().iterator();
                SocketChannel socketChannel = null;
                while (it.hasNext()){ //有事件接入
                    SelectionKey sk = it.next();
                    if (sk.isAcceptable()){ //这里是客户端登入。获取器通道,注册到咱们的选择器上
                        socketChannel = ssChannel.accept();
                        socketChannel.configureBlocking(false);

                        System.out.println(socketChannel.getRemoteAddress()+"----上线了");
                        socketChannel.register(selector,SelectionKey.OP_READ);
                    }else if (sk.isReadable()){ //登录的客户端发送消息
                        readData(sk);
                    }
                    it.remove();
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 处理客户端发送的消息
     * @param sk
     */
    private void readData(SelectionKey sk) {
        SocketChannel socketChannel = null;
        try{
            socketChannel = (SocketChannel)sk.channel(); //获取当前客户端的通道
            ByteBuffer buff = ByteBuffer.allocate(1024);
            int len = 0;
            int read = socketChannel.read(buff); //读取通道中的消息到缓冲区里面
            buff.flip();    //切换为写模式,就是让buff中的position为0,limit为当前直接的长度
            if (read>0){
                String msg = new String(buff.array(),0,buff.remaining());
                System.out.println("客户端"+socketChannel.getRemoteAddress()+": " + msg);
                //消息转发
                sendInfoToOtherClients(msg, socketChannel);
            }
        }catch (Exception e){
            //这里出异常通常是客户端断开连接,意味着下线
            try {
                System.err.println(socketChannel.getRemoteAddress()+"下线了");
                sk.channel();
                socketChannel.close();
            } catch (IOException ioException) {
                ioException.printStackTrace();
            }
        }

    }

    /**
     * 转发消息
     * @param msg
     * @param currentChannel 自己的通道
     * @throws IOException
     */
    private void sendInfoToOtherClients(String msg, SocketChannel currentChannel) throws IOException {
        for (SelectionKey key : selector.keys()){ //遍历selector里面的通道
            Channel channel = key.channel();
            if (channel instanceof SocketChannel && channel!=currentChannel){ //排除自己,自己就不必要转发的
                SocketChannel sc = (SocketChannel)channel;
                ByteBuffer buff = ByteBuffer.wrap(msg.getBytes()); //wrap底层做了一个ByteBuffer的操作,封装好的api,范围确认好了
                sc.write(buff);
            }
        }
    }


    public static void main(String[] args) {
        System.out.println("--------服务端开始启动-----------");
        Server server = new Server();
        server.listen();
    }
}

客户端

客户端概述

public class Client {
    //定义相关的属性
    private final String HOST = "127.0.0.1"; // 服务器的ip
    private final int PORT = 9999; //服务器端口
    private Selector selector;
    private SocketChannel socketChannel;
    private String username;

    //构造器, 完成初始化工作
    public Client() throws IOException {

        selector = Selector.open();
        //连接服务器
        socketChannel = socketChannel.open(new InetSocketAddress("127.0.0.1", PORT));
        //设置非阻塞
        socketChannel.configureBlocking(false);
        //将channel 注册到selector
        socketChannel.register(selector, SelectionKey.OP_READ);
        //得到username
        username = socketChannel.getLocalAddress().toString().substring(1);
        System.out.println(username + " 就绪...");

    }

    //向服务器发送消息
    public void sendInfo(String info) {
        info = username + " 说:" + info;
        System.out.println("我:"+info);
        try {
            socketChannel.write(ByteBuffer.wrap(info.getBytes()));
        }catch (IOException e) {
            e.printStackTrace();
        }
    }

    //读取从服务器端回复的消息
    public void readInfo() {
        try {

            int readChannels = selector.select();
            if(readChannels > 0) {//有可以用的通道

                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {

                    SelectionKey key = iterator.next();
                    if(key.isReadable()) {
                        //得到相关的通道
                        SocketChannel sc = (SocketChannel) key.channel();
                        //得到一个Buffer
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        //读取
                        sc.read(buffer);
                        //把读到的缓冲区的数据转成字符串
                        String msg = new String(buffer.array());
                        System.out.println(msg.trim());
                    }
                }
                iterator.remove(); //删除当前的selectionKey, 防止重复操作
            } else {
                //System.out.println("没有可以用的通道...");

            }

        }catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws Exception {
        //启动我们客户端
        Client chatClient = new Client();
        //启动一个线程, 每隔3秒,读取从服务器发送数据
        new Thread() {
            public void run() {

                while (true) {
                    chatClient.readInfo();
                    try {
                        Thread.currentThread().sleep(3000);
                    }catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();

        //发送数据给服务器端
        Scanner scanner = new Scanner(System.in);

        while (scanner.hasNextLine()) {
            String s = scanner.nextLine();
            chatClient.sendInfo(s);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值