NIO网络编程实战之简单多人聊天室 - Go语言中文社区

NIO网络编程实战之简单多人聊天室


NIO网络编程实战
利用NIO编程知识,实现多人聊天室。

1. NIO编程实现步骤

第一步:创建Selector

第二步:创建ServerSocketChannel,并绑定监听端口

第三步:将Channel设置为非阻塞模式

第四步:将Channel注册到Selector上,监听连接事件

第五步:循环调用Selector的select方法,检测就绪情况

第六步:调用selectedKeys方法获取就绪channel集合

第七步:判断就绪事件种类,调用业务处理方法

第八步:根据业务需要决定是否再次注册监听事件,重复执行第三步操作

2. NIO网络编程实战

利用NIO编程知识,实现多人聊天室。

2.1 程序结构目录

在这里插入图片描述

2.2 服务器端程序

package com.xuxin.niochatroom;

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

/**
 * NIO服务器端
 */
public class NioServer {

    /**
     * 启动
     */
    public void start() throws IOException {
        // 1. 创建Selector
        Selector selector = Selector.open();

        // 2. 通过ServerSocketChannel创建channel通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        // 3. 为channel通道绑定监听端口
        serverSocketChannel.bind(new InetSocketAddress(8000));

        // 4. 设置channel为非阻塞模式
        serverSocketChannel.configureBlocking(false);

        // 5. 将channel注册到selector上,监听连接事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("服务器启动成功!");

        // 6. 循环等待新接入的连接
        for (;;) {
            // 获取可用channel的数量
            int readyChannels = selector.select();
            if (readyChannels == 0) {
                continue;
            }
            // 获取可用channel的集合
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator iterator = selectionKeys.iterator();
            while(iterator.hasNext()) {
                // selectionKey实例
                SelectionKey selectionKey = (SelectionKey) iterator.next();
                // 移除Set中的当前selectionKey
                iterator.remove();

                // 7. 根据就绪状态,调用对应方法处理业务逻辑
                // 如果是 接入事件
                if (selectionKey.isAcceptable()) {
                    acceptHandler(serverSocketChannel, selector);
                }
                // 如果是 可读事件
                if (selectionKey.isReadable()) {
                    readHandler(selectionKey, selector);
                }
            }
        }
    }

    /**
     * 接入事件处理器
     */
    private void acceptHandler(ServerSocketChannel serverSocketChannel,
                              Selector selector) throws IOException {
        // 如果要是接入事件,创建socketChannel
        SocketChannel socketChannel = serverSocketChannel.accept();
        // 将socketChannel设置为非阻塞工作模式
        socketChannel.configureBlocking(false);
        // 将channel注册到selector上,监听 可读事件
        socketChannel.register(selector, SelectionKey.OP_READ);
        // 回复客户端提示信息
        socketChannel.write(Charset.forName("UTF-8")
                .encode("你与聊天室里其他人都不是朋友关系,请注意隐私安全"));
    }

    /**
     * 可读事件处理器
     */
    private void readHandler(SelectionKey selectionKey, Selector selector)
            throws IOException {
        // 要从selectionKey中获取到已经就绪的channel
        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
        // 创建buffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        // 循环读取客户端请求信息
        String request = "";
        while (socketChannel.read(byteBuffer) > 0) {
            // 切换buffer为读模式
            byteBuffer.flip();
            // 读取buffer中的内容
            request += Charset.forName("UTF-8").decode(byteBuffer);
        }
        // 将channel再次注册到selector上,监听他的可读事件
        socketChannel.register(selector, selectionKey.OP_READ);

        // 将客户端发送的请求信息 广播给其他客户端
        if (request.length() > 0) {
            // 广播给其他客户端
            broadCast(selector, socketChannel, request);
        }
    }

