BIO & NIO & AIO

目錄

1 BIO

2 NIO

3 AIO


1 BIO

BIO是同步阻塞模型,其核心是一個客戶端連接對應一個處理線程。實現起來簡單但是吞吐量低,而且客戶端不做讀寫操作的話,服務端會被阻塞(可以做多線程處理)。

BIO的示例代碼如下:

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;

/**
 * 服務端示例
 *
 * @author Robert Hou
 * @date 2020年05月01日 23:07
 **/
@Slf4j
public class BIOServer {

    @SneakyThrows
    public static void main(String[] args) {
        ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();
        ExecutorService pool = new ThreadPoolExecutor(5, 200, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1024), namedThreadFactory);

        ServerSocket serverSocket = new ServerSocket(9000);
        while (true) {
            log.info("等待連接。。。");
            //阻塞方法
            Socket socket = serverSocket.accept();
            log.info("有客戶端連接了。。。");
            pool.execute(() -> handler(socket));
        }
    }

    @SneakyThrows
    private static void handler(Socket socket) {
        if (log.isDebugEnabled()) {
            log.debug("thread id = " + Thread.currentThread().getId());
        }
        byte[] bytes = new byte[1024];

        log.info("準備read。。。");
        //接收客戶端的數據,阻塞方法,沒有數據可讀時就阻塞
        int read = socket.getInputStream().read(bytes);
        log.info("read完畢。。。");
        if (read != -1) {
            if (log.isInfoEnabled()) {
                log.info("接收到客戶端的數據:" + new String(bytes, 0, read));
            }
            log.debug("thread id = " + Thread.currentThread().getId());
        }
        socket.getOutputStream().write("HelloClient".getBytes());
        socket.getOutputStream().flush();
    }
}
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.net.Socket;

/**
 * 客戶端代碼
 *
 * @author Robert Hou
 * @date 2020年05月01日 23:08
 **/
@Slf4j
public class BIOClient {

    @SneakyThrows
    public static void main(String[] args) {
        Socket socket = new Socket("localhost", 9000);
        //向服務端發送數據
        socket.getOutputStream().write("HelloServer".getBytes());
        socket.getOutputStream().flush();
        log.info("向服務端發送數據結束");
        byte[] bytes = new byte[1024];
        //接收服務端回傳的數據
        socket.getInputStream().read(bytes);
        if (log.isInfoEnabled()) {
            log.info("接收到服務端的數據:" + new String(bytes));
        }
        socket.close();
    }
}

2 NIO

NIO是同步非阻塞模型,服務器實現模式爲一個線程可以處理多個請求連接,客戶端發送的連接請求都會註冊到多路複用器selector上,多路複用器輪詢到連接有IO請求就交給後邊的線程進行處理。Redis就是典型的NIO線程模型,selector收集所有連接的事件並且轉交給後端線程,線程連續執行所有事件命令並將結果寫回客戶端。

NIO的示例代碼如下:

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

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;

/**
 * 服務端代碼
 *
 * @author Robert Hou
 * @date 2020年05月01日 23:09
 **/
@Slf4j
public class NIOServer {

    @SneakyThrows
    public static void main(String[] args) {

        //創建一個在本地端口進行監聽的服務Socket通道.並設置爲非阻塞方式
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //必須配置爲非阻塞才能往selector上註冊,否則會報錯,selector模式本身就是非阻塞模式
        ssc.configureBlocking(false);
        ssc.socket().bind(new InetSocketAddress(9000));
        //創建一個選擇器selector
        Selector selector = Selector.open();
        //把ServerSocketChannel註冊到selector上,並且selector對客戶端accept連接操作感興趣
        ssc.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            log.info("等待事件發生。。。");
            //輪詢監聽channel裏的key,select是阻塞的,accept()也是阻塞的
            selector.select();
            log.info("有事件發生了。。。");
            //有客戶端請求,被輪詢監聽到
            Iterator<SelectionKey> it = selector.selectedKeys().iterator();
            while (it.hasNext()) {
                SelectionKey key = it.next();
                //刪除本次已處理的key,防止下次select重複處理
                it.remove();
                handle(key);
            }
        }
    }

    @SneakyThrows
    private static void handle(SelectionKey key) {
        if (key.isAcceptable()) {
            log.info("有客戶端連接事件發生了。。。");
            ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
            //NIO非阻塞體現:此處accept方法是阻塞的,但是這裏因爲是發生了連接事件,所以這個方法會馬上執行完,不會阻塞
            //處理完連接請求不會繼續等待客戶端的數據發送
            SocketChannel sc = ssc.accept();
            sc.configureBlocking(false);
            //通過Selector監聽Channel時對讀事件感興趣
            sc.register(key.selector(), SelectionKey.OP_READ);
        } else if (key.isReadable()) {
            log.info("有客戶端數據可讀事件發生了。。。");
            SocketChannel sc = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            //NIO非阻塞體現:首先read方法不會阻塞,其次這種事件響應模型,當調用到read方法時肯定是發生了客戶端發送數據的事件
            int len = sc.read(buffer);
            if (len != -1) {
                if (log.isInfoEnabled()) {
                    log.info("讀取到客戶端發送的數據:" + new String(buffer.array(), 0, len));
                }
            }
            ByteBuffer bufferToWrite = ByteBuffer.wrap("HelloClient".getBytes());
            sc.write(bufferToWrite);
            key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
        } else if (key.isWritable()) {
            key.channel();
            log.info("write事件");
            //NIO事件觸發是水平觸發
            //使用Java的NIO編程的時候,在沒有數據可以往外寫的時候要取消寫事件,
            //在有數據往外寫的時候再註冊寫事件
            key.interestOps(SelectionKey.OP_READ);
        }
    }
}
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

