初识Netty

Netty是由JBoss提供的一个Java的开源框架,是GitHub上的独立项目。

Netty是一个异步的,基于事件驱动的网络应用框架,用于快速开发高性能、高可靠的网络IO程序。

Netty主要针对于TCP协议下,面向客户端的高并发应用,或者是Peer-to-Peer场景下的大量数据次序传输的应用。

Netty本质上是一个NIO的框架,适用于服务器通讯相关的多种应用场景。

底层是NIO,NIO底层是Java IO和网络IO,再往下是TCP/IP协议。

Netty的应用场景

1、经典的Hadoop的高性能通信和序列化组件AVRO(实现数据文件的共享),他的Netty Service是基于Netty的二次封装。

2、在分布式系统中,各个节点之间需要远程服务调用例如RPC框架dubbo。

3、无论是手游服务端还是大型网络游戏,登录服务器都是用Netty作为高性能基础通信组件。

4、地图服务器之间可以方便的通过Netty进行高性能的通信。

IO模型

IO模型很大程度的决定了程序通信的性能。

Java共支持3种IO模型:BIO,NIO,AIO。

BIO:同步阻塞IO,也就是传统阻塞型的IO,服务器实现模式是一个连接对应一个线程。客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个链接不做任何事情会造成不必要的线程开销。

NIO:同步非阻塞IO,服务器实现模式是一个线程处理多个请求,客户端发送的链接请求都会注册到多路复用器上,多路复用器轮询到链接有IO请求就进行处理。

AIO:异步非阻塞,AIO引入了异步通道的概念,采用了Proactor模式,简化了程序编写,有效的请求才启动线程,他的特点是先由操作系统完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且链接时间较长的应用。

BIO的编程流程

1、服务端启动一个ServerSocket

2、客户端启动Socket对服务器进行通信,默认情况下对每个客户端建立一个线程。

3、客户端发送请求后,先咨询服务器是否有线程响应,如果没有则会等待,或者被拒绝。

4、如果有响应,客户端线程会等待请求结束后,才会继续执行。(阻塞,同步)

public class BIOServer {

    public static void main(String[] args) throws IOException {
ExecutorService executorService = Executors.newCachedThreadPool();
//创建服务器端socket
final ServerSocket serverSocket = new ServerSocket(6666);
while (true){
System.out.println("等待连接...");
final Socket socket = serverSocket.accept();
//连接一个客户端 System.out.println("连接一个客户端");
executorService.execute(new Runnable() {
public void run() {
handler(socket);
}
});
} } //编写一个handle方法用来处理客户端通讯
public static void handler(Socket socket) {
byte[] bytes = new byte[1024];
try {
InputStream inputStream = socket.getInputStream();
//获取输入流,读取客户端发来的数据
int i;
System.out.println("线程id: "+Thread.currentThread().getId()+" 线程名称 "+Thread.currentThread().getName());
System.out.println("等待读入信息"); while ((i = inputStream.read(bytes)) != -1) {
System.out.println("客户端数据: "+new String(bytes, 0, i));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("关闭与客户端的连接....");
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
} }
}

启动main方法,打开cmd命令窗口。

输入telnet 127.0.0.1 6666 ;连接服务端,这相当于建立了一个客户端连接,然后Ctrl+],从客户端向服务器发送信息。

send hello

然后再开启一个连接。向服务器发送send success

当关闭命令行窗口后客户端与服务器的链接就断开了。从上面可知BIO编程模型,每次建立一个连接,服务端就会创建一个线程。然后每次进行读取的时候,如果客户端不发送数据,服务端线程就一直阻塞在那,直到数据读取成功。

BIO 问题分析

1、每个请求都需要创建独立的线程,与对应的客户端进行数据读入,业务处理,数据写入。

2、当并发数较大时,需要创建大量的线程来处理连接,系统资源占用较大。

3、连接建立后,如果当前线程暂时没有数据可读,则线程就阻塞在Read上,造成不必要的资源浪费。

Java NIO

  • Java NIO全称是java non-blocking IO,是指JDK提供的新API。从JDK1.4开始,Java提供了一系列改进的输入输出的新特性,被通称为NIO,是同步非阻塞的IO模型。
  • NIO相关类放在java.nio包以及子包下。
  • NIO有三大核心部分:Channel(通道),Selector(选择器),Buffer(缓冲区)。
  • NIO是面向缓冲区,或者面向块编程的。数据读取到一个稍后处理的缓冲区,需要的时候可以在缓冲区前后移动,这就增加了处理过程的灵活性,使用它可以提供非阻塞的高伸缩网络。
  • Java NIO的非阻塞模式,是一个线程从某个通道发送请求或者读取数据,但是它仅仅能得到目前可用的数据,如果当前没有任务可做,他也不会阻塞等待,它可以去完成其他的事情。
  • NIO可以做到一个线程来处理多个操作,假设有10000个请求过来,根据实际情况,可以分配50到100个线程来处理,而不是必须要创建10000个线程。
  • HTTP2.0使用了多路复用技术,做到同一个连接并发处理多个请求,而且并发请求的数量比HTTP1.1打了好几个数量级。

