Netty 框架学习 —— 传输
概述
流经网络的数据总是具有相同的类型:字节,这些字节如何传输主要取决于我们所说的网络传输。用户并不关心传输的细节,只在乎字节是否被可靠地发送和接收
如果使用 Java 网络编程,你会发现,某些时候当你需要支持高并发连接,随后你尝试将阻塞传输切换为非阻塞传输,那么你会因为这两种 API 的截然不同而遇到问题。Netty 提供了一个通用的 API,这使得转换更加简单。
传统的传输方式
这里介绍仅使用 JDK API 来实现应用程序的阻塞(OIO)和非阻塞版本(NIO)
阻塞网络编程如下:
public class PlainOioServer {
public void server(int port) throws IOException {
// 将服务器绑定到指定端口
final ServerSocket socket = new ServerSocket(port);
try {
while (true) {
// 接收连接
final Socket clientSocket = socket.accept();
System.out.println("Accepted connection from " + clientSocket);
// 创建一个新的线程来处理连接
new Thread(() -> {
OutputStream out;
try {
out = clientSocket.getOutputStream();
// 将消息写给已连接的客户端
out.write("Hi\r\n".getBytes(StandardCharsets.UTF_8));
out.flush();
// 关闭连接x
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
这段代码可以处理中等数量的并发客户端,但随着并发连接的增多,你决定改用异步网络编程,但异步的 API 是完全不同的
非阻塞版本如下:
public class PlainNioServer {
public void server(int port) throws IOException {
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
ServerSocket ssocket = serverChannel.socket();
InetSocketAddress address = new InetSocketAddress(port);
// 将服务器绑定到选定的端口
ssocket.bind(address);
// 打开 Selector 来处理 Channel
Selector selector = Selector.open();
// 将 ServerSocket 注册到 Selector 以接受连接
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
final ByteBuffer msg = ByteBuffer.wrap("Hi\r\n".getBytes());
while (true) {
try {
// 等待需要处理的新事件,阻塞将一直持续到下一个传入事件
selector.select();
} catch (IOException e) {
e.printStackTrace();
break;
}
Set<SelectionKey> readKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = readKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
try {
// 检查事件是否是一个新的已经就绪可以被接受的连接
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
client.configureBlocking(false);
// 接受客户端,并将它注册到选择器
client.register(selector, SelectionKey.OP_WRITE | SelectionKey.OP_READ, msg.duplicate());
System.out.println("Accepted connection from " + client);
}
// 检查套接字是否已经准备好写数据
if (key.isWritable()) {
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = (ByteBuffer) key.attachment();
while (buffer.hasRemaining()) {
// 将数据写到已连接的客户端
if (client.write(buffer) == 0) {
break;
}
}
client.close();
}
} catch (IOException exception) {
key.cancel();
try {
key.channel().close();
} catch (IOException cex) {
cex.printStackTrace();
}
}
}
}
}
}
可以看到,阻塞和非阻塞的代码是截然不同的。如果为了实现非阻塞而完全重写程序,无疑十分困难
基于 Netty 的传输
使用 Netty 的阻塞网络处理如下:
public class NettyOioServer {
public void server(int port) throws Exception {
final ByteBuf buf = Unpooled.unreleasableBuffer(
Unpooled.copiedBuffer("Hi\n\r", StandardCharsets.UTF_8));
EventLoopGroup group = new OioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(group)
// 使用阻塞模式
.channel(OioServerSocketChannel.class)
.localAddress(new InetSocketAddress(port))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(
new SimpleChannelInboundHandler<>() {
@Override
protected void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception {
ctx.writeAndFlush(buf.duplicate())
.addListener(ChannelFutureListener.CLOSE);
}
});
}
});
ChannelFuture f = b.bind().sync();
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully().sync();
}
}
}
而非阻塞版本和阻塞版本几乎一模一样,只需要改动两处地方
EventLoopGroup group = new NioEventLoopGroup();
b.group(group).channel(NioServerSocketChannel.class);
传输 API
传输 API 的核心是 interface Channel,它被用于所有的 IO 操作。每个 Channel 都将被分配一个 ChannelPipeline 和 ChannelConfig,ChannelConfig 包含了该 Channel 的所有配置设置,ChannelPipeline 持有所有将应用于入站和出站数据以及事件的 ChannelHandler 实例
除了访问所分配的 ChannelPipeline 和 ChannelConfig 之外,也可以利用 Channel 的其他方法
| 方法名 | 描述 |
|---|---|
| eventLoop | 返回分配给 Channel 的 EventLoop |
| pipeline | 返回分配给 Channel 的 ChannelPipeline |
| isActive | 如果 Channel 活动的,返回 true |
| localAddress | 返回本地的 SocketAddress |
| remoteAddress | 返回远程的 SocketAddress |
| write | 将数据写到远程节点 |
| flush | 将之前已写的数据冲刷到底层传输 |
| writeAndFlush | 等同于调用 write() 并接着调用 flush() |
内置的传输
Netty 内置了一些可开箱即用的传输,但它们所支持的协议不尽相同,因此你必须选择一个和你的应用程序所使用协议相容的传输
| 名称 | 包 | 描述 |
|---|---|---|
| NIO | io.netty.channel.socket.nio | 使用 java.nio.channels 包作为基础 |
| Epoll | io.netty.channel.epoll | 由 JNI 驱动的 epoll() 和非阻塞 IO,可支持只有在 Linux 上可用的多种特性,比 NIO 传输更快,且完全非阻塞 |
| OIO | io.netty.channel.socket.oio | 使用 java.net 包作为基础 |
| Local | io.netty.channel.local | 可以在 VM 内部通过管道进行通信的本地传输 |
| Embedded | io.netty.channel.embedded | Embedded 传输,允许使用 ChannelHandler 而不需要一个真正的基于网络的传输,主要用于测试 |
Netty 框架学习 —— 传输的更多相关文章
- Netty 框架学习 —— 引导
概述 前面我们学习了 ChannelPipeline.ChannelHandler 和 EventLoop 之后,接下来的问题是:如何将它们组织起来,成为一个可实际运行的应用程序呢?答案是使用引导(B ...
- Netty 框架学习 —— 编解码器框架
编解码器 每个网络应用程序都必须定义如何解析在两个节点之间来回传输的原始字节,以及如何将其和目标应用程序的数据格式做相互转换.这种转换逻辑由编解码器处理,编解码器由编码器和解码器组成,它们每种都可以将 ...
- Netty 框架学习 —— 第一个 Netty 应用
概述 在本文,我们将编写一个基于 Netty 实现的客户端和服务端应用程序,相信通过学习该示例,一定能更全面的理解 Netty API 该图展示的是多个客户端同时连接到一台服务器.客户端建立一个连接后 ...
- Netty 框架学习 —— EventLoop 和线程模型
EventLoop 接口 Netty 是基于 Java NIO 的,因此 Channel 也有其生命周期,处理一个连接在其生命周期内发生的事件是所有网络框架的基本功能.通常来说,我们使用一个线程来处理 ...
- Netty 框架学习 —— 预置的 ChannelHandler 和编解码器
Netty 为许多提供了许多预置的编解码器和处理器,几乎可以开箱即用,减少了在烦琐事务上话费的时间和精力 空闲的连接和超时 检测空闲连接以及超时对于释放资源来说至关重要,Netty 特地为它提供了几个 ...
- Netty 框架学习 —— ByteBuf
概述 网络数据的基本单位总是字节,Java NIO 提供了 ByteBuffer 作为它的字节容器,但这个类的使用过于复杂.Netty 的 ByteBuf 具有卓越的功能性和灵活性,可以作为 Byte ...
- Netty 框架学习 —— 单元测试
EmbeddedChannel 概述 ChannelHandler 是 Netty 程序的关键元素,所以彻底地测试它们应该是你的开发过程中的一个标准部分,EmbeddedChannel 是 Netty ...
- Netty 框架学习 —— 添加 WebSocket 支持
WebSocket 简介 WebSocket 协议是完全重新设计的协议,旨在为 Web 上的双向数据传输问题提供一个切实可行的解决方案,使得客户端和服务器之间可以在任意时刻传输消息 Netty 对于 ...
- Netty 框架学习 —— UDP 广播
UDP 广播 面向连接的传输(如 TCP)管理两个网络端点之间的连接的建立,在连接的生命周期的有序和可靠的消息传输,以及最后,连接的有序终止.相比之下,类似 UDP 的无连接协议中则没有持久化连接的概 ...
随机推荐
- 利用DNS进行命令控制和搭建隧道
目录 利用DNS进行命令控制(DNS-Shell) 利用DNS搭建隧道 利用DNS进行命令控制(DNS-Shell) DNS-Shell是一款通过DNS信道实现交互式Shell的强大工具,该工具的服务 ...
- 【apache】使用HttpClient,进行简单网页抓取
1 package com.lw.httpclient.test; 2 import org.apache.http.client.methods.CloseableHttpResponse; 3 i ...
- Pytest自动化测试-简易入门教程(02)
Pytest框架简介 Pytest是一个非常成熟的全功能的Python测试框架,主要有以下几个特点:1.简单灵活,容易上手,支持参数化2.能够支持简单的单元测试和复杂的功能测试,3.还可以用来做sel ...
- 线程安全(ThreadSafety)
这节讲一下线程安全的例子,以及如何解决线程安全问题. 上节提到了线程安全的问题,说了一个例子,1000个人抢100张票,这节就从此案例着手,下面先看一下代码实现: private static int ...
- c++通讯录管理系统
代码拷贝 #include<iostream> #include<string> #include<stdlib.h> #define MAX 1000 using ...
- TLS是如何保障数据传输安全(中间人攻击)
前言 前段时间和同事讨论HTTPS的工作原理,当时对这块知识原理掌握还是靠以前看了一些博客介绍,深度不够,正好我这位同事是密码学专业毕业的,结合他密码学角度对tls加解密这阐述,让我对这块原理有了更进 ...
- [源码分析] 定时任务调度框架 Quartz 之 故障切换
[源码分析] 定时任务调度框架 Quartz 之 故障切换 目录 [源码分析] 定时任务调度框架 Quartz 之 故障切换 0x00 摘要 0x01 基础概念 1.1 分布式 1.1.1 功能方面 ...
- kubernetes客户端client-go使用
下载地址: https://github.com/kubernetes/client-go 官方使用文档参考:https://v1-16.docs.kubernetes.io/docs/referen ...
- [Python] 命名空间&作用域
Python的类语句不会创建实例 类会创建命名空间,通过对象访问类的属性和方法 类不会创建作用域,对方法和属性的引用必须加以限定(如在方法中必须通过self引用实例的属性) class My1(): ...
- Python菜鸟100例
题目地址 #-*- codeing = utf-8 -*- #@Time : 2021/3/18 21:17 #@Author : HUGBOY #@File : 1.py #@Software: P ...