    /**
     * 广播给其他客户端
     */
    private void broadCast(Selector selector, SocketChannel sourceChannel, String request) {
        // 获取到所有已接入的客户端channel
        Set<SelectionKey> selectionKeySet = selector.keys();

        // 循环向所有channel广播信息
        selectionKeySet.forEach(selectionKey -> {
            Channel targetChannel = selectionKey.channel();

            // 剔除发消息的客户端
            if ((targetChannel instanceof SocketChannel) && (targetChannel != sourceChannel)) {
                try {
                    // 将信息群发给target的客户端
                    ((SocketChannel) targetChannel).write(Charset.forName("UTF-8").encode(request));
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }


    /**
     * 主程序
     * @param args
     */
    public static void main(String[] args) throws IOException {
        NioServer nioServer = new NioServer();
        nioServer.start();
    }
}

2.3 客户端程序

package com.xuxin.niochatroom;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Scanner;

/**
 * NIO客户端
 */
public class NioClient {

    /**
     * 启动
     */
    public void start(String nickName) throws IOException {
        // 连接服务器
        SocketChannel socketChannel = SocketChannel.open(
                new InetSocketAddress("127.0.0.1", 8000)
        );

        // 接收服务器端响应
        // 创建线程,专门负责接收服务器端的响应数据
        // selector socketChannel 注册
        Selector selector = Selector.open();
        socketChannel.configureBlocking(false);
        socketChannel.register(selector, SelectionKey.OP_READ);
        new Thread(new NioClientHandler(selector)).start();

        // 向服务器端发送数据
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNextLine()) {
            String request = scanner.nextLine();
            if ((request != null) && (request.length() > 0)) {
                socketChannel.write(Charset.forName("UTF-8").encode(nickName + " : " + request));
            }
        }
    }
}

2.4 客户端处理服务器端响应的线程

package com.xuxin.niochatroom;

import java.io.IOException;
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.Set;

public class NioClientHandler implements Runnable {

    private Selector selector;

    public NioClientHandler(Selector selector) {
        this.selector = selector;
    }

    @Override
    public void run() {
        try {
            // 1. 循环等待服务器端响应
            for (; ; ) {
                // 获取可用channel的数量
                int readyChannels = selector.select();
                if (readyChannels == 0) {
                    continue;
                }
                // 获取可用channel的集合
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator iterator = selectionKeys.iterator();
                while (iterator.hasNext()) {
                    // selectionKey实例
                    SelectionKey selectionKey = (SelectionKey) iterator.next();
                    // 移除Set中的当前selectionKey
                    iterator.remove();

                    // 7. 根据就绪状态,调用对应方法处理业务逻辑
                    // 如果是 可读事件
                    if (selectionKey.isReadable()) {
                        readHandler(selectionKey, selector);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 可读事件处理器
     */
    private void readHandler(SelectionKey selectionKey, Selector selector)
            throws IOException {
        // 要从selectionKey中获取到已经就绪的channel
        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
        // 创建buffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        // 循环读取服务器端的响应数据
        String reponse = "";
        while (socketChannel.read(byteBuffer) > 0) {
            // 切换buffer为读模式
            byteBuffer.flip();
            // 读取buffer中的内容
            reponse += Charset.forName("UTF-8").decode(byteBuffer);
        }
        // 将channel再次注册到selector上,监听他的可读事件
        socketChannel.register(selector, selectionKey.OP_READ);

        // 将服务器端的响应数据打印到本地
        if (reponse.length() > 0) {
            System.out.println(":: " + reponse);
        }
    }
}

2.5 测试

2.5.1 创建用于测试的客户端类

ClientA:

package com.xuxin.niochatroom;

import java.io.IOException;

public class ClientA {

    public static void main(String[] args) throws IOException {
        new NioClient().start("ClientA");
    }
}

ClientB:

package com.xuxin.niochatroom;

import java.io.IOException;

public class ClientB {

    public static void main(String[] args) throws IOException {
        new NioClient().start("ClientB");
    }
}

ClientC:

package com.xuxin.niochatroom;

import java.io.IOException;

public class ClientC {

    public static void main(String[] args) throws IOException {
        new NioClient().start("ClientC");
    }
}

2.5.2 结果

启动服务器端

在这里插入图片描述

启动ClientA、ClientB、ClientC,并先由ClientA开始输入聊天信息,再由ClientB开始输入聊天信息,最后由ClientC开始输入聊天信息

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2.6 NIO网络编程缺陷

麻烦

NIO类库和API繁杂。需要了解ServerSocketChannel、SocketChannel、ByteBuffer等核心类库的使用

问题

可靠性能力补齐,工作量和难度都非常大。
客户端的断连、重连、网络闪断、失败缓存、网络阻塞和异常码流等问题。

有坑

Selector空轮询,导致CPU100%。

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/qq_28303495/article/details/89601933
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

0 条评论

请先 登录 后评论

官方社群

GO教程

推荐文章

猜你喜欢