Netty网络框架学习笔记-2(NIO实现一个群聊_2022-01-26)

Netty网络框架学习笔记-2(NIO实现简单一个群聊_2022-01-26)

实现的功能有:

  • 监听所有客户端的上线与离线
  • 多人群聊, 客户端发送消息给所有的客户端 (通过服务端转发)
  • 或者单独私聊发送信息!

1.0 服务端的实现

/**
 * @Author: ZhiHao
 * @Date: 2022/1/26 17:44
 * @Description: 群聊服务端实现
 * @Versions 1.0
 **/
@Slf4j
public class GroupChatNIOService {

    private static final ConcurrentMap<Integer, SocketChannel> CHANNEL_CONCURRENT_MAP = new ConcurrentHashMap<>();
    private static final AtomicInteger atomicInteger = new AtomicInteger(1);

    public static void main(String[] args) throws Exception {
        Selector selector = Selector.open();
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(8888));
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        for (; ; ) {
            log.info("GroupChatNIOService-阻塞等待selector中的所有通道其中的事件发生");
            int select = selector.select();
            // 监听到的事件大于0, 进入操作
            if (select > 0) {
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
                while (keyIterator.hasNext()) {
                    SelectionKey selectionKey = keyIterator.next();
                    SelectableChannel channel = selectionKey.channel();
                    if (selectionKey.isAcceptable()) {
                        ServerSocketChannel serverSocketChannel1 = (ServerSocketChannel) channel;
                        SocketChannel socketChannel = serverSocketChannel1.accept();
                        socketChannel.configureBlocking(false);
                        int andIncrement = atomicInteger.getAndIncrement();
                        socketChannel.register(selector, SelectionKey.OP_READ, andIncrement);
                        log.info("GroupChatNIOService-{}号,客户端上线了群聊!", andIncrement);
                        CHANNEL_CONCURRENT_MAP.put(andIncrement, socketChannel);
                    }

                    if (selectionKey.isReadable()) {
                        SocketChannel socketChannel = (SocketChannel) channel;
                        ByteBuffer byteBuffer = ByteBuffer.allocate(1024 * 1024);
                        StringBuilder builder = new StringBuilder(1024);
                        try { 
                            // 这里可以使用前面学习笔记中的扩容机制 (但是也有不少缺点不够完善)
                            while (socketChannel.read(byteBuffer) > 0) {
                                byteBuffer.flip();
                                builder.append(new String(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit()));
                                byteBuffer.clear();
                            }
                        } catch (IOException e) {
                            Integer andIncrement = (Integer) selectionKey.attachment();
                            CHANNEL_CONCURRENT_MAP.remove(andIncrement);
                            log.info("GroupChatNIOService-{}号,客户端离线了群聊!, 进行取消注册与关闭通道", andIncrement);
                            selectionKey.cancel(); // 取消注册
                            channel.close(); // 关闭通道
                            keyIterator.remove(); // 防止重复操作
                            continue;
                        }
                        String str = builder.toString();
                        log.info("GroupChatNIOService-服务端接收到客户端的信息:{}", str);
                        JSONObject jsonObject = JSONUtil.parseObj(str);
                        // 进行转发信息到其他客户端
                        forward((Integer) selectionKey.attachment(), jsonObject);
                    }
                    keyIterator.remove(); // 防止重复操作, 一直循环0事件
                }
            }
        }
    }

    private static void forward(Integer attachment, JSONObject jsonObject) throws IOException {
        Integer clientNum = Convert.convertQuietly(Integer.class, jsonObject.get("clientNum"));
        String data = (String) jsonObject.get("data");
        // 私聊操作
        if (Objects.nonNull(clientNum)) {
            SocketChannel socketChannel = CHANNEL_CONCURRENT_MAP.get(clientNum);
            if (Objects.isNull(socketChannel)){
                log.info("GroupChatNIOService-对应客户端还没有上线, 请等待上线!");
                return;
            }
            socketChannel.write(ByteBuffer.wrap(data.getBytes(StandardCharsets.UTF_8)));
            return;
        }
        // 群聊操作
        Set<Map.Entry<Integer, SocketChannel>> entries = CHANNEL_CONCURRENT_MAP.entrySet();
        for (Map.Entry<Integer, SocketChannel> entry : entries) {
            if (entry.getKey().equals(attachment)) {
                continue; // 排除自己
            }
            SocketChannel socketChannel = entry.getValue();
            socketChannel.write(ByteBuffer.wrap(data.getBytes(StandardCharsets.UTF_8)));
        }
    }
}

2.0 客户端1号实现

/**
 * @Author: ZhiHao
 * @Date: 2022/1/26 17:44
 * @Description: 群聊服务端实现
 * @Versions 1.0
 **/
@Slf4j
public class GroupChatNIOClient1 {


    private static ExecutorService executors = Executors.newSingleThreadExecutor();

