IT技术之家

首页 > Android

Android

NIO通信实现_zhangm2020

发布时间:2022-10-24 18:00:26 Android 0次 标签:NIO通信 Selector选择器 非阻塞
目录 基于channel通道实现通信 服务端代码 客户端代码 通信结果 Channel通道与流的区别 基于Selector选择器服务端实现通信 服务端代码 通信结果...

目录

NIO编程

BIO与NIO区别

基于channel通道实现通信

服务端代码

客户端代码

通信结果

Channel通道与流的区别

基于Selector选择器服务端实现通信

服务端代码

通信结果


之前手写了同步阻塞的socket通信聊天室:Java实现ChatRoom_zhangm2020的博客-CSDN博客

NIO是怎么实现的?那跟同步阻塞有什么区别呢?

NIO编程

三大核心部分:Channel(通道)、Buffer(缓冲区)、Selector(选择器)

NIO是面向缓冲区编程的。数据读取到一个缓冲区中,需要时可在缓冲区中前后移动。Selector选择器用于监听多个通道的事件(连接请求,数据到达等)因此使用单个线程就可以监听多个客户端通道

BIO与NIO区别

BIO以流的方式处理数据;NIO以缓冲区的方式处理数据BIO是阻塞的;NIO是非阻塞的BIO基于字节流和字符流进行操作;NIO基于Channel通道和Buffer缓冲区进行操作

基于channel通道实现通信

场景:一个服务端一个客户端(不涉及Selector)

服务端代码

public class NIOServer {
    public static void main(String[] args) throws IOException, InterruptedException {
        //1. 打开一个服务端通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //2. 绑定对应的端口号
        serverSocketChannel.bind(new InetSocketAddress(9999));
        //3. 通道默认是阻塞的,需要设置为非阻塞
        serverSocketChannel.configureBlocking(false);
        System.out.println("服务端启动成功");
        while (true) {
            //4. 检查是否有客户端连接 有客户端连接会返回对应的通道
            SocketChannel socketChannel = serverSocketChannel.accept();
            if (null == socketChannel) {
                System.out.println("没有客户连接,非阻塞我去干其他事情");
                Thread.sleep(2000);
                continue;
            }
            //5. 获取客户端传递过来的数据,并把数据放在byteBuffer这个缓冲区中
            ByteBuffer allocate = ByteBuffer.allocate(1024);
            int read = socketChannel.read(allocate);
            String message = new String(allocate.array(), 0, read);
            System.out.println("读到客户端消息:" + message);
            //6. 给客户端回写数据
            socketChannel.write(ByteBuffer.wrap("我是服务端,我收到了你的消息".getBytes()));
            //7. 释放资源
            socketChannel.close();
        }
    }
}

注:java.nio.channels.SocketChannel#read(java.nio.ByteBuffer)返回值

//正数: 表示本次读到的有效字节个数. //0 : 表示本次没有读到有效字节. //-1 : 表示读到了末尾

该方法是阻塞的,如果客户端连接后一直没有发数据,那么这个方法会阻塞等待客户端信息,所以会造成服务端资源的浪费,场景2会涉及到Selector选择器,可以避免这个问题

客户端代码

public class NIOClient {
    public static void main(String[] args) throws IOException {
        //1. 打开通道
        SocketChannel socketChannel = SocketChannel.open();
        //2. 设置连接IP和端口号
        socketChannel.connect(new InetSocketAddress("127.0.0.1", 9999));
        //3. 写出数据
        socketChannel.write(ByteBuffer.wrap("你好,我是客户端".getBytes()));
        //4. 读取服务器写回的数据
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        int read = socketChannel.read(byteBuffer);
        if (read > 0) {
            System.out.println("获取到服务端消息:" + new String(byteBuffer.array(), 0, read));
            //5. 释放资源
            socketChannel.close();
        }
    }
}

通信结果

Channel通道与流的区别

通道可读可写;流是单向的(只能读或者写,输入流输出流)通道可以异步读写(非阻塞)通道是基于缓冲区buffer来读写的

基于Selector选择器服务端实现通信

场景:一个服务端多个客户端

服务端代码

public class NIOSelectorServer {
    public static void main(String[] args) throws IOException {
        //1. 打开一个服务端通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //2. 绑定对应的端口号
        serverSocketChannel.bind(new InetSocketAddress(9999));
        //3. 通道默认是阻塞的,需要设置为非阻塞
        serverSocketChannel.configureBlocking(false);
        //4. 创建选择器
        Selector selector = Selector.open();
        //5. 将服务端通道注册到选择器上,并指定注册监听的事件为OP_ACCEPT
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("服务端启动成功");
        while (true) {
            //6. 检查选择器是否有事件
            //返回值:事件个数
            int select = selector.select(2000);
            if (0 == select) {
                System.out.println("没有事件");
                continue;
            }
            //7. 获取事件集合
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                //8. 判断事件是否是客户端连接事件SelectionKey.isAcceptable()
                if (selectionKey.isAcceptable()) {
                    System.out.println("获取到连接继续事件");
                    //9. 得到客户端通道,并将通道注册到选择器上, 并指定监听事件为OP_READ
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    System.out.println("客户端已连接" + socketChannel);
                    //通道必须设置为非阻塞,selector是轮询监听,所以不能被阻塞到某个通道上
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector, SelectionKey.OP_READ);
                }
                //10. 判断是否是客户端读就绪事件SelectionKey.isReadable()
                if (selectionKey.isReadable()) {
                    System.out.println("获取到读就绪事件");
                    //11. 得到客户端通道,读取数据到缓冲区
                    //获取selector监听到的通道事件
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    int read = socketChannel.read(byteBuffer);
                    if (read > 0) {
                        System.out.println("读到客户端消息:" + new String(byteBuffer.array(), 0, read));
                        //12. 给客户端回写数据
                        socketChannel.write(ByteBuffer.wrap("我是服务端,我收到了你的消息".getBytes()));
                        socketChannel.close();
                    }
                }
                //13. 从集合中删除对应的事件, 因为防止二次处理
                iterator.remove();
            }
        }
    }
}

java.nio.channels.Selector#select(long)返回值:事件个数?

客户端代码不变

通信结果

开启两个客户端:客户端0,客户端1