BIO和NIO的比较

1、BIO以流的方式处理数据,而NIO以块的方式处理数据,块IO的效率比流IO高很多

2、BIO是阻塞的,NIO是非阻塞的

3、NIO是基于字节流和字符流进行操作,而NIO是基于Channel和Buffer进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。selector用于监听多个通道的事件,比如连接请求,数据到达,因此使用单个线程就可以监听多个客户端通道。

NIO中Selector、Channel、Buffer的关系

  1. 每个channel都会对应一个buffer。
  2. 一个selector对应一个线程,一个线程对应多个channel。
  3. 程序切换到哪个channel是由Event(事件)决定的。
  4. selector会根据不同的事件,在各个通道上进行切换。
  5. buffer是一个内存块,底层有一个数组。
  6. 数据的读取和写入是通过buffer,buffer可以切换读写,通过flip方法,但是BIO是单向输出,要么是输入流,要么是输出流。
  7. channel是双向的,可以返回底层操作系统的情况,比如linux,底层的操作系统通道就是双向的。

Buffer

缓冲区:缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个容器对象(含数组),该对象提供了一组方法,可以更轻松的使用内存块;缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化。Channel提供从文件,网络读取数据的渠道,但是读取或写入的数据都必须经过Buffer。

Buffer是一个抽象类,类关系如下:

属性表示的含义

  • capacity:缓冲区容量大小,缓冲区一旦初始化不能改变
  • limit:表示缓冲区当前终点,不能对缓冲区超过极限位置进行读写,limit可以修改。
  • position:位置,每次读取缓冲区,都会改变。
  • mark:标记 -1

ByteBuffer主要的方法如下:

   //创建初始缓冲区
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
//设置缓冲区的初始容量
public static ByteBuffer allocate(int capacity) {
if (capacity < 0)
throw new IllegalArgumentException();
return new HeapByteBuffer(capacity, capacity);
} //构造初始化位置offset和上届length的缓冲区
public static ByteBuffer wrap(byte[] array,
int offset, int length)
{
try {
return new HeapByteBuffer(array, offset, length);
} catch (IllegalArgumentException x) {
throw new IndexOutOfBoundsException();
}
} //把数组放到缓冲区中使用
public static ByteBuffer wrap(byte[] array) {
return wrap(array, 0, array.length);
}

Channel

NIO的通道类似于流,但是区别如下。

  • 通道可以同时进行读写,而流只能读或者只能写
  • 通道可以实现异步读写数据
  • 通道可以从缓冲区读数据,也可以写数据到缓冲

BIO中的stream是单向的,例如FileInputStream对象只能进行读取数据的操作,NIO的Channel是双向的,可以读操作,也可以写操作。

Channel在NIO中是一个接口

常用的Channel类有:FileChannel,DatagramChannel、ServerSocketChannel和SocketChannel。

FileChannel用户文件的数据读写,DatagramChannel用于UDP的数据读写,ServerSocketChannel和SocketChannel用于TCP的数据读写。

FileChannel

FileChannel主要用来对文件进行IO操作

//从通道中读取数据放入缓冲区
public abstract int read(ByteBuffer dst) throws IOException;
//将缓冲区的数据写入通道
public abstract int write(ByteBuffer src) throws IOException;
//从目标通道中复制数据到当前通道
public abstract long transferTo(long position, long count,
WritableByteChannel target)
throws IOException;
//把数据从当前通道复制到目标通道
public abstract long transferFrom(ReadableByteChannel src,
long position, long count)
throws IOException;

实例:将数据写入到本地文件

文件不存在就创建

/**
* 创建file_1.txt文件,向文件中写入“前研工作室”,通过管道写入
*/
public class NIOFileChannel {
public static void main(String[] args) throws IOException { //创建文件输出流
FileOutputStream fileOutputStream = new FileOutputStream("D:\\file_1.txt");
//写入的数据
String message = "前研工作室";
//获取一个管道,类型其实是FileChannelImpl
FileChannel channel = fileOutputStream.getChannel();
//创建一个缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//将数据放入缓冲区
byteBuffer.put(message.getBytes());
//缓冲区转变成读状态
byteBuffer.flip();
//将byteBuffer数据写入fileChannel
channel.write(byteBuffer);
//关闭输入流
fileOutputStream.close(); }
}

执行结果

使用前面学到的ByteBuffer和FileChannel将之前创建的文件file_1.txt中的数据读取到控制台上

代码实现

