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 的无连接协议中则没有持久化连接的概 ...
随机推荐
- mongoDB常用
登陆{ 本地的话直接mongo,如果是docker直接就这样docker exec -it 2d71a13e3128 mongo 或者直接这样 mongo 127.0.0.1:27017 } 退出是 ...
- [CTF]URL编码
[CTF]URL编码 --------------------- 作者:adversity` 来源:CSDN 原文:https://blog.csdn.net/qq_40836553/artic ...
- 制作一个简单的toast弹框
toast弹框的作用 toast弹框顾名思义,就是为了弹出一个提示框,效果如图: 使用toast弹框可以可用户带来更好的交互体验 toast弹框的使用 Toast组件 制做出toast的样式以及出现的 ...
- SpringBoot简单尝试
一.spring boot核心 配置在类路径下autoconfigure下(多瞅瞅) @SpringBootApplication里的重要注解(@Configuration,@EnableAutoCo ...
- ES6学习-0 前言
本菜鸟做了二十来年的程序开发了,前后台都写过,队伍也带过.大约是2000年左右,是用dephi 写后台CGI,所有的html,js,css基本都是混在CGI里输出到前台的,那时也没有明确的前后台的概念 ...
- 联想RD350板载RAID110i,安装CentOS 7 不识别RAID设备
联想RD350板载RAID110i,安装CentOS 7 不识别RAID设备 情况如题所述. 1. 确认BIOS中 Boot mode为[UEFI]或者[AUTO] 2. 确认BIOS中 Stor ...
- Sed常用功能个人整理
Sed常用功能个人整理 AsdilFibrizo关注 2019.06.24 10:23:41字数 240阅读 15 Sed对1G以下的数据效率很高这里介绍一些个人在工作中遇到的sed问题 1.查找字段 ...
- Ansible_编写Playbook文件
一.Playbook的实施 1.Ansible playbook与临时命令概述: 临时命令可以作为一次性命令对一组目标主机运行一项简单的任务 play是针对清单中选定的主机运行的一组有序任务.play ...
- nohup 命令 2>&1 |tee lmbench.log & 只适用没有需要敲y或x的
nohup make results 2>&1 |tee lmbench.log & nohup 命令 2>&1 |tee lmbench.log & 只适 ...
- exec函数族实例解析-(转自blankqdb)
fork()函数通过系统调用创建一个与原来进程(父进程)几乎完全相同的进程(子进程是父进程的副本,它将获得父进程数据空间.堆.栈等资源的副本.注意,子进程持有的是上述存储空间的"副本&quo ...