Netty通信技术(上)

一、Java NIO

1.为什么要有NIO

1.基于java.net.ServerSocket包和java.net.Socket包下的API封装的是同步阻塞IO模型
2.但是Java.nio包下封装的API是支持同步非阻塞IO模型以及IO多路复用模型

2.java NIO概述

1.NIO称为New IO,也可以成为非阻塞IO
2.三大组件:Channel(通道)、Buffer(缓冲区)、Selector(选择器/多路复用器)

二、Channel

1.什么是Channl

1.Channel(通道):就是一个数据的通道,通过Channel对数据的读写
2.Channel与传统的流不同之处在于Channel是双向的,即Channel是全双工模式

2.传统的流如下图:
在这里插入图片描述
3.Channel流如下图
在这里插入图片描述
4.Channel实现体系

可分为网络读写Channel和文件读写Channel

5.Channel关系图
在这里插入图片描述

1.ServerSocketChannel底层代表的是一个服务器的Socket
2.SocketChannel底层代表了一个客户端Socket

注意:ServerSocketChannel与SocketChannel是网络读写,fileChannel是文件读写
6.网络Channel的核心功能

1.ServerSocketChannel:绑定端口,开启监听,接收连接
2.SocketChannel:连接服务端,进行读写操作

三、Buffer

1.什么是Buffer

1.是一个缓冲区,也是一个容器,在java NIO中数据都是用Buffer存储的
2.通过Channel读取接收的数据先写到Buffer中,应用程序在从Buffer中读取
3.通过Channel写数据也是先将数据写到Buffer中,再将Buffer交给Channel,Channel读取然后写到网络中

2.图示:
在这里插入图片描述
2.Buffer集成关系图示:
在这里插入图片描述
注意:ByteBuffer是最受欢迎的
3.Buffer如何创建

ByteBuffer是一个抽象类,提供了静态工厂方法来创建一个实例

在这里插入图片描述
3.1:allocation方法

1.使用allocate方法,我们将得到一个非直接缓冲区
2.真实创建的是一个HeapByteBuffer的实例
3.数据是存储在堆区按容量开辟空间的字节数组中

allocate(int capacity):
在这里插入图片描述
3.2:allocateDirect方法

1.使用allocateDirect方法,我们将得到一个直接缓冲区
2.真实创建的是一个DirectByteBuffer的实例
3.数据是存储在一块操作系统直接内存中(堆外)
4.DirectByteBuffer中持有内存地址用以操作这块内存

allocateDirect(int capacity)
在这里插入图片描述
4.ByteBuffer如何读取数据

不管buffer的数据真实存储在哪,对于数据的操作行为是一样的,在Buffer类中定义了四个索引,这些索引记录了底层数据元素的状态
	capacity:缓冲区可以容纳的数据元素的最大数量  
	limit:停止读写的索引  
	position:当前要读或写的索引,读写操作是从position开始,到limit停止  
	mark:可用于记录position位置,默认-1

4.1:图示
在这里插入图片描述
5.ByteBuffer操作索引可以分为4类
在这里插入图片描述
6.ByteBuffer读写API

1,向ByteBuffer 中写数据:核心方法是put
	put(byte[] src)  
	put(byte[] src, int offset, int length)  
	put(ByteBuffer byteBuffer)  
2,从ByteBuffer中读数据:核心方法是get
	get(byte[] dst)  
	get(byte[] dst, int offset, int length)

6.1put(byte[] src)
在这里插入图片描述
6.2put(byte[] src, int offset, int length)
在这里插入图片描述
6.3put(ByteBuffer byteBuffer)
在这里插入图片描述
6.4get(byte[] dst)
在这里插入图片描述
6.5get(byte[] dst, int offset, int length)
在这里插入图片描述

四、Selector

1.什么是Selector

Selector是多路复用器,也可以说是多路选择器,底层封装的是多路IO模型,在linux上采用的是epoll

2.核心功能

1.检测:将Channel注册到selector上并指定要监听的事件,selector会不断轮询注册在其上的Channel,检测其事件是否就绪
2.获取key:Selector每次返回已就绪Channel对应的SelectionKey,通过SelectionKey可获取事件信息,然后进行后续操作

在这里插入图片描述
3.工作流程

1.封装IO多路复用的相关操作,采用的是epoll实现,主要用于检测Channel事件的就绪状态  
2.将channel注册给selector并指定监听的事件,  
3.selector会不断轮询检测,当channel就绪后会返回该channel

4.图示
在这里插入图片描述

五、基于JAVA NIO 编写服务端程序

1.核心逻辑分析

1,创建ServerSocketChannel,绑定地址端口,设置阻塞标识 
2,打开一个Selector,将ServerSocketChannel注册到selector上,监听OP_ACCEPT事件  
3,在一个loop中执行selector的select  
4,根据返回的SelectionKey信息判断事件类型,根据事件类型完成不同操作  
5,如果是ACCEPT事件则accept一个客户端连接SocketChannel,设置阻塞标识并将其注册到selector上,监听OP_READ事件  
6,如果是读写事件则进行读写操作

2.案例的核心要点

1,ServerSocketChannel绑定地址端口开启监听并注册到selector  
2,selector轮询执行select,然后根据返回的SelectionKey进行分支逻辑  
3,读写数据时基于Buffer操作

3.代码如下:

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.logging.Logger;


/**
 * @author Andy
 * NIO 服务端程序
 */
public class NIOServerDemo {

