NIO实现多人聊天室(java)

多人聊天室服务端代码

package com.xjb.nio.chatroom;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

/**
 * @author: xjb
 * @date: 2020/3/26
 * @description: 多人聊天室服务端
 **/
public class ChatRoomServer {

    private Selector selector;
    // 端口
    static final int port = 9999;
    private Charset charset = Charset.forName("utf-8");
    //记录在线用户名称
    private Set<String> users = new HashSet<>();

    private static String USER_EXIST = "用户已经存在,请更换名称!";
    //相当于自定义协议格式,与客户端协商好
    private static final String USER_CONTENT_SPILIT = "#@#";

    private static boolean flag = false;

    public void init() throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 切换非阻塞模式
        serverSocketChannel.configureBlocking(false);
        // 绑定端口
        serverSocketChannel.socket().bind(new InetSocketAddress(port));
        selector = Selector.open();
        // 将channel注册到selector下,并监听连接事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("Server is listening now...");
        //获取事件
        while (true) {
            selector.select();
            //可以通过这个方法,知道可用通道的集合
            Iterator<SelectionKey> it = selector.selectedKeys().iterator();
            while (it.hasNext()) {
                SelectionKey key = it.next();
                //处理事件
                dealWithSelectionKey(key);
                it.remove();
            }

        }
    }

    /**
     * 处理事件
     *
     * @param key
     */
    private void dealWithSelectionKey(SelectionKey key) throws IOException {
        if (key.isAcceptable()) {
            ServerSocketChannel server = (ServerSocketChannel) key.channel();
            SocketChannel clientSocketChannel = server.accept();
            // 切换非阻塞模式
            clientSocketChannel.configureBlocking(false);
            //注册选择器,并设置为读取模式,收到一个连接请求,然后起一个SocketChannel,
            //并注册到selector上,之后这个连接的数据,就由这个SocketChannel处理
            clientSocketChannel.register(selector, SelectionKey.OP_READ);
            //将此对应的channel设置为准备接受其他客户端请求
            key.interestOps(SelectionKey.OP_ACCEPT);
            System.out.println("Server is listening from client :" + clientSocketChannel.getRemoteAddress());
            clientSocketChannel.write(charset.encode("请输入你的名称"));
        }
        //处理来自客户端的数据读取请求
        if (key.isReadable()) {
            //返回该SelectionKey对应的 Channel,其中有数据需要读取
            SocketChannel clientSocketChannel = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            StringBuilder content = new StringBuilder();
            try {
                while (clientSocketChannel.read(buffer) > 0) {
                    buffer.flip();
                    content.append(charset.decode(buffer));
                    buffer.clear();
                }
                System.out.println("Server is listening from client " + clientSocketChannel.getRemoteAddress() + " data rev is: " + content);
                //将此对应的channel设置为准备下一次接受数据
                key.interestOps(SelectionKey.OP_READ);
            } catch (IOException e) {
                key.cancel();
                if (key.channel() != null) {
                    key.channel().close();
                }
            }
            if (content.length() > 0) {
                String[] arrayContent = content.toString().split(USER_CONTENT_SPILIT);
                //注册用户
                if (arrayContent.length == 1) {
                    String name = arrayContent[0];
                    if (users.contains(name)) {
                        clientSocketChannel.write(charset.encode(USER_EXIST));
                    } else {
                        users.add(name);
                        int num = OnlineNum(selector);
                        String message = "welcome " + name + " to chat room! Online numbers:" + num;
                        BroadCast(selector, null, message);
                    }
                } else if (arrayContent.length > 1) {
                    String name = arrayContent[0];
                    String message = content.substring(name.length() + USER_CONTENT_SPILIT.length());
                    message = name + " say " + message;
                    if (users.contains(name)) {
                        //不回发给发送此内容的客户端
                        BroadCast(selector, clientSocketChannel, message);
                    }
                }
            }
        }


    }

    //TODO 要是能检测下线,就不用这么统计了 (此处可以开启一个线程定时刷新users中的值)
    private int OnlineNum(Selector selector) {
        int res = 0;
        //检测通道数量来检测在线人数
        for (SelectionKey key : selector.keys()) {
            Channel targetchannel = key.channel();

            if (targetchannel instanceof SocketChannel) {
                res++;
            }
        }
        return res;
    }


    /**
     * 广播给其他所有用户
     *
     * @param selector
     * @param except
     * @param content
     * @throws IOException
     */
    public void BroadCast(Selector selector, SocketChannel except, String content) throws IOException {
        //广播数据到所有的SocketChannel中
        for (SelectionKey key : selector.keys()) {
            Channel targetchannel = key.channel();
            //如果except不为空,不回发给发送此内容的客户端
            if (targetchannel instanceof SocketChannel && targetchannel != except) {
                SocketChannel dest = (SocketChannel) targetchannel;
                dest.write(charset.encode(content));
            }
        }
    }

    public static void main(String[] args) throws IOException {
        new ChatRoomServer().init();
    }
}

多人聊天室客户端代码

package com.xjb.nio.chatroom;

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;

/**
 * @author: xjb
 * @date: 2020/3/26
 * @description: 多人聊天室客户端
 **/
public class ChatRoomClient {
    private Selector selector =null;
    static final int port = 9999;
    private Charset charset = Charset.forName("UTF-8");
    private SocketChannel sc = null;
    // 名称
    private String name = "";
    private static String USER_EXIST = "用户已经存在,请更换名称!";
    //相当于自定义协议格式,与客户端协商好
    private static final String USER_CONTENT_SPILIT = "#@#";

    public void init() throws IOException {
        sc = SocketChannel.open(new InetSocketAddress(port));
        sc.configureBlocking(false);
        selector = Selector.open();
        sc.register(selector, SelectionKey.OP_READ);
        //开辟一个新线程来读取从服务器端的数据
        new Thread(new ClientThread()).start();

        //在主线程中 从键盘读取数据输入到服务器端
        Scanner scan = new Scanner(System.in);
        while (scan.hasNextLine()) {
            String line = scan.nextLine();
            if ("".equals(line)) continue; //不允许发空消息
            if ("".equals(name)) {
                name = line;
                line = name + USER_CONTENT_SPILIT;
            } else {
                line = name + USER_CONTENT_SPILIT + line;
            }
            sc.write(charset.encode(line));//sc既能写也能读,这边是写
        }
    }


    private class ClientThread implements Runnable {
        @Override
        public void run() {
            try {
                while (true) {
                    int readyChannels = selector.select();
                    if (readyChannels == 0) continue;
                    Iterator<SelectionKey> it = selector.selectedKeys().iterator();
                    while (it.hasNext()) {
                        SelectionKey selectionKey = it.next();
                        dealWithSelectionKey(selectionKey);
                        it.remove();
                    }
                }
            } catch (IOException e) {

            }
        }

        private void dealWithSelectionKey(SelectionKey sk) throws IOException {
            if (sk.isReadable()) {
                //使用 NIO 读取 Channel中的数据,这个和全局变量sc是一样的,因为只注册了一个SocketChannel
                //sc既能写也能读,这边是读
                SocketChannel sc = (SocketChannel) sk.channel();

                ByteBuffer buff = ByteBuffer.allocate(1024);
                StringBuilder content = new StringBuilder();
                while (sc.read(buff) > 0) {
                    buff.flip();
                    content.append(charset.decode(buff));
                    buff.clear();
                }
                //若系统发送通知名字已经存在,则需要换个昵称
                if (USER_EXIST.equals(content.toString())) {
                    name = "";
                }
                System.out.println(content);
                sk.interestOps(SelectionKey.OP_READ);
            }
        }
    }

    public static void main(String[] args) throws IOException {
        new ChatRoomClient().init();
    }

}

发布了17 篇原创文章 · 获赞 11 · 访问量 1万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览