public static String readFileByChannel() throws IOException {
//创建文件输入流
File file = new File("D:\\file_1.txt");
FileInputStream fileInputStream = new FileInputStream(file);
//创建缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());
//获取通道
FileChannel channel = fileInputStream.getChannel();
//读取到通道中
channel.read(byteBuffer);
return new String(byteBuffer.array()); }

读取结果

 public static void main(String[] args) throws IOException {
String messages = readFileByChannel();
System.out.println("file_01: "+messages);
//file_01: 前研工作室
}

再来一个实例,使用Buffer完成文件的复制

要求

  • 使用FileChannel和read,write完成文件的拷贝
  • 拷贝文本文件file_1.txt,放在当前目录下

代码实现:

public static void copyFileByChannelAndBuffer(File file) throws IOException {
//从指定文件中读取数据复制到file_2.txt
FileInputStream fileInputStream = new FileInputStream(file);
FileChannel inputStreamChannel = fileInputStream.getChannel();
//写入到file_2.txt文件中
FileOutputStream fileOutputStream = new FileOutputStream("file_2.txt");
FileChannel outputStreamChannel = fileOutputStream.getChannel(); ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length()); while (true) {
//每次读取之前要清空缓冲区,否则会写入
byteBuffer.clear();
int read = inputStreamChannel.read(byteBuffer);
//文件读取完毕退出循环
if (read == -1) {
break;
}
byteBuffer.flip();
outputStreamChannel.write(byteBuffer); }
//关闭相关流的操作
fileInputStream.close();
fileOutputStream.close();
}

最终执行结果

实例:拷贝文件通过transferFrom方法

使用FileChannel和方法transferFrom,完成文件的拷贝

需求:将D盘下的pic_02.jpg复制到当前目录下

第一步创建相关的输入输出流,第二步是获取对应流的通道,第三步是使用transferFrom去完成拷贝,最后关闭相关通道和流。

public class NiOFileChannel2 {
public static void main(String[] args) throws IOException {
//创建相关流
FileInputStream fileInputStream = new FileInputStream("D:\\pic_02.jpg");
FileOutputStream fileOutputStream = new FileOutputStream("D:\\pic_03.jpg");
//获取对应的fileChannel
FileChannel inputStreamChannel = fileInputStream.getChannel();
FileChannel outputStreamChannel = fileOutputStream.getChannel();
//使用transferFrom去完成拷贝
outputStreamChannel.transferFrom(inputStreamChannel,0,inputStreamChannel.size());
//关闭相关通道和流
inputStreamChannel.close();
outputStreamChannel.close();
fileInputStream.close();
fileOutputStream.close(); } }

关于Buffer和Channel的注意事项和细节

ByteBuffer支持类型化的put和get,put放入的是什么数据类型,get就应该使用相应的数据类型来取出.当遇到java.nio.BufferOverflowException异常时,可能是你所创建的缓冲区带下已经不能容纳你所加入的数据。

public class NIOByteBufferGetPut {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(10);
long val = 12345;
buffer.putInt(100);
//因为缓冲区一共分配了10个字节,int占用4个字节,long占8个字节,两者加起来已经大于10个字节,所以会抛出java.nio.BufferOverflowException
buffer.putLong(val);
buffer.putChar('特');
buffer.putShort((short) 14);
buffer.flip();
System.out.println(buffer.getInt());
System.out.println(buffer.getLong());
System.out.println(buffer.getChar());
System.out.println(buffer.getShort()); }
}

可以将一个普通的Buffer转成只读的Buffer。

public static void main(String[] args) {
IntBuffer intBuffer = IntBuffer.allocate(10);
for (int i = 0; i < intBuffer.capacity(); i++) {
intBuffer.put(i*2);
}
IntBuffer intBuffer1 = intBuffer.asReadOnlyBuffer();
System.out.println(intBuffer1.getClass().toString()); while (intBuffer1.hasRemaining()) {
System.out.println(intBuffer1.get());
}
intBuffer1.put(1);// 会抛出ReadOnlyBufferException异常
}

asReadOnlyBuffer()方法返回的是IntBuffer

duplicate方法中是创建了一个HeapIntBufferR实例,biang通过构造函数将readOnly属性设置成了true。HeapIntBufferR是IntBuffer的子类。

protected HeapIntBufferR(int[] buf,
int mark, int pos, int lim, int cap,
int off)
{
super(buf, mark, pos, lim, cap, off);
this.isReadOnly = true;
}
public IntBuffer duplicate() {
return new HeapIntBufferR(hb,
this.markValue(),
this.position(),
this.limit(),
this.capacity(),
offset);
}

未完待续,以上总结的可能有错误,欢迎指出!!