    public static final Logger log= Logger.getLogger(NIOServerDemo.class.getName());

    public static void main(String[] args) throws IOException {
        // 创建ServerSocketChannel,,
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 绑定地址端口
        serverSocketChannel.bind(new InetSocketAddress(9999));
        // 设置非阻塞标识
        serverSocketChannel.configureBlocking(false);
        // 打开一个Selector,,监听OP_ACCEPT事件
        Selector selector = Selector.open();
        // 将ServerSocketChannel注册到selector上
        SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        // 在一个loop中执行selector的select
        while (true) {
            // select就是检测是否有就绪的事件
            int select = selector.select();
            // 有多少事件已经就绪
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey key = iterator.next();
                iterator.remove();
                // 处理key
                processSelectedKey(key,selector);
            }
        }
    }

    private static void processSelectedKey(SelectionKey key, Selector selector) throws IOException {
        // 判断key是否有效
        if (key.isValid()) {
            // 监听类型
            if (key.isAcceptable()) {
                // 获取服务端ServerSocketChannel
                ServerSocketChannel serverSocketChannel = (ServerSocketChannel)key.channel();
                // 获取客户端SocketChannel
                SocketChannel socketChannel = serverSocketChannel.accept();
                // 设置非阻塞的
                socketChannel.configureBlocking(false);
                // 监听读数据
                socketChannel.register(selector,SelectionKey.OP_READ);
            }
            // 读取类型
            if (key.isReadable()) {
                // 获取客户端SocketChannel
                SocketChannel socketChannel = (SocketChannel)key.channel();
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                int read = socketChannel.read(byteBuffer);
                // 有数据
                if (read > 0){
                    // 读写模式切换flip
                    byteBuffer.flip();
                    byte[] bytes = new byte[byteBuffer.remaining()];
                    // 获取数据
                    byteBuffer.get(bytes);
                    // 解码
                    String s = new String(bytes, "UTF-8");
                    log.info("收到客户端数据"+s);

                    // 给客户端发送数据
                    byte[] serverByte = "我是服务端:已收到客户端数据".getBytes();
                    ByteBuffer serverByteBuffer = ByteBuffer.allocate(serverByte.length);
                    serverByteBuffer.put(serverByte);
                    serverByteBuffer.flip();
                    socketChannel.write(serverByteBuffer);
                }
            }
        }
    }
}

4.测试
4.1服务端

在这里插入图片描述
4.2客户端

在这里插入图片描述

六、Reactor线程模型

1.NIO编写的服务端性能瓶颈分析

1.服务端单线程selector轮询后处理了所有的事情,包括接收连接,读写数据,业务处理,职责不清晰
2.根源:线程模型没有划分好

2线程模型指导思想

Reactor线程模型,是一种并发编程线程模型,一种具有指导意义的编程思想,并非java专属

3.目前对应哪种reactor模型(单线程)

Reactor单线程模型:接收连接,IO处理,业务处理均在一个线程中完成

4.单线程图示
在这里插入图片描述

在这里插入图片描述
5.优化思路(多线程)

1.将复杂且耗时的数据编解码及业务处理独立出来,扔给业务线程池来进行处理
2.Reactor多线程模型:连接的接收和IO处理在reactor线程中完成,编解码和业务处理提交给业务线程

6.多线程图示
在这里插入图片描述

在这里插入图片描述
7.单线程与多线程的总结

Reactor单线程模型的弊端:
	1、职责不清晰,接收连接,IO操作,业务处理均在一个线程中完成
	2、性能低下,无法支撑高并发场景
Reactor多线程模型的特点
	1、将编解码和业务处理剥离到单独的线程池中执行
	2、一定程度提升了性能

8.如何优化多线程模型 —> 主从Reactor多线程模型工作模式

1.思路:接收连接和IO处理进一步切分,分别用单独的线程处理
2.主从Reactor多线程模型:mainReactor线程只负责连接的接收,subReactor线程负责IO处理,编解码等业务操作提交给业务线程进行处理,subReactor线程可以根据需要有多个。

主从多线程图示:
在这里插入图片描述
在这里插入图片描述
9.主从线程模式如何划分

1,1个MainReactor线程负责接收连接,并将连接交给其中一个subReactor线程
2、subReactor线程负责检测事件和IO操作,并将读到的数据提交给工作线程进行业务处理
3,工作线程负责数据的编解码及业务处理

10.主从Reactor多线程的优劣

优势:
	1,分工明确:MainReactor 线程与 SubReactor 线程的职责分工明确,MainReactor 线程只需要接收新连接,SubReactor 线程完成事件检测和IO操作,工作线程完成具体业务处理
	2,交互简单:MainReactor 线程与 SubReactor 线程的数据交互简单, MainReactor 线程只需要把新连接传给SubReactor 线程,SubReactor 线程无需返回数据
	3,多个SubReactor 线程能够应对更高的并发请求
优劣:编程复杂度较高

11.主从模式的总结

这种模式也被叫做服务器的 1+M+N 线程模式,即使用该模式开发的服务器包含一个(或多个,一般来说只需要一个)连接建立线程+M 个 IO 线程+N 个业务处理线程。这是业界成熟的服务器程序设计模式,由于其优点明显,在许多项目中被广泛使用,包括 Nginx、Netty 等

12.代码
代码正在加急编写中ing…写完会上传…

1,基于JAVA NIO API 实现一个服务端应用程序,在9999端口开启服务
2,服务端要求基本满足主从reactor多线程模式
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值