网络编程-Netty-01 Nio

本文详细介绍了BIO的同步和异步阻塞模型,包括其源码示例,并深入剖析了NIO的非阻塞I/O机制,涉及Selector、SocketChannel和EPoll等关键概念。NIO的优点、缺点及适用场景,以及JVM跨平台实现和Linux内核函数的应用被逐一探讨。
摘要由CSDN通过智能技术生成

1、IO模型

目前IO模型实际指数据进行收发的通道模型,常见磁盘IO、网络IO等,是互联网发展到现在各架构体系中最常讨论的话题之一。因为IO的瓶颈限制尝尝导致开发者们不得不通过扩容来解决,而好的IO模型无疑能减少因IO瓶颈而造成的资源浪费。

JAVA目前支持的3中网络变成IO模型:BIO、NIO、AIO

2、BIO(blocking io)

2.1 BIO介绍

同步或异步阻塞式收发模型,客户端的请求需要服务端线程等待处理。

  • 同步阻塞模型:则现成阻塞其他客户端的连接,直至完成当前连接的accept()连接事件或read()读写事件。
  • 异步阻塞模型:常见于在服务端开启线程池,主线程用于接收客户端的accept()连接事件,再有线程池处理客户端的读写事件。但当客户端连接过多,或长连接过多时,服务端仍无法满足非阻塞的需求,而只能让溢出的连接保持阻塞等待,直至超时或中断放弃连接。
    在这里插入图片描述
    在这里插入图片描述
2.2 源码使用demo
//BIO客户端Demo
public class BioSocketClient {
    public static void main(String[] args) throws IOException {
        Socket client = new Socket("127.0.0.1",9090);
        client.getOutputStream().write("hello".getBytes(StandardCharsets.UTF_8));
        client.getOutputStream().flush();
        System.out.println("向服务端发送数据");
        byte[] bytes = new byte[1024];
        //接收服务端的响应
        client.getInputStream().read(bytes);
        System.out.println("接收服务端数据:"+ new String(bytes));
        client.close();
    }
}

//服务端阻塞等待连接
public class BioSocketServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(9090);
        while (true){
            System.out.println("等待客户端连接...");
            //accept 阻塞方法
            Socket client = serverSocket.accept();
            System.out.println("有客户端完成连接");
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        handleRequest(client);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
	//服务端阻塞等到客户端的读事件
    public static void handleRequest(Socket socket) throws IOException {
        byte[] bytes = new byte[1024];
        System.out.println("阻塞读取客户端消息");
        //read 阻塞方法,接受客户端发送的数据
        int read = socket.getInputStream().read(bytes);
        System.out.println("read数据完成");
        if(read != -1){
            System.out.println("客户端消息:"+ new String(bytes,0,read));
        }
        //处理服务器的响应
        socket.getOutputStream().write("hello client".getBytes(StandardCharsets.UTF_8));
        socket.getOutputStream().flush();
    }
}
2.3 BIO优缺点及应用场景分析
2.3.1 缺点
  • accept()、read()都是阻塞操作,在连接后不做读写会导致线程资源阻塞浪费
  • 当需要更多的连接时,线程数、内存都要飙升,服务端压力很大,常见client10000的C10k问题
2.3.2 使用场景

BIO适用于连接数少且连接稳定的场景,对服务器的压力小

3、NIO

3.1 NIO介绍

NIO被称为Non Blocking io 或new io。可以实现非阻塞式请求,通过将客户端发送的连接注册到多路复用器(selector),保证一个线程可以处理多个连接的连接及读写事件。

3.2 NIO线程模型

在这里插入图片描述
在这里插入图片描述
应用:目前的nio已被广泛应用,技术路线可见
Nio —> Netty —> vert等等,在产品应用中也非常广泛。

3.2 Nio-client/server module

在上图Nio异步非阻塞模型中引入了多个概念,SocketChannel(通道)、ServerSocketChannel、Selector(多路复用器)、SelectorKeys、Buffer(缓冲区)等,我们先行使用基本功能,构建简单的客户端、服务端交互代码,随后对其概念及源码做剖析。
客户端:

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.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;

public class NioClient {
    //通道管理器-> 多通道复用
    private Selector selector;

    public static void main(String[] args) throws IOException {
        NioClient nioClient = new NioClient();
        nioClient.initClient();//初始化建立连接
        nioClient.connect();//连接后读和发送消息
    }