Netty与NIO的更多相关文章

  1. android netty5.0 编译时 java.lang.NoClassDefFoundError: io.netty.channel.nio.NioEventLoopGroup

    android netty5.0 编译时 java.lang.NoClassDefFoundError: io.netty.channel.nio.NioEventLoopGroup 复制netty包 ...

  2. 漫谈Java IO之 Netty与NIO服务器

    前面介绍了基本的网络模型以及IO与NIO,那么有了NIO来开发非阻塞服务器,大家就满足了吗?有了技术支持,就回去追求效率,因此就产生了很多NIO的框架对NIO进行封装--这就是大名鼎鼎的Netty. ...

  3. Netty、NIO、多线程

    一:Netty.NIO.多线程? 时隔很久终于又更新了!之前一直迟迟未动也是因为积累不够,后面比较难下手.过年期间@李林锋hw发布了一个Netty5.0架构剖析和源码解读,看完也是收获不少.前面的文章 ...

  4. 2.Netty 与 NIO 之前世今生

      2.Netty 与 NIO 之前世今生 本文围绕一下几点阐述: 1. NIO 的核心组件 Buffer.Selector.Channel. 2.何谓多路复用? 3.Netty 支持的功能与特性. ...

  5. [netty4][netty-transport]netty之nio传输层

    [netty4][netty-transport]netty之nio传输层 nio基本处理逻辑 查看这里 Selector的处理 Selector实例构建 NioEventLoop.openSelec ...

  6. Netty(二)Netty 与 NIO 之前世今生

    2.1 Java NIO 三件套 在 NIO 中有几个核心对象需要掌握:缓冲区(Buffer).选择器(Selector).通道(Channel). 2.1.1 缓冲区 Buffer 1.Buffer ...

  7. netty简单NIO模型

    首先是使用java原生nio类库编写的例子,开发一套nio框架不简单,所以选择了netty,该例完成后,是netty举例. package com.smkj.netty; public class T ...

  8. 【Netty】NIO框架Netty入门

    Netty介绍 Netty是由JBOSS提供的一个java开源框架.Netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速开发高性能.高可靠性的网络服务器和客户端程序. 也就是说,Netty ...

  9. 基于Netty的NIO优化实践

    1. 浅谈React模型 2. Netty TCP 3. Netty UTP

随机推荐

  1. 笨方法学python笔记

    编程是什么 编程就是通过输出一种语言给计算机"听",命令其去执行相应的操作. 我们称我们给计算机下达的命令称为指令.一般说程序就是有多个指令构成的. 计算机需要使用非常多的电路来实 ...

  2. linux 上安装部署python

    一般在linux中使用python 需要安装pyenv 进行版本控制 因为linux6.9自带的Python是2.6的 同时很多命令都是基于2.6开发的 所以系统环境不能改 我们要开发 只能用pyen ...

  3. 36个JS特效教程,学完即精通

    6个JS特效教程,学完即精通   JavaScript特效教程,学完你就能写任何特效.本课程将JavaScript.BOM.DOM.jQuery和Ajax课程中的各种网页特效提取出了再进行汇总.内容涵 ...

  4. 傲视Kubernetes(三):Kubernetes中的Pod

    从本文开始,将正式开始Kubernetes的核心内容学习.首先要了解的是Pod,总共大约分为六篇左右,本篇是第一篇,相信学完之后,我们会对Pod有一个整体的理解. 本文内容: 1.什么是Pod 2.P ...

  5. redis 存储验证码 基本使用

    1 redis 存储验证码 基本使用 1.1 setting 配置 CACHES = { "default": { "BACKEND": "djang ...

  6. mysql 8.0 MGR组复制配置

    一.配置组复制的步骤 1.初始化数据目录 2.配置主服务器(primary) 3.配置辅助服务器(secondaries) 4.启动mysql实例 5.安装组复制插件(primary and seco ...

  7. ssl证书---验证等级分类

    DV SSL证书(domain Validation SSL): 指只验证网站域名所有权的简易型SSL证书,此类证书仅能起到网站机密信息加密的作用,无法向用户证明网站的真实身份.所以,不推荐在电子商务 ...

  8. Python----Flask Web框架(一)

    Flask是一个轻量级的基于Python的web框架. 本文适合有一定HTML.Python.网络基础的同学阅读. 1. 简介 这份文档中的代码使用 Python 3 运行.是的,所以读者需要自己在电 ...

  9. (一)、vim及gvim添加多行注释及删除多行注释块操作

    一.添加多行注释 选中要注释的行(连续的多行): Ctrl + v进入块选择模式: 按住I(大写)进入行首插入模式: 插入注释符: 按Esc回到命令模式. 或者 1.   进入命令行模式,按ctrl ...

  10. 小团队产品研发管理V0.0.1

    序言 之前做研发的时候非常鄙视管理,觉得管理的那些人就知道搞政治,后来做了开发主管,以及到部门经理之后,管的人多了发现管理真是门大学问,真的应该每个人都要学习一些基本管理知识,特别是刚入社会的打工人. ...