BIO & NIO

IO模型
  IO模型就是说用什么样的通道进行数据的发送和接收,Java共支持3种网络编程IO模式:BIO,NIO,AIO
 
BIO(Blocking IO)同步阻塞模型,一个客户端连接对应一个处理线程
缺点:
1、IO代码里read操作是阻塞操作,如果连接不做数据读写操作会导致线程阻塞,浪费资源
2、如果线程很多,会导致服务器线程太多,压力太大,比如C10K问题
应用场景:
BIO 方式适用于连接数目比较小且固定的架构, 这种方式对服务器资源要求比较高, 但程序简单易理解。
复制代码
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

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);
       new Thread(()->{
      try {
       handler(clientSocket);
       } catch (IOException e) {
       e.printStackTrace();
       }
       }).start();
} } public static void handler(Socket clientSocket) throws IOException { byte[] bytes = new byte[1024]; System.out.println("准备 Read.."); //接收客户端的数据,阻塞方法,没有数据可读时就阻塞 int read = clientSocket.getInputStream().read(bytes); System.out.println("read 完毕"); if( read != -1) { System.out.println("接收到客户的数据:" + new String(bytes,0,read)); } clientSocket.getOutputStream().write("HelloClient".getBytes()); clientSocket.getOutputStream().flush(); } }
复制代码
复制代码
import java.io.IOException;
import java.net.Socket;

public class SocketClient {

    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("localhost", 9000);

        // 向服务端发送数据
        socket.getOutputStream().write("HelloServer".getBytes());
        socket.getOutputStream().flush();

        System.out.println("向服务端发送数据结束");

        byte[] bytes = new byte[1024];
        int read = socket.getInputStream().read(bytes);
        //接收服务端回收的数据
        System.out.println("接收到服务端到数据:" + new String(bytes,0,read));
        socket.close();
    }
}
复制代码

 NIO(Non Blocking IO)

同步非阻塞,服务器实现模式为 一个线程可以处理多个请求(链接), 客户端发送到链接请求都会注册到 多路复用器selector 上, 多路复用器轮询到链接有 IO 请求就进行处理, JDK1.4 开始引入。

应用场景: 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.Iterator;
import java.util.List;

/**
 * 此方式链接太多,会有大量无效遍历,加入 10000个链接,只有1000个有写数据,其他9000个没有断开,每次还是要轮询一万次,十分之九的遍历都是无效的
 */
public class NioServer {

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

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

        //创建 NIO ServerSocketChannel, 与 BIO 的ServerSocket 类似
        ServerSocketChannel serverSocket = ServerSocketChannel.open();
        serverSocket.socket().bind(new InetSocketAddress(9000));

        //设置ServerSocketChannel 为 非阻塞
        serverSocket.configureBlocking(false);
        System.out.println("服务启动成功");

        while (true) {
            // 非阻塞模式 accept 方法不会阻塞, 否则会阻塞
            // NIO 的非阻塞是有操作系统内部实现的, 底层调用了 linux 内核的 accept 函数
            SocketChannel socketChannel = serverSocket.accept();
            if (socketChannel != null) {        //如果有客户端链接
                System.out.println("链接成功");
                // 设置SocketChannel 为非阻塞
                socketChannel.configureBlocking(false);
                //保存客户端链接在 list 中
                channelList.add(socketChannel);
            }

            //遍历链接进行数据读取
            Iterator<SocketChannel> iterator = channelList.iterator();
            while(iterator.hasNext()) {
                SocketChannel sc = iterator.next();
                ByteBuffer byteBuffer = ByteBuffer.allocate(128);

                // 非阻塞模式 read 方法不会阻塞,否则会阻塞
                int len = sc.read(byteBuffer);
                //如果有数据,把数据打印出来
                if(len > 0) {
                    System.out.println("接收到消息:" + new String(byteBuffer.array(),0,len));
                }else if(len == -1) {
                    iterator.remove();
                    System.out.println("客户端断开连接");
                }
            }
        }
    }
}
复制代码
复制代码
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;

/**
 * 引入 多路复用器 selector
 */
public class NioSelectorServer {

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

        //创建NIO ServerSocketChannel
        ServerSocketChannel serverSocket = ServerSocketChannel.open();
        serverSocket.socket().bind(new InetSocketAddress(9000));

        // 设置ServerSocketChannel 为 非阻塞
        serverSocket.configureBlocking(false);
        //打开Selector 处理 channel, 即创建 epoll
        Selector selector = Selector.open();
        //把 ServerSocketChannel 注册到Servlet 上,并且 selector 对客户端 accept 链接操作感兴趣
        serverSocket.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("服务启动成功");

        while(true) {
            //阻塞等待需要处理的事件发生
            selector.select();

            // 获取selector 中注册的全部事件的 SelectionKey 实例
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();

            // 遍历 SelectionKey 对事件进行处理
            while(iterator.hasNext()) {
                SelectionKey key = iterator.next();
                // 如果是 OP_ACCEPT 事件, 则进行链接获取和事件注册
                if(key.isAcceptable()) {
                    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()) {
                    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(),0,len));
                    }else if(len == -1) {
                        System.out.println("客户端断开链接");
                        socketChannel.close();
                    }
                }
                // 从事件集合里删除本次处理到 key, 防止下次 select 重复处理
                iterator.remove();
            }
        }
    }
}
复制代码

NIO 有 三大核心组件: Channel(通道), Buffer(缓冲区),Selector(多路复用器)

1、channel 类似于流, 每个 channel 对应一个 buffer 缓冲区, buffer 底层就是个数组

2、channel 会注册到 selector 上,由 selector 根据 channel 读写事件的发生将其交由某个空闲的线程处理

3、NIO 的 Buffer 和 channel 都是既可以读也可以写 

posted @   长弓射大狗  阅读(23)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示