Netty
作者:互联网
Netty
Netty介绍和应用场景
Netty的介绍
- Netty 是由 JBOSS 提供的一个 Java 开源框架,现为 Github上的独立项目。
- Netty 是一个异步的、基于事件驱动的网络应用框架,用以快速开发高性能、高可 靠性的网络 IO 程序
- Netty主要针对在TCP协议下,面向Clients端的高并发应用,或者Peer-to-Peer(P2P)场景下 的大量数据持续传输的应用。
- Netty本质是一个NIO框架,适用于服务器通讯相关的多种应用场景
- 要透彻理解Netty , 需要先学习 NIO , 这样我们才能阅读 Netty 的源码
Netty的应用场景
-
互联网行业
- 互联网行业:在分布式系统中,各个 节点之间需要远程服务调用,高性能 的 RPC 框架必不可少,Netty 作为异步 高性能的通信框架,往往作为基础通 信组件被这些 RPC 框架使用
- 典型的应用有:阿里分布式服务框架 Dubbo 的 RPC 框架使用 Dubbo 协议进 行节点间通信,Dubbo 协议默认使用 Netty 作为基础通信组件,用于实现各 进程节点之间的内部通信
-
游戏行业
- 无论是手游服务端还是大型的网络游戏, Java 语言得到了越来越广泛的应用
- Netty 作为高性能的基础通信组件,提 供了 TCP/UDP 和 HTTP 协议栈,方便定制和开发私有协议栈,账号登录服务器
- 地图服务器之间可以方便的通过 Netty 进行高性能的通信
-
大数据领域
- 经典的 Hadoop 的高性能通信和 序列化组件 Avro 的 RPC 框架, 默认采用 Netty 进行跨界点通信
- 它的 Netty Service 基于 Netty 框 架二次封装实现。
-
其它开源项目使用到Netty
- 网址:
https://netty.io/wiki/related-projects.html
- 网址:
Netty的学习参考资料
- Netty In Action
- Netty 权威指南
Java BIO编程
I/O模型
I/O 模型基本说明
- I/O 模型简单的理解:就是用什么样的通道进行数据的发送和接收,很大程度上决定了程序通信的性能
- Java共支持3种网络编程模型/IO模式:BIO、NIO、AIO
- Java BIO : 同步并阻塞(传统阻塞型),服务器实现模式为一个连接一个线程,即客户端 有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成 不必要的线程开销
- Java NIO : 同步非阻塞,服务器实现模式为一个线程处理多个请求(连接),即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求就进行处理
- Java AIO(NIO.2) : 异步非阻塞,AIO 引入异步通道的概念,采用了 Proactor 模式,简化了程序编写,有效的请求才启动线程,它的特点是先由操作系统完成后才通知服务端程
BIO、NIO、AIO适用场景分析
- BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高, 并发局限于应用中,JDK1.4以前的唯一选择,但程序简单易理解。
- NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,弹幕 系统,服务器间通讯等。编程比较复杂,JDK1.4开始支持。
- AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分 调用OS参与并发操作,编程比较复杂,JDK7开始支持。
Java BIO 基本介绍
- Java BIO 就是传统的java io 编程,其相关的类和接口在 java.io
- BIO(blocking I/O) : 同步阻塞,服务器实现模式为一个连接一个线程,即客户端有连 接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造 成不必要的线程开销,可以通过线程池机制改善(实现多个客户连接服务器)。
- BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高, 并发局限于应用中,JDK1.4以前的唯一选择,程序简单易理解
Java BIO 工作机制
-
工作原理图
graph TD; Scoket_1 --- read/write_1 --- Thread_1 Scoket_2 --- read/write_2 --- Thread_2 Scoket_3 --- read/write_3 --- Thread_3 -
BIO编程简单流程
- 服务器端启动一个ServerSocket
- 客户端启动Socket对服务器进行通信,默认情况下服务器端需要对每个客户 建立一个线程与之通讯
- 客户端发出请求后, 先咨询服务器 是否有线程响应,如果没有则会等待,或者被拒绝
- 如果有响应,客户端线程会等待请求结束后,在继续执行
应用程序比如浏览器、电子邮件、文件传输服务器等产生的数据,会通过传输层协议进行传输。而应用程序是不会和传输层直接建立联系的,而是有一个能够连接应用层和传输层之间的套件,这个套件就是 Socket
Java BIO 应用实例
- 实例说明
- 使用BIO模型编写一个服务器端,监听6666端口,当有客户端连接时,就启动一个线程与之通讯
- 要求使用线程池机制改善,可以连接多个客户端
- 服务器端可以接收客户端发送的数据(telnet 方式即)
1.什么是Telnet?
对于Telnet的认识,不同的人持有不同的观点,可以把Telnet当成一种通信协议,但是对于入侵者而言,Telnet只是一种远程登录的工具。一旦入侵者与远程主机建立了Telnet连接,入侵者便可以使用目标主机上的软、硬件资源,而入侵者的本地机只相当于一个只有键盘和显示器的终端而已。
2.为什么需要telnet?
telnet就是查看某个端口是否可访问。我们在搞开发的时候,经常要用的端口就是 8080。那么你可以启动服务器,用telnet 去查看这个端口是否可用。
Telnet协议是TCP/IP协议家族中的一员,是Internet远程登陆服务的标准协议和主要方式。它为用户提供了在本地计算机上完成远程主机工作的能力。在终端使用者的电脑上使用telnet程序,用它连接到服务器。终端使用者可以在telnet程序中输入命令,这些命令会在服务器上运行,就像直接在服务器的控制台上输入一样。可以在本地就能控制服务器。要开始一个telnet会话,必须输入用户名和密码来登录服务器。Telnet是常用的远程控制Web服务器的方法。
3.Windows XP怎么执行telnet 命令?
1)、点击开始 → 运行 → 输入CMD,回车。
2)、在出来的DOS界面里,输入telnet测试端口命令: telnet IP 端口 或者 telnet 域名 端口,回车。
如果端口关闭或者无法连接,则显示不能打开到主机的链接,链接失败;端口打开的情况下,链接成功,则进入telnet页面(全黑的),证明端口可用。
Telnet 客户端命常用命令:
open : 使用 openhostname 可以建立到主机的 Telnet 连接。
close : 使用命令 close 命令可以关闭现有的 Telnet 连接。
display : 使用 display 命令可以查看 Telnet 客户端的当前设置。
send : 使用 send 命令可以向 Telnet 服务器发送命令。支持以下命令:
ao : 放弃输出命令。
ayt : “Are you there”命令。
esc : 发送当前的转义字符。
ip : 中断进程命令。
synch : 执行 Telnet 同步操作。
brk : 发送信号。
上表所列命令以外的其他命令都将以字符串的形式发送至 Telnet 服务器。例如,sendabcd 将发送字符串 abcd 至 Telnet 服务器,这样,Telnet 会话窗口中将出现该字符串。
quit :使用 quit 命令可以退出 Telnet 客户端。
windows10 中telnet默认是关闭的,需要打开才能使用
控制面板 -> 程序 -> 启动或关闭Windows功能 -> 勾选 Telnet客户端 后点击确定
如果 Telnet客户端 依旧无法使用或无法建立多个连接,则需重启计算机
使用步骤
1. window+r —> cmd —> telnet回车 —> 输入quit退出telnet模式
2. telnet IP PORT -> 连接成功后会跳到Tenlet控制页面(全黑,啥也看不到),输入crlt+] 进入命令模式 -> send hello world -> 退出Telnet控制台,quit指令
/**
* 线程工厂
*
* @author
* @since
*/
public class ThreadFactoryBuild {
private final String NUM = "%d";
private final String STR = "%s";
/**
* %d 数字
* %s 字符串
*/
private String nameType = STR;
private String name;
private final ThreadFactory nameThreadFactory;
public ThreadFactoryBuild() {
nameThreadFactory = r -> {
if (STR.equals(nameType)) {
name = UUID.randomUUID().toString().replace("-", "");
} else {
name = String.valueOf(System.currentTimeMillis());
}
return new Thread(r, name);
};
}
public ThreadFactoryBuild setName(String nameType) {
this.nameType = nameType;
return this;
}
public ThreadFactory build() {
return this.nameThreadFactory;
}
}
/**
* BIO 服务器
*
* @author
* @since
*/
public class BioService {
/**
* 自定义线程名称,方便出错的时候朔源
*/
private static final ThreadFactory NAME_THREAD_FACTORY = new ThreadFactoryBuild().setName("%s").build();
/**
* 编码集
*/
private static final Charset GBK = Charset.forName("GBK");
/**
* 引导程序
*
* @param port 端口号
*/
public static void bootstrap(int port) throws IOException {
// 思路
// 1. 创建一个线程池
// 2. 如果有客户端链接就创建一个线程与之通讯(单独写一个方法)
// 获取CPU核数
int processors = Runtime.getRuntime().availableProcessors();
System.out.println("CPU cores: " + processors);
// 线程池机制
ThreadPoolExecutor pool = new ThreadPoolExecutor(processors, processors, 0L, TimeUnit.MICROSECONDS, new LinkedBlockingQueue<>(), NAME_THREAD_FACTORY);
// 创建ServerSocket
ServerSocket serverSocket = new ServerSocket(port);
System.out.println("服务器启动了");
while (true) {
System.out.println("线程信息 id = " + Thread.currentThread().getId() + " 名称 = " + Thread.currentThread().getName());
// 监听客户端链接
System.out.println("等待连接...");
final Socket socket = serverSocket.accept();
System.out.println("连接到一个客户端");
// 创建一个线程与之通讯(单独写一个方法)
pool.execute(() -> {
handler(socket);
});
}
}
/**
* 与客户端通讯处理
*
* @param socket 套接字
*/
private static void handler(Socket socket) {
long id = Thread.currentThread().getId();
try {
System.out.println("线程信息 id = " + id + " 名称 = " + Thread.currentThread().getName());
byte[] buff = new byte[1024];
// 通过socket获取输入流
InputStream inputStream = socket.getInputStream();
// 循环读取客户端发送数据
while (true) {
System.out.println("id = " + id + "=>read...");
int read = inputStream.read(buff);
if (read != -1) {
// 输出客户端发送数据
System.out.println("id = " + id + "=>" + new String(buff, 0, read, GBK));
} else {
break;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
System.out.println("id = " + id + "=>关闭与client的连接");
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* BIO 启动类
*
* @author
* @since
*/
public class BioStudy {
public static void main(String[] args) throws IOException {
BioService.bootstrap(6666);
}
}
Java BIO 问题分析
- 每个请求都需要创建独立的线程,与对应的客户端进行数据 Read,业务处理,数据 Write 。
- 当并发数较大时,需要创建大量线程来处理连接,系统资源占 用较大。
- 连接建立后,如果当前线程暂时没有数据可读,则线程就阻塞 在 Read 操作上,造成线程资源浪费
Java NIO编程
Java NIO 基本介绍
- Java NIO 全称 java non-blocking IO,是指 JDK 提供的新 API。从 JDK1.4 开始,Java 提供了一系列改进的输入/输出 的新特性,被统称为 NIO(即 New IO),是同步非阻塞
- NIO 相关类都被放在 java.nio 包及子包下,并且对原 java.io 包中的很多类进行改写。
- NIO 有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(选择器)
- NIO是 面向缓冲区 ,或者面向 块 编程的。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动,这就 增加了处理过程中的灵活性,使用它可以提供非阻塞式的高伸缩性网络
- Java NIO的非阻塞模式,使一个线程从某通道发送请求或者读取数据,但是它仅能得 到目前可用的数据,如果目前没有数据可用时,就什么都不会获取,而不是保持线 程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞 写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情
- 通俗理解:NIO是可以做到用一个线程来处理多个操作的。假设有10000个请求过来, 根据实际情况,可以分配50或者100个线程来处理。不像之前的阻塞IO那样,非得分配10000个
- HTTP2.0使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求的数量比HTTP1.1大了好几个数量级
案例说明NIO Buff
public class BasicBuffer {
public static void main(String[] args) {
// 举例说明Buffer的使用(简单说明)
// 创建一个Buffer, 大小为5,即可以存放5个int
IntBuffer buffer = IntBuffer.allocate(5);
// 向buffer存放数据 <code>buffer.put(5) | buffer.put(2) | buffer.put(0)</code>
for (int i = 0; i < buffer.capacity(); i++) {
buffer.put(i * 2);
}
// 如何从buffer读取数据
// 将 buffer 转换, 读写切换(!!!)
buffer.flip();
while (buffer.hasRemaining()) {
System.out.println(buffer.get());
}
}
}
NIO 和 BIO 的比较
- BIO 以流的方式处理数据,而 NIO 以块的方式处理数据,块 I/O 的效率比流 I/O 高很多
- BIO 是阻塞的,NIO 则是非阻塞的
- BIO基于字节流和字符流进行操作,而 NIO 基于 Channel(通道)和 Buffer(缓冲区)进 行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。 Selector(选择器)用于监听多个通道的事件(比如:连接请求,数据到达等),因此使用单个线程就可以监听多个客户端通道
NIO 三大核心原理示意
- Selector 、Channel 和 Buffer 的关系图(简单版)
- 关系图的说明
- 每个channel 都会对应一个Buffer
- Selector 对应一个线程, 一个线程对应多个channel(连接)
- 该图反应了有三个channel 注册到 该selector
- 程序切换到哪个channel 是有事件决定的, Event 就是一个重要的概念
- Selector 会根据不同的事件,在各个通道上切换
- Buffer 就是一个内存块 , 底层是有一个数组
- 数据的读取写入是通过Buffer, 这个和BIO不同 , BIO 中要么是输入流,或者是 输出流, 不能双向,但是NIO的Buffer 是可以读也可以写, 需要 flip 方法切换
- channel 是双向的, 可以返回底层操作系统的情况, 比如Linux ,底层的操作系统 通道就是双向的
缓冲区(Buffer)
基本介绍
缓冲区(Buffer):缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个 容器对象(含数组),该对象提供了一组方法,可以更轻松地使用内存块,,缓冲区对 象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。Channel 提供从文件、 网络读取数据的渠道,但是读取或写入的数据都必须经由 Buffer
flowchart LR; NIO程序 <-->|data| 缓冲区 <-->|Channel| 文件[/文件/]标签:Netty,NIO,BIO,Telnet,线程,客户端 来源: https://www.cnblogs.com/Zzzyyw/p/16182198.html