    public void initClient() throws IOException {
        SocketChannel channel = SocketChannel.open();
        //channel通道设置为非阻塞式
        channel.configureBlocking(false);

        this.selector = Selector.open();
        //配置服务器地址
        channel.connect(new InetSocketAddress("127.0.0.1",9090));
        //将通道管理和通道绑定,并且监听连接事件
        channel.register(selector, SelectionKey.OP_CONNECT);
    }

    /**
     * 监听建连事件,发生交互
     */
    public void connect() throws IOException {
        while (true){
            //非阻塞式等待事件驱动
            selector.select();

            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()){
                SelectionKey key = (SelectionKey) iterator.next();
                iterator.remove();
                //连接事件 -> 发送消息给客户端
                if (key.isConnectable()){
                    SocketChannel channel1 = (SocketChannel) key.channel();
                    //事件监听结果为正在连接,设置为连接完成
                    if (channel1.isConnectionPending()){
                        channel1.finishConnect();
                    }
                    channel1.configureBlocking(false);
                    ByteBuffer buffer = ByteBuffer.wrap("hello server".getBytes(StandardCharsets.UTF_8));
                    channel1.write(buffer);
                    //客户端与服务器建立连接后,监听读写事件
                    channel1.register(selector,SelectionKey.OP_READ);
                }else if (key.isReadable()){
                    //读事件 -> 接收服务器响应内容
                    SocketChannel channel = (SocketChannel) key.channel();
                    //创建读取的缓冲区
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    int read = channel.read(buffer);
                    if (read != -1){
                        System.out.println("客户端接受消息:"+ new String(buffer.array()));
                    }
                }
            }

        }
    }
}

服务端:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

public class NioSelectorServer {
    public static void main(String[] args) throws IOException {
        //创建Nio channel
        ServerSocketChannel serverSocket = ServerSocketChannel.open();
        serverSocket.socket().bind(new InetSocketAddress(9090));
        //将服务侧channel设置为非阻塞式
        serverSocket.configureBlocking(false);
        //打开Selector 处理channel
        Selector selector = Selector.open();
        //将channel注册到Selector上,并由selector监听accept()事件
        SelectionKey selectionKey = serverSocket.register(selector,SelectionKey.OP_ACCEPT);
        System.out.println("服务器启动并监听accept事件成功");
        while (true){
            //阻塞等待需要处理的事件
            selector.select();

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

            //遍历KeySet对事件进行处理
            while (iterator.hasNext()){
                SelectionKey next = iterator.next();
                //监听到发生了OP_ACCEPT事件
                if (next.isAcceptable()) {
                    ServerSocketChannel channel = (ServerSocketChannel) next.channel();
                    SocketChannel accepted = channel.accept();
                    //设置非阻塞式读事件监听
                    accepted.configureBlocking(false);
                    accepted.register(selector,SelectionKey.OP_READ);
                    System.out.println("客户端连接成功");
                }else if (next.isReadable()){
                    SocketChannel channel = (SocketChannel) next.channel();
                    ByteBuffer byteBuffer = ByteBuffer.allocate(128);
                    int read = channel.read(byteBuffer);
                    //有数据则读取并打印
                    if (read > 0){
                        System.out.println("接收数据:"+new String(byteBuffer.array()));
                    }else if (read == -1){
                        System.out.println("客户端断开连接");
                        channel.close();
                    }
                }
                //完成连接事件或读时间  从select中移除
                iterator.remove();
            }
        }
    }
}
3.4 Nio及EPoll源码解析

在这里插入图片描述

3.4.1 JVM跨平台特性

针对不同操作系统,hotspot源码分别不同的类进行处理,得以实现jvm跨平台的能力。如图DefaultSelectorProvider在Windows、macosx、solaris(SunOS、Linux)等有不同的实现。
在这里插入图片描述

3.4.2 核心代码

Selector.open() //创建多路复用器

socketChannel.register() //将channel注册到多路复用器上

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

  • SocketChannel 通道,java调用linux内核函数,创建客户端或服务端在linux系统中的socket文件描述器fd。
  • Selector 多路复用器,实际是用于封装linux系统epoll文件描述器的java对象。
  • 将获取到的Socket连接的文件描述符的事件绑定到Selector对应的Epoll文件描述符上,进行事件的异步通知
3.4.3 linux内核函数

linux内核的epoll_create(256)函数
在这里插入图片描述
linux内核的epoll_ctl()函数。linux内核将监听新的fd(socket)的ADD、MODIFY、DELETE事件
在这里插入图片描述
linux内核的epoll_wait()函数。linux内核查询epoll的就绪列表rdlist,等待事件发生。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

旧梦昂志

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值