/**
 * 客戶端代碼
 *
 * @author Robert Hou
 * @date 2020年05月01日 23:09
 **/
@Slf4j
public class NIOClient {

    /**
     * 通道管理器
     */
    private Selector selector;

    /**
     * 啓動客戶端測試
     */
    @SneakyThrows
    public static void main(String[] args) {
        NIOClient client = new NIOClient();
        client.initClient("127.0.0.1", 9000);
        client.connect();
    }

    /**
     * 獲得一個Socket通道,並對該通道做一些初始化的工作
     *
     * @param ip   連接的服務器的ip
     * @param port 連接的服務器的端口號
     */
    @SneakyThrows
    public void initClient(String ip, int port) {
        //獲得一個Socket通道
        SocketChannel channel = SocketChannel.open();
        //設置通道爲非阻塞
        channel.configureBlocking(false);
        //獲得一個通道管理器
        this.selector = Selector.open();
        //客戶端連接服務器,其實方法執行並沒有實現連接,需要在listen()方法中調
        //用channel.finishConnect();才能完成連接
        channel.connect(new InetSocketAddress(ip, port));
        //將通道管理器和該通道綁定,併爲該通道註冊SelectionKey.OP_CONNECT事件。
        channel.register(selector, SelectionKey.OP_CONNECT);
    }

    /**
     * 採用輪詢的方式監聽selector上是否有需要處理的事件,如果有,則進行處理
     */
    @SneakyThrows
    public void connect() {
        //輪詢訪問selector
        while (true) {
            selector.select();
            //獲得selector中選中的項的迭代器
            Iterator<SelectionKey> it = this.selector.selectedKeys().iterator();
            while (it.hasNext()) {
                SelectionKey key = it.next();
                //刪除已選的key,以防重複處理
                it.remove();
                //連接事件發生
                if (key.isConnectable()) {
                    SocketChannel channel = (SocketChannel) key.channel();
                    //如果正在連接,則完成連接
                    if (channel.isConnectionPending()) {
                        channel.finishConnect();
                    }
                    //設置成非阻塞
                    channel.configureBlocking(false);
                    //在這裏可以給服務端發送信息哦
                    ByteBuffer buffer = ByteBuffer.wrap("HelloServer".getBytes());
                    channel.write(buffer);
                    //在和服務端連接成功之後,爲了可以接收到服務端的信息,需要給通道設置讀的權限。
                    //獲得了可讀的事件
                    channel.register(this.selector, SelectionKey.OP_READ);
                } else if (key.isReadable()) {
                    read(key);
                }
            }
        }
    }

    /**
     * 處理讀取服務端發來的信息 的事件
     *
     * @param key SelectionKey
     */
    @SneakyThrows
    public void read(SelectionKey key) {
        //和服務端的read方法一樣
        //服務器可讀取消息:得到事件發生的Socket通道
        SocketChannel channel = (SocketChannel) key.channel();
        //創建讀取的緩衝區
        ByteBuffer buffer = ByteBuffer.allocate(512);
        int len = channel.read(buffer);
        if (len != -1) {
            if (log.isInfoEnabled()) {
                log.info("客戶端收到信息:" + new String(buffer.array(), 0, len));
            }
        }
    }
}

3 AIO

AIO是異步非阻塞模型,採用訂閱-通知模式,由操作系統完成後回調通知服務端程序啓動線程去處理。

AIO的示例代碼如下:

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;

/**
 * 服務端代碼
 *
 * @author Robert Hou
 * @date 2020年05月01日 23:09
 **/
@Slf4j
public class AIOServer {

    @SneakyThrows
    public static void main(String[] args) {
        final AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(9000));

        serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {

            @Override
            @SneakyThrows
            public void completed(AsynchronousSocketChannel socketChannel, Object attachment) {
                //在此接收客戶端連接,如果不寫這行代碼後面的客戶端連接連不上服務端
                serverChannel.accept(attachment, this);
                log.debug(String.valueOf(socketChannel.getRemoteAddress()));
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                socketChannel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {

                    @Override
                    public void completed(Integer result, ByteBuffer buffer) {
                        buffer.flip();
                        log.info(new String(buffer.array(), 0, result));
                        socketChannel.write(ByteBuffer.wrap("HelloClient".getBytes()));
                    }

                    @Override
                    public void failed(Throwable exc, ByteBuffer buffer) {
                        exc.printStackTrace();
                    }
                });
            }

            @Override
            public void failed(Throwable exc, Object attachment) {
                exc.printStackTrace();
            }
        });

        Thread.sleep(Integer.MAX_VALUE);
    }
}
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;

/**
 * 客戶端代碼
 *
 * @author Robert Hou
 * @date 2020年05月01日 23:10
 **/
@Slf4j
public class AIOClient {

    @SneakyThrows
    public static void main(String[] args) {
        AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
        socketChannel.connect(new InetSocketAddress("127.0.0.1", 9000)).get();
        socketChannel.write(ByteBuffer.wrap("HelloServer".getBytes()));
        ByteBuffer buffer = ByteBuffer.allocate(512);
        Integer len = socketChannel.read(buffer).get();
        if (len != -1) {
            if (log.isInfoEnabled()) {
                log.info("客戶端收到信息:" + new String(buffer.array(), 0, len));
            }
        }
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章