首页技术文章正文

如何通过NIO实现群聊?【黑马程序员】

更新时间:2020-10-29 来源:黑马程序员 浏览量:

1.NIO群聊实现步骤

·构建Selector以及服务端监听通

·道启动监听并处理建立连接请求

·处理读数据

·群发数据实现

·客户端测试实现

2. 服务端实现
2.0 服务端完整代码服务端的主要功能如下

(1)开放监听端口,方法ChatServer构造方法

(2)处理链接请求,方法listener实现连接的建立

(2)读取消息内容,方法readData

(4)转发消息给当前所有在线的人,方法sendData2All

package com.hgy.chat;
/**
* 群聊服务器
*/
public class ChatServer {
    private ServerSocketChannel serverSocketChannel;
    private Selector selector;
    /**
    * 初始化服务端
    */
    public ChatServer() {
        try {
            // 创建Selector以及ServerSocketChannel
            selector = Selector.open();
            serverSocketChannel = serverSocketChannel.open();
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.socket().bind(new InetSocketAddress(8888));
            //将服务端监听通道注册到Selector中
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        } catch (Exception e) {
            e.printStackTrace();
        }   
    }
    /**
    * 监听客户端操作
    */
    public void listener() {
        while (true) {
            try {
                if (selector.select(1000) == 0) {
                continue;
            }
            //获得所有有事件的key
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();
                    //如果当前key是处理链接类型
                    if (key.isAcceptable()) {
                        SocketChannel socketChannel =
                        serverSocketChannel.accept();
                        socketChannel.configureBlocking(false);
                        socketChannel.register(selector, SelectionKey.OP_READ);
                    }
                    // 当前链接是读数据类型
                    if (key.isReadable()) {
                        readData(key);
                    }
                    iterator.remove();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    /**
    * 读取数据并群发给所有的用户
    * @param key
    */
    private void readData(SelectionKey key) {
        try {
            if (key.isReadable()) {
                SocketChannel channel = (SocketChannel) key.channel();
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                channel.read(byteBuffer);
                String s = new String(byteBuffer.array());
                // 写到其他所有客户端
                sendData2All(s);
        }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
    * 群发给所有的用户
    * @param msg 需要发送的消息
    */
    private void sendData2All(String msg) {
        try {
            // 当前在selector上注册的所有key就是所有用户
            Set<SelectionKey> keys = selector.keys();
            for (SelectionKey key : keys) {
                // 获取每个用户的通道
                SelectableChannel channel = key.channel();
                // 实现数据发送
                if (channel instanceof SocketChannel) {
                    System.out.println(":::" + msg);
                    ByteBuffer byteBuffer = ByteBuffer.wrap(msg.getBytes());
                    SocketChannel socketChannel = (SocketChannel) channel;
                    socketChannel.write(byteBuffer);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        ChatServer chatServer = new ChatServer();
        chatServer.listener();
    }
}


2.1 构建Selector以及服务端监听通道

当ChatServer对象被创建时具体实现步骤如下

(1)创建serverSocketChannel对象

(2)设置处理模式为非阻塞模式

(3)绑定监听端口

(4)将channel注册到selector中

public class ChatServer {
    private ServerSocketChannel serverSocketChannel;
    private Selector selector;
    /**
    * 初始化服务端
    */
    public ChatServer() {
        try {
            // 创建Selector以及ServerSocketChannel
            selector = Selector.open();
            serverSocketChannel = serverSocketChannel.open();
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.socket().bind(new InetSocketAddress(8888));
            //将服务端监听通道注册到Selector中
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


2.2 实现监听并处理建立连接请求

连接请求处理实现步骤

(1)获得所有有事件的key,通过key就可以拿到用户的SocketChannel

(2)循环遍历每一个key,判断当前是读事件,还是建立连接事件

(3)如果是建立连接事件则直接将该通道注册到selector中

(4)如果是读数据事件就交给具体的读数据方法处理数据

 

2.3 处理读数据数据

处理的具体实现步骤

(1)通过key获取和用户连接的通道(相当于输入流)

(2)获取通道的数据并打印

(3)将数据转发给其他在线用户

public void listener() {
    while (true) {
        try {
            if (selector.select(1000) == 0) {
                continue;
            }
            //获得所有有事件的key
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                //如果当前key是处理链接类型
                if (key.isAcceptable()) {
                    SocketChannel socketChannel =
                    serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector, SelectionKey.OP_READ);
                }
                // 当前链接是读数据类型
                if (key.isReadable()) {
                    readData(key);
                }
                iterator.remove();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


2.4 群发数据实现

数据群发实现步骤

(1)当前在线用户实际上就是selector中所有注册的key,也就是在线的用户

(2)通过key拿到和用户的链接讲消息转发出去

/**
* 监听客户端操作
*/
/**
* 读取数据并群发给所有的用户
* @param key
*/
private void readData(SelectionKey key) {
    try {
        if (key.isReadable()) {
            SocketChannel channel = (SocketChannel) key.channel();
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            channel.read(byteBuffer);
            String s = new String(byteBuffer.array());
            // 写到其他所有客户端
            sendData2All(s);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}


2.5 启动服务端

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


3. 客户端实现

客户端实现

(1)首先创建SocketChannel对象并链接到具体的服务器

(2)将通道注册到selector中

(3)开启一个新的线程监听selector中所有key的事件

(4)在主线程中循环阻塞获取用户的输入 

public class ChatClient {
    public static void main(String[] args) throws Exception {
    // 客户端代码, 建立连接
        Selector selector = Selector.open();
        SocketChannel socketChannel = SocketChannel.open(new
        InetSocketAddress("127.0.0.1", 8888));
        socketChannel.configureBlocking(false);
        socketChannel.register(selector, SelectionKey.OP_READ);
        // 开启一个新的线程轮询当前客户是否有可读消息
        new Thread(() -> {
            while (true) {
                try {
                    int select = selector.select(1000);
                    // 有可读消息进行解析打印
                    if (select > 0) {
                        for (SelectionKey key : selector.selectedKeys()) {
                            if (key.isReadable()) {
                                SocketChannel channel = (SocketChannel)
                                key.channel();
                                ByteBuffer byteBuffer =
                                ByteBuffer.allocate(1024);
                                channel.read(byteBuffer);
                                System.out.println(":==:" + new
                                String(byteBuffer.array()));
                                // 写到其他所有客户端
                                System.out.println(new
                                String(byteBuffer.array()));
                            }
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }   
            }
        }).start();
        // 主线程中循环获取用户输入的聊天消息
        while(true) {
            Scanner scanner = new Scanner(System.in);
            //发送用户的消息
            socketChannel.write(ByteBuffer.wrap(scanner.nextLine().getBytes()));
        }
    }
}


猜你喜欢:

什么是敏捷开发?十分钟了解

IO流、字节流和字符流分别是什么?

Java类加载机制详解 



分享到:
在线咨询 我要报名
和我们在线交谈!