BIO、NIO

java io分为BIO和NIO及NIO升级版AIO,这里主要介绍BIO及NIO的网络socket简单示例

首先区分概念(以下内容来自引用):

区分同步或异步(synchronous/asynchronous)。简单来说,同步是一种可靠的有序运行机制,当我们进行同步操作时,后续的任务是等待当前调用返回,才会进行下一步;而异步则相反,其他任务不需要等待当前调用返回,通常依靠事件、回调等机制来实现任务间次序关系。

区分阻塞与非阻塞(blocking/non-blocking)。在进行阻塞操作时,当前线程会处于阻塞状态,无法从事其他任务,只有当条件就绪才能继续,比如 ServerSocket 新连接建立完毕,或数据读取、写入操作完成;而非阻塞则是不管 IO 操作是否结束,直接返回,相应操作在后台继续处理。

NIO主要概念(以下内容来自引用):

  • Buffer,高效的数据容器,除了布尔类型,所有原始数据类型都有相应的 Buffer 实现。
  • Channel,类似在 Linux 之类操作系统上看到的文件描述符,是 NIO 中被用来支持批量式 IO 操作的一种抽象。File 或者 Socket,通常被认为是比较高层次的抽象,而 Channel 则是更加操作系统底层的一种抽象,这也使得 NIO 得以充分利用现代操作系统底层机制,获得特定场景的性能优化,例如,DMA(Direct Memory Access)等。不同层次的抽象是相互关联的,我们可以通过 Socket 获取 Channel,反之亦然。
  • Selector,是 NIO 实现多路复用的基础,它提供了一种高效的机制,可以检测到注册在 Selector 上的多个 Channel 中,是否有 Channel 处于就绪状态,进而实现了单线程对多 Channel 的高效管理。

bio实现socket交互演示

每来一个socket连接创建一个线程处理socket

class RequestHandler extends Thread{

    private Socket socket;


    public RequestHandler(Socket socket) {
        super();
        this.socket = socket;
    }

    @Override
    public void run() {
        try (PrintWriter printWriter = new PrintWriter(socket.getOutputStream())) {
            printWriter.println("hello world!");
            printWriter.println("this is server ");
            printWriter.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


/*
bio 基本演示
    */
@Test
public void socket1() throws Exception{
    Thread sThread = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                // serverSocket 绑定 ip port
                ServerSocket serverSocket = new ServerSocket(8888);
                while (true) {
                    // serverSocket 等待连接
                    Socket socket = serverSocket.accept();
                    //获取socket 后开启线程处理
                    new RequestHandler(socket).start();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    });
    // 服务器启动
    sThread.start();

    // Socket 客户端(接收信息并打印)
    try (Socket cSocket = new Socket(InetAddress.getLocalHost(), 8888)) {
        BufferedReader bufferedReader = new BufferedReader(
                new InputStreamReader(cSocket.getInputStream()));
        //读取数据并打印
        bufferedReader.lines().forEach(s -> System.out.println("客户端:" + s));

    } catch (IOException e) {
        e.printStackTrace();
    }
}

bio增加线程池演示

池化线程提升效率

/*
bio 基本演示 + 连接池
    */
@Test
public void socket2() throws Exception{

    Executor executor = Executors.newFixedThreadPool(8);

    Thread sThread = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                ServerSocket serverSocket = new ServerSocket(8888);
                while (true) {
                    Socket socket = serverSocket.accept();
                    executor.execute(new RequestHandler(socket));
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    });
    sThread.start();

    try (Socket cSocket = new Socket(InetAddress.getLocalHost(), 8888)) {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(cSocket.getInputStream()));
        bufferedReader.lines().forEach(s -> System.out.println("客户端:" + s));

    } catch (IOException e) {
        e.printStackTrace();
    }
}

nio实现socket交互演示

/*
nio socket 基本演示
    */
