JAVA的BIO和NIO

2022-02-07  本文已影响0人  东南枝下

回顾telnet命令的使用

# 连接到主机
open 127.0.0.1 9000

BIO

阻塞IO,在建立连接serverSocket.accept()和获取数据clientSocket.getInputStream().read(bytes)处阻塞
单线程无法处理多个连接,高并发环境需要创建大量线程处理,线程会爆炸式增长

public class SocketServer {

    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(9000);
        while (true) {
            System.out.println("等待连接...");
            // 没有连接,阻塞在此处
            Socket clientSocket = serverSocket.accept();
            System.out.println("有客户端连接了....");
            handler(clientSocket);
            clientSocket.close();
        }
    }

    private static void handler(Socket clientSocket) throws IOException {
        byte[] bytes = new byte[1024];
        System.out.println("准备read...");
        while (true) {
            // 阻塞方法,阻塞在此处
            int read = clientSocket.getInputStream().read(bytes);
            System.out.println("read 完毕...");

            if (read != -1) {
                String inStr = new String(bytes, 0, read);
                inStr = inStr.trim();
                System.out.println("客户端接收到到数据  " + inStr);

                if ("exit".equals(inStr)) {
                    break;
                }
            }
        }
    }

}

NIO

简单NIO

NIO是通过channel来建立连接,读写数据的
可以设置serverSocket.configureBlocking(false);使建立连接serverSocket.accept();为非阻塞
可以设置socketChannel.configureBlocking(false);使读取数据channel.read(byteBuffer);为非阻塞
单一线程可以处理多个连接
当连接数巨大时,每次读取需要遍历一个巨大的连接列表

package nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.List;

/**
 * @author Jenson
 */
public class NioServer {

    /**
     * 保存客户端链接
     */
    static List<SocketChannel> channelList = new ArrayList<>();


    public static void main(String[] args) throws IOException {
        // 创建NIO
        ServerSocketChannel serverSocket = ServerSocketChannel.open();
        serverSocket.socket().bind(new InetSocketAddress(9000));
        // 设置为非阻塞
        serverSocket.configureBlocking(false);
        System.out.println("服务启动成功");

        while (true) {

            // 非阻塞在此处不会阻塞
            // NIO非阻塞由操作系统内部实现,底层调用了linux内核的accept函数
            SocketChannel socketChannel = serverSocket.accept();
            if (socketChannel != null) {
                System.out.println("链接成功");
                // 设置为非阻塞
                socketChannel.configureBlocking(false);
                // 保存客户端链接
                channelList.add(socketChannel);
            }

            // 遍历链接进行数据读取
            for (SocketChannel channel : channelList) {
                ByteBuffer byteBuffer = ByteBuffer.allocate(128);
                // 非阻塞模式在此处就不会阻塞
                int len = channel.read(byteBuffer);

                if (len > 0) {
                    System.out.println("接收到到消息为  " + new String(byteBuffer.array()));
                } else if (len == -1) {
                    // 客户端断开链接
                    channelList.remove(channel);
                }

            }

        }

    }
}

selector多路复用器

Channel注册到Selector中,注册时需要指定监听的事件
Selector相当于一个集合,记录了那些有事件发生的集合
当有事件发生时selector.select();就会跳出阻塞
selectionKeySet.iterator()可以获取到发生的事件
SelectionKey.channel()可以获取到发生事件的Chanel

package nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * @author Jenson
 */
public class NioSelectorServer {

    public static void main(String[] args) throws IOException {

        // 创建NIO
        ServerSocketChannel serverSocket = ServerSocketChannel.open();
        serverSocket.socket().bind(new InetSocketAddress(9000));
        // 设置为非阻塞
        serverSocket.configureBlocking(false);
        // 打开selector处理channel,创建epoll
        Selector selector = Selector.open();
        // 把ServerSocketChannel注册到Selector上
        SelectionKey selectionKey = serverSocket.register(selector, SelectionKey.OP_ACCEPT);

        System.out.println("服务启动成功");


        while (true) {
            // 等待阻塞需要处理到的事件发生 (SelectionKey.OP_ACCEPT)
            selector.select();
            // 获取selector注册的全部事件的selectionKey实例
            Set<SelectionKey> selectionKeySet = selector.selectedKeys();
            Iterator<SelectionKey> selectionKeyIterator = selectionKeySet.iterator();
            while (selectionKeyIterator.hasNext()) {
                SelectionKey key = selectionKeyIterator.next();

                if (key.isAcceptable()) {
                    // 如果是OP_ACCEPT,则进行连接获取和事件注册
                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    SocketChannel socketChannel = server.accept();
                    socketChannel.configureBlocking(false);
                    // 注册客户端 读事件
                    socketChannel.register(selector, SelectionKey.OP_READ);
                    System.out.println("客户端连接成功");
                } else if (key.isReadable()) {
                    // 如果是OP_READ,则进行读取和打印
                    SocketChannel socketChannel = (SocketChannel) key.channel();
                    ByteBuffer byteBuffer = ByteBuffer.allocate(128);
                    int len = socketChannel.read(byteBuffer);
                    if (len > 0) {
                        System.out.println("接收到到消息为  " + new String(byteBuffer.array()));
                    } else if (len == -1) {
                        // 客户端断开链接
                        socketChannel.close();
                    }
                }
                selectionKeyIterator.remove();
            }
        }
    }
}

参考学习: https://www.bilibili.com/video/BV1fA41157Ht?share_source=copy_web

上一篇下一篇

猜你喜欢

热点阅读