    public static void main(String[] args) throws Exception {
        Selector selector = Selector.open();
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(false);
        socketChannel.register(selector, SelectionKey.OP_CONNECT);
        socketChannel.connect(new InetSocketAddress("127.0.0.1", 8888));
        for (; ; ) {
            log.info("GroupChatNIOClient1-阻塞等待selector中的所有通道其中的事件发生");
            int select = selector.select();
            if (select > 0) {
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
                while (keyIterator.hasNext()) {
                    SelectionKey selectionKey = keyIterator.next();

                    if (selectionKey.isConnectable()) {
                        SocketChannel channel = (SocketChannel) selectionKey.channel();
                        while (!channel.finishConnect()) {
                        }
                        channel.configureBlocking(false);
                        channel.register(selector, SelectionKey.OP_READ);
                    }

                    if (selectionKey.isReadable()) {
                        SocketChannel channel = (SocketChannel) selectionKey.channel();
                        ByteBuffer byteBuffer = ByteBuffer.allocate(1024 * 1024);
                        StringBuilder builder = new StringBuilder(1024);
                        while (channel.read(byteBuffer) > 0) {
                            byteBuffer.flip();
                            builder.append(new String(byteBuffer.array(), byteBuffer.position(), byteBuffer.limit()));
                            byteBuffer.clear();
                        }
                        log.info("GroupChatNIOClient1号, 接收到消息:{}", builder.toString());
                    }
                    //写数据回服务端
                    executors.execute(()->{
                        try {
                            waitWrite((SocketChannel) selectionKey.channel());
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    });
                    keyIterator.remove(); // 防止重复操作, 一直循环0事件
                }
            }
        }
    }

    private static void waitWrite(SocketChannel channel) throws IOException {
        Scanner scan = new Scanner(System.in);
        log.info("GroupChatNIOClient1号, 求输入需要发送的信息, 格式 1-xxxxx");
        System.out.print("输入数据:");
        String str = null;  // 接收数据
        while (StrUtil.isNotBlank((str = scan.next()))) {
            break;
        }
        String[] split = str.split("-");
        JSONObject jsonObject = new JSONObject();
        if (split.length > 1) {
            jsonObject.putOnce("clientNum", split[0]);
            jsonObject.putOnce("data", split[1]);
        } else {
            jsonObject.putOnce("data", split[0]);
        }
        log.info("GroupChatNIOClient1号, 输入需要发送的信息为:{}", jsonObject.toString());
        channel.write(ByteBuffer.wrap(jsonObject.toString().getBytes(StandardCharsets.UTF_8)));
    }

}

2.0 客户端2、3号实现

代码完全与客户端一号一样

3.0 结果:

20:29:30.909 [main] INFO com.zhihao.nio.GroupChatNIOService - GroupChatNIOService-阻塞等待selector中的所有通道其中的事件发生
20:29:34.581 [main] INFO com.zhihao.nio.GroupChatNIOService - GroupChatNIOService-1号,客户端上线了群聊!
20:29:34.583 [main] INFO com.zhihao.nio.GroupChatNIOService - GroupChatNIOService-阻塞等待selector中的所有通道其中的事件发生
20:29:37.135 [main] INFO com.zhihao.nio.GroupChatNIOService - GroupChatNIOService-2号,客户端上线了群聊!
20:29:37.135 [main] INFO com.zhihao.nio.GroupChatNIOService - GroupChatNIOService-阻塞等待selector中的所有通道其中的事件发生
20:29:39.694 [main] INFO com.zhihao.nio.GroupChatNIOService - GroupChatNIOService-3号,客户端上线了群聊!
20:29:39.694 [main] INFO com.zhihao.nio.GroupChatNIOService - GroupChatNIOService-阻塞等待selector中的所有通道其中的事件发生
20:30:26.093 [main] INFO com.zhihao.nio.GroupChatNIOService - GroupChatNIOService-服务端接收到客户端的信息:{"data":"二号你是个大帅B","clientNum":"2"}
20:30:26.159 [main] INFO com.zhihao.nio.GroupChatNIOService - GroupChatNIOService-阻塞等待selector中的所有通道其中的事件发生
20:30:43.786 [main] INFO com.zhihao.nio.GroupChatNIOService - GroupChatNIOService-服务端接收到客户端的信息:{"data":"你也是个帅B","clientNum":"1"}
20:30:43.786 [main] INFO com.zhihao.nio.GroupChatNIOService - GroupChatNIOService-阻塞等待selector中的所有通道其中的事件发生
20:31:25.273 [main] INFO com.zhihao.nio.GroupChatNIOService - GroupChatNIOService-服务端接收到客户端的信息:{"data":"三号我就是喜欢群发,"}
20:31:25.273 [main] INFO com.zhihao.nio.GroupChatNIOService - GroupChatNIOService-阻塞等待selector中的所有通道其中的事件发生
20:33:32.265 [main] INFO com.zhihao.nio.GroupChatNIOService - GroupChatNIOService-3号,客户端离线了群聊!, 进行取消注册与关闭通道
20:33:32.266 [main] INFO com.zhihao.nio.GroupChatNIOService - GroupChatNIOService-阻塞等待selector中的所有通道其中的事件发生
20:33:44.630 [main] INFO com.zhihao.nio.GroupChatNIOService - GroupChatNIOService-2号,客户端离线了群聊!, 进行取消注册与关闭通道
20:33:44.630 [main] INFO com.zhihao.nio.GroupChatNIOService - GroupChatNIOService-阻塞等待selector中的所有通道其中的事件发生

---------------------------------------------------------------------
20:29:34.583 [main] INFO com.zhihao.nio.GroupChatNIOClient1 - GroupChatNIOClient1-阻塞等待selector中的所有通道其中的事件发生
20:29:34.589 [main] INFO com.zhihao.nio.GroupChatNIOClient1 - GroupChatNIOClient1-阻塞等待selector中的所有通道其中的事件发生
20:29:34.592 [pool-1-thread-1] INFO com.zhihao.nio.GroupChatNIOClient1 - GroupChatNIOClient1号, 求输入需要发送的信息, 格式 1-xxxxx
输入数据:2-二号你是个大帅B
20:30:26.091 [pool-1-thread-1] INFO com.zhihao.nio.GroupChatNIOClient1 - GroupChatNIOClient1号, 输入需要发送的信息为:{"data":"二号你是个大帅B","clientNum":"2"}
20:30:43.787 [main] INFO com.zhihao.nio.GroupChatNIOClient1 - GroupChatNIOClient1号, 接收到消息:你也是个帅B
20:30:43.787 [main] INFO com.zhihao.nio.GroupChatNIOClient1 - GroupChatNIOClient1-阻塞等待selector中的所有通道其中的事件发生
20:30:43.787 [pool-1-thread-1] INFO com.zhihao.nio.GroupChatNIOClient1 - GroupChatNIOClient1号, 求输入需要发送的信息, 格式 1-xxxxx
输入数据:20:31:25.273 [main] INFO com.zhihao.nio.GroupChatNIOClient1 - GroupChatNIOClient1号, 接收到消息:三号我就是喜欢群发,
20:31:25.274 [main] INFO com.zhihao.nio.GroupChatNIOClient1 - GroupChatNIOClient1-阻塞等待selector中的所有通道其中的事件发生

------------------------------------------------------------------------
20:29:37.138 [main] INFO com.zhihao.nio.GroupChatNIOClient2 - GroupChatNIOClient2-阻塞等待selector中的所有通道其中的事件发生
20:29:37.142 [main] INFO com.zhihao.nio.GroupChatNIOClient2 - GroupChatNIOClient2-阻塞等待selector中的所有通道其中的事件发生
20:29:37.147 [pool-1-thread-1] INFO com.zhihao.nio.GroupChatNIOClient2 - GroupChatNIOClient2号, 求输入需要发送的信息, 格式 1-xxxxx
输入数据:20:30:26.159 [main] INFO com.zhihao.nio.GroupChatNIOClient2 - GroupChatNIOClient2号, 接收到消息:二号你是个大帅B
20:30:26.161 [main] INFO com.zhihao.nio.GroupChatNIOClient2 - GroupChatNIOClient2-阻塞等待selector中的所有通道其中的事件发生
1-你也是个帅B
20:30:43.785 [pool-1-thread-1] INFO com.zhihao.nio.GroupChatNIOClient2 - GroupChatNIOClient2号, 输入需要发送的信息为:{"data":"你也是个帅B","clientNum":"1"}
20:30:43.785 [pool-1-thread-1] INFO com.zhihao.nio.GroupChatNIOClient2 - GroupChatNIOClient2号, 求输入需要发送的信息, 格式 1-xxxxx
输入数据:20:31:25.274 [main] INFO com.zhihao.nio.GroupChatNIOClient2 - GroupChatNIOClient2号, 接收到消息:三号我就是喜欢群发,
20:31:25.274 [main] INFO com.zhihao.nio.GroupChatNIOClient2 - GroupChatNIOClient2-阻塞等待selector中的所有通道其中的事件发生

---------------------------------------------------------------------------
20:29:39.696 [main] INFO com.zhihao.nio.GroupChatNIOClient3 - GroupChatNIOClient3-阻塞等待selector中的所有通道其中的事件发生
20:29:39.702 [main] INFO com.zhihao.nio.GroupChatNIOClient3 - GroupChatNIOClient3-阻塞等待selector中的所有通道其中的事件发生
20:29:39.706 [pool-1-thread-1] INFO com.zhihao.nio.GroupChatNIOClient3 - GroupChatNIOClient3号, 求输入需要发送的信息, 格式 1-xxxxx
输入数据:三号我就是喜欢群发, 1、2号给我接收
20:31:25.270 [pool-1-thread-1] INFO com.zhihao.nio.GroupChatNIOClient3 - GroupChatNIOClient3号, 输入需要发送的信息为:{"data":"三号我就是喜欢群发,"}

到此完毕, 加深熟悉了NIO网络编程的知识!

1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

懵懵懂懂程序员

如果节省了你的时间, 请鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值