@Test
public void socket3() throws Exception{

    Thread sThread = new Thread(new Runnable() {
        @Override
        public void run() {

            // 创建 server 端
            // 1.创建Selector
            // 2.创建 ServerSocketChannel
            // 3.绑定 ip port
            // 4.设置为 非阻塞,注册到 Selector 中
            // 5.selector.select() 等待 就绪的 channel
            // 6. 有就绪的 channel 将它取出 并 accept  拿到 socketChannel 进行 交互
            try (Selector selector = Selector.open();
                    ServerSocketChannel serverSocket = ServerSocketChannel.open();) {// 创建 Selector 和 Channel
                serverSocket.bind(new InetSocketAddress(InetAddress.getLocalHost(), 8888));
                //阻塞模式下,注册操作是不允许的
                serverSocket.configureBlocking(false);
                // 注册到 Selector,并说明关注点
                serverSocket.register(selector, SelectionKey.OP_ACCEPT);
                while (true) {
                    selector.select();// 阻塞等待就绪的 Channel,这是关键点之一
                    Set<SelectionKey> selectedKeys = selector.selectedKeys();
                    Iterator<SelectionKey> iter = selectedKeys.iterator();
                    while (iter.hasNext()) {
                        SelectionKey key = iter.next();
                        // 生产系统中一般会额外进行就绪状态检查
                        sendData((ServerSocketChannel) key.channel());
                        iter.remove();
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    });
    sThread.start();

    Thread.sleep(200);

    //创建 client端
    //1.创建 SocketChannel
    //2.连接到 ip port
    // 3.和服务端交互 read or write

    // Socket 客户端(接收信息并打印)
    try (SocketChannel cSocket = SocketChannel.open()) {

        cSocket.connect(new InetSocketAddress(InetAddress.getLocalHost(),8888));

        Message<String> objectMessage = receiveData(cSocket);

        System.out.println("client receive:");
        System.out.println(objectMessage.getClass());
        System.out.println(objectMessage.getData().getClass());
        System.out.println("client receive: " + new ObjectMapper().writeValueAsString(objectMessage));

    } catch (IOException e) {
        e.printStackTrace();
    }
}

Message交互对象

class Message<T>{
    private Integer code;
    private T data;
    private String message;

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

工具方法toBytes/toObject(使用Jackson进行对象和数组的转换)

public <T> T toObject (byte[] bytes, Class<T> clazz) {

    try {
        return new ObjectMapper().readValue(bytes, clazz);
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;

}

public byte[] toBytes(Object object){
    try {
        return new ObjectMapper().writeValueAsBytes(object);
    } catch (JsonProcessingException e) {
        e.printStackTrace();
    }
    return null;
}

工具方法sendData/receiveData

private void sendData(ServerSocketChannel serverSocketChannel) throws IOException {
    try (SocketChannel sSocket = serverSocketChannel.accept();) {

        Message<String> stringMessage = new Message<>();
        stringMessage.setCode(1);
        stringMessage.setData("Hello World!");
        stringMessage.setMessage("你好");

        System.out.println("server:");
        System.out.println("server send: "+ new ObjectMapper().writeValueAsString(stringMessage));
        sSocket.write(ByteBuffer.wrap(toBytes(stringMessage)));
    }
}
private <T> Message<T> receiveData(SocketChannel channel){
    ByteArrayOutputStream baos = new ByteArrayOutputStream();

    ByteBuffer buffer = ByteBuffer.allocate(1024);

    int read=0;
    try {
        while((read = channel.read(buffer)) != -1){
            buffer.flip();//将 limit = pos; pos = 0 目的是 让 get 方法 从 开头读取
            byte[] bytes = new byte[read];
            buffer.get(bytes);
            baos.write(bytes);// 读到的 bytes 写入 目标数组
            buffer.clear();//将 pos = 0; limit = capacity; 准备下次 读取 新数据
        }
        return toObject(baos.toByteArray(), Message.class);
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
}

总结:

nio主要是三个对象buffer/channel/selector的操作

服务端创建要经历:1、创建selector  2、创建ServerSocketChannel并绑定到指定地址  3、向selector注册  4、等待准备就绪的channel

客户端连接要经历:1、创建SocketChannel  2、创建SocketChannel并绑定到指定地址 3、连接完成后就可以接收或发送数据

完整代码见:https://gitee.com/xic/chunfenwx/blob/master/tomcat-demo/nio/src/test/java/com/chunfen/tomcat/nio/SocketTest.java

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值