Java-第十七部分-NIO和Netty-多人聊天室

212 阅读3分钟

NIO和Netty全文

服务端

  • 主要框架
public void startServer() throws Exception {
    Selector sel = Selector.open();
    ServerSocketChannel ssc = ServerSocketChannel.open();
    //绑定
    ssc.bind(new InetSocketAddress(8000));
    ssc.configureBlocking(false);
    //将ssc注册到sel,接收连接
    ssc.register(sel, SelectionKey.OP_ACCEPT);
    System.out.println("server starting...");
    for (;;) {
        int channelCount = sel.select();
        if (channelCount == 0) continue;
        //获取可用的channel
        Set<SelectionKey> sks = sel.selectedKeys();
        Iterator<SelectionKey> it = sks.iterator();
        while(it.hasNext()) {
            //获取一个
            SelectionKey sk = it.next();
            //获取一个之后,要将其移出,等待重新注册,否则再次获取时,是获取不到通道的
            it.remove();
            if (sk.isAcceptable()) {
                //接收状态
                acceptOperator(ssc, sel);
            } else if (sk.isReadable()) {
                //可读状态
                readOperator(sel, sk);
            }
        }
    }
}
  • 处理接收状态
//处理接入状态的通道
private void acceptOperator(ServerSocketChannel ssc, Selector sel) throws Exception {
    //创建sc
    SocketChannel sc = ssc.accept();
    sc.configureBlocking(false);
    //将获取的连接,注册到selector上,监听读
    sc.register(sel, SelectionKey.OP_READ);
    sc.write(Charset.forName("UTF-8").encode("欢迎进入聊天室,请注意隐私安全"));
}
  • 处理读状态
//处理可读状态
private void readOperator(Selector sel, SelectionKey sk) throws Exception {
    //获取channel
    SocketChannel sc = (SocketChannel) sk.channel();
    ByteBuffer bb = ByteBuffer.allocate(1024);
    int len = sc.read(bb);
    String message = "";
    if (len > 0) {
        bb.flip();
        //读取
        message += Charset.forName("UTF-8").decode(bb);

    }
    //再次注册这个channel,监听后后面的可读信息
    sc.register(sel, SelectionKey.OP_READ);
    //将这个客户端的信息广播出去
    if (message.length() > 0) {
        System.out.println(sc + " - " + message);
        castOtherClient(message, sel, sc);
    }
}
  • 广播
private void castOtherClient(String message, Selector sel, SocketChannel sc) throws Exception {
    //获取所有接入的channel
    Set<SelectionKey> sks = sel.keys();
    for (SelectionKey sk : sks) {
        SelectableChannel otherSC = sk.channel();
        //不是ServerSocketChannel 且 不传给自己
        if (otherSC instanceof SocketChannel && otherSC != sc) {
            ((SocketChannel) otherSC).write(Charset.forName("UTF-8").encode(message));
        }
    }
}

客户端

public void startClient(String name) throws Exception {
    SocketChannel sc = SocketChannel.open(new InetSocketAddress("localhost", 8000));
    Scanner scanner = new Scanner(System.in);
    //接收服务端的返回消息
    Selector sel = Selector.open();
    sc.configureBlocking(false);
    //是通过这个通道写入的,将通道注册
    sc.register(sel, SelectionKey.OP_READ);
    //创建线程,处理收到的内容
    new Thread(new ClientThread(sel)).start();
    //发送消息
    while (scanner.hasNextLine()) {
        String msg = scanner.nextLine();
        if (msg.length() > 0) {
            sc.write(Charset.forName("UTF-8").encode(name + " - " + msg));
        }
    }
}
  • Runnable的实现类
public class ClientThread implements Runnable{

    private Selector sel;

    public ClientThread(Selector sel) {
        this.sel = sel;
    }

    @Override
    public void run() {
        try {
            for (;;) {
                int channelCount = sel.select();
                if (channelCount == 0) continue;
                //获取可用的channel
                Set<SelectionKey> sks = sel.selectedKeys();
                Iterator<SelectionKey> it = sks.iterator();
                while(it.hasNext()) {
                    //获取一个
                    SelectionKey sk = it.next();
                    //移除
                    it.remove();
                    //只关注可读状态
                    if (sk.isReadable()) {
                        //可读状态
                        readOperator(sel, sk);
                    }
                }
            }
        } catch (Exception e) {

        }

    }

    private void readOperator(Selector sel, SelectionKey sk) throws Exception {
        //获取channel
        SocketChannel sc = (SocketChannel) sk.channel();
        ByteBuffer bb = ByteBuffer.allocate(1024);
        int len = sc.read(bb);
        String message = "";
        if (len > 0) {
            bb.flip();
            //读取
            message += Charset.forName("UTF-8").decode(bb);
        }
        //再次注册这个channel,监听后后面的可读信息
        sc.register(sel, SelectionKey.OP_READ);
        if (message.length() > 0) {
            System.out.println(sc + " - " + message);
        }
    }
}

另一个版本

服务端

public class GroupChatServer {
    private Selector sel;
    private ServerSocketChannel ssc;
    private final static int PORT = 7777;
    //构造
    public GroupChatServer() {
        try {
            sel = Selector.open();
            ssc = ServerSocketChannel.open();
            ssc.socket().bind(new InetSocketAddress(PORT));
            //非阻塞模式
            ssc.configureBlocking(false);
            //注册
            ssc.register(sel, SelectionKey.OP_ACCEPT);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //监听
    public void listen() {
        try {
            while (true) {
                int count = sel.select();
                if (count > 0) {
                    //遍历
                    Iterator<SelectionKey> keyIterator = sel.selectedKeys().iterator();
                    while (keyIterator.hasNext()) {
                        SelectionKey sk = keyIterator.next();
                        if (sk.isAcceptable()) {
                            SocketChannel sc = ssc.accept();
                            sc.configureBlocking(false);
                            sc.register(sel, SelectionKey.OP_READ);
                            System.out.println(sc.getRemoteAddress().toString().substring(1) + " - 上线");
                        } else if (sk.isReadable()) {
                            readData(sk);
                        }
                        keyIterator.remove();
                    }
                } else {
                    System.out.println("wait...");
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {

        }
    }
    //读取消息
    private void readData(SelectionKey sk) {
        SocketChannel sc = null;
        try {
            sc = (SocketChannel) sk.channel();
            ByteBuffer bb = ByteBuffer.allocate(1024);
            int len = sc.read(bb);
            if (len > 0) {
                String msg = new String(bb.array(), 0, len);
                System.out.println("from client: " + msg);
                //转发消息
                sendInfoToOtherClients(msg, sc);
            }
        } catch (IOException e) {
            //群发数据发生异常时,说明是离线
            try {
                System.out.println(sc.getRemoteAddress().toString().substring(1) + " - 离线");
                //取消注册
                sk.cancel();
                //关闭通道
                sc.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        } finally {

        }
    }
    //转发消息
    private void sendInfoToOtherClients(String msg, SocketChannel sc) throws IOException {
        System.out.println("群发消息...");
        //遍历已注册的通道
        for (SelectionKey key : sel.keys()) {
            Channel toSc = key.channel();
            //排除 ServerSocketChannel 和 本身
            if (toSc instanceof SocketChannel && toSc != sc) {
                ByteBuffer bb = ByteBuffer.wrap(msg.getBytes());
                ((SocketChannel) toSc).write(bb);
            }
        }
    }
    public static void main(String[] args) {
        GroupChatServer gcs = new GroupChatServer();
        gcs.listen();
    }
}

客户端

public class GroupChatClient {
    private final static String HOST = "127.0.0.1";
    private final static int PORT = 7777;
    private Selector sel;
    private SocketChannel sc;
    private String username;

    public GroupChatClient() {
        try {
            sel = Selector.open();
            sc = SocketChannel.open(new InetSocketAddress(HOST, PORT));
            sc.configureBlocking(false);
            sc.register(sel, SelectionKey.OP_READ);
            username = sc.getLocalAddress().toString().substring(1);
            System.out.println(username + " - ready");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //发送信息
    public void sendInfo (String info) {
        info = username + " - " + info;
        try {
            sc.write(ByteBuffer.wrap(info.getBytes()));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //读取其他通道转发来的信息
    public void readInfo() {
        while (true) {
            try {
                int readChannels = sel.select();
                if (readChannels > 0) {
                    Iterator<SelectionKey> keyIterator = sel.selectedKeys().iterator();
                    while (keyIterator.hasNext()) {
                        SelectionKey sk = keyIterator.next();
                        if (sk.isReadable()) {
                            SocketChannel sc = (SocketChannel) sk.channel();
                            ByteBuffer bb = ByteBuffer.allocate(1024);
                            int len = sc.read(bb);
                            if (len > 0) {
                                String msg = new String(bb.array(), 0, len);
                                System.out.println(username + " receive: " + msg);
                            }
                        }
                        keyIterator.remove();
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        //启动
        GroupChatClient gcc = new GroupChatClient();
        //每个三秒读取数据
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    gcc.readInfo();
                }
            }
        }).start();
        //发送数据
        Scanner scanner = new Scanner(System.in);
        while(scanner.hasNextLine()) {
            String s = scanner.nextLine();
            gcc.sendInfo(s);
        }
    }
}