Netty学习笔记(一) - 简介和组件设计
在互联网发达的今天,网络已经深入到生活的方方面面,一个高效、性能可靠的网络通信已经成为一个重要的诉求,在Java方面需要寻求一种高性能网络编程的实践。
一、简介
当前JDK(本文使用的JDK 1.8)中已经有网络编程相关的API,使用过程中或多或少会存在以下几个问题:
- 阻塞:早期JDK里的API是用阻塞式的实现方式,在读写数据调用时数据还没有准备好,或者目前不可写,操作就会被阻塞直到数据准备好或目标可写为止。虽然可以采用每一个连接创建一个线程进行处理,但是可能会造成大量线程得不到释放,消耗资源。从JDK 1.4开始提供非阻塞的实现。
- 处理和调度IO烦琐:偏底层的API实现暴露了更多的与业务无关的操作细节,使得在高负载下实现一个可靠和高效的逻辑就变得复杂和烦琐。
Netty是一款异步的事件驱动的网络应用程序框架,支持快速地开发可维护的高性能的面向协议的服务器和客户端。它拥有简单而强大的设计模型,易于使用,拥有比Java API更高的性能等特点,它屏蔽了底层实现的细节,使开发人员更关注业务逻辑的实现。
二、组件和设计
- Channel:屏蔽底层网络传输细节,提供简单易用的诸如bind、connect、read、write方法。
- EventLoop:线程模型。处理连接生命周期过程中发生的事件,以及其他一些任务。
- ChannelFuture:异步接口,用于注册Listener以便在某个操作完成时得到通知。
- ChannelHandler:处理入站和出站数据的的一系列接口和抽象类,开发人员扩展这些类型来完成业务逻辑。
- ChannelPipline:管理ChannelHandler的容器,将多个ChannelHandler以链式的方式管理,数据将在这个链上依次流动并被ChannelHandler逐个处理。
- 引导(Bootstrap、ServerBootstrap):初始化客户端和服务端的入口类。
三、一个简单的Demo
创建一个maven工程,引入Netty。为了方便调试,Demo中引入了日志和junit5。
<!-- pom.xml --> <dependencies>
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.50.Final</version>
</dependency> <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.30</version>
</dependency> <!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency> <!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-core -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
<version>1.2.3</version>
</dependency> <dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.5.2</version>
<scope>test</scope>
</dependency>
</dependencies>
创建Client和Server
package com.niklai.demo; import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.CharsetUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import java.net.InetSocketAddress; public class Client {
private static final Logger logger = LoggerFactory.getLogger(Client.class.getSimpleName()); public static void init() {
try {
Bootstrap bootstrap = new Bootstrap(); // 初始化客户端引导
NioEventLoopGroup group = new NioEventLoopGroup();
bootstrap.group(group) // 指定适用于NIO的EventLoop
.channel(NioSocketChannel.class) // 适用于NIO的Channel
.remoteAddress(new InetSocketAddress("localhost", 9999)) // 指定要绑定的IP和端口
.handler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new ClientHandler()); // 添加ChannelHandler到ChannelPipline
}
});
ChannelFuture future = bootstrap.connect().sync(); // 阻塞直到连接到远程节点
future.channel().closeFuture().sync(); // 阻塞直到关闭Channel
group.shutdownGracefully().sync(); // 释放资源
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
} static class ClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
logger.info("channel active...."); String msg = "Client message!";
logger.info("send message: {}....", msg);
ctx.writeAndFlush(Unpooled.copiedBuffer(msg, CharsetUtil.UTF_8));
} @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
logger.info("read message: {}....", buf.toString(CharsetUtil.UTF_8));
}
}
}
package com.niklai.demo; import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.CharsetUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import java.net.InetSocketAddress; public class Server {
private static final Logger logger = LoggerFactory.getLogger(Server.class.getSimpleName()); public static void init() {
try {
ServerBootstrap serverBootstrap = new ServerBootstrap(); // 初始化客户端引导
NioEventLoopGroup group = new NioEventLoopGroup();
serverBootstrap.group(group) // 指定适用于NIO的EventLoop
.channel(NioServerSocketChannel.class) // 适用于NIO的Channel
.localAddress(new InetSocketAddress("localhost", 9999)) // 指定要绑定的IP和端口
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new ServerHandler()); // 添加ChannelHandler到ChannelPipline
}
}); ChannelFuture future = serverBootstrap.bind().sync(); // 异步绑定阻塞直到完成
future.channel().closeFuture().sync(); // 阻塞直到关闭Channel
group.shutdownGracefully().sync(); // 释放资源
} catch (InterruptedException e) {
logger.error(e.getMessage(), e);
}
} static class ServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
logger.info("channel active.....");
} @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
logger.info("read message: {}.....", buf.toString(CharsetUtil.UTF_8));
} @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
logger.info("read complete.....");
ctx.writeAndFlush(Unpooled.copiedBuffer("receive message!", CharsetUtil.UTF_8))
.addListener(ChannelFutureListener.CLOSE);
}
}
}
日志配置文件
<?xml version="1.0" encoding="UTF-8"?> <configuration>
<!-- 定义控制台输出 -->
<appender name="consoleOut" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%date %level [%thread] %class#%method [%file:%line] %msg%n</pattern>
</encoder>
</appender> <root level="info">
<appender-ref ref="consoleOut" />
</root>
</configuration>
logback.xml
单元测试代码
package com.niklai.demo; import org.junit.jupiter.api.Test; public class NettyTest { @Test
public void test1() throws InterruptedException {
new Thread(() -> {
// 服务端
Server.init();
}).start();
Thread.sleep(1000); // 客户端
Client.init(); Thread.sleep(5000);
}
}
运行结果如下图
从控制台日志中可以看到当Client连接到Server后, ServerHandler 和 ClientHandler 的 channerActive 方法都会被调用, ClientHandler 会调用 ctx.writeAndFlush() 方法给Server发送一条消息, ServerHandler 的 channelRead 方法被调用读取到消息,消息读取完毕后 channelReadComplete 方法被调用,发送应答消息给Client, ClientHandler 的 channelRead 方法被调用获取到应答消息。到此一个完整的发送--应答流程就结束了。
Netty学习笔记(一) - 简介和组件设计的更多相关文章
- Netty学习笔记-入门版
目录 Netty学习笔记 前言 什么是Netty IO基础 概念说明 IO简单介绍 用户空间与内核空间 进程(Process) 线程(thread) 程序和进程 进程切换 进程阻塞 文件描述符 文件句 ...
- C#学习笔记——面向对象、面向组件以及类型基础
C#学习笔记——面向对象.面向组件以及类型基础 目录 一 面向对象与面向组件 二 基元类型与 new 操作 三 值类型与引用类型 四 类型转换 五 相等性与同一性 六 对象哈希码 一 面向对象与面向组 ...
- Linux内核学习笔记-1.简介和入门
原创文章,转载请注明:Linux内核学习笔记-1.简介和入门 By Lucio.Yang 部分内容来自:Linux Kernel Development(Third Edition),Robert L ...
- React学习笔记 - JSX简介
React Learn Note 2 React学习笔记(二) 标签(空格分隔): React JavaScript 一.JSX简介 像const element = <h1>Hello ...
- amazeui学习笔记--css(常用组件16)--文章页Article
amazeui学习笔记--css(常用组件16)--文章页Article 一.总结 1.基本使用:文章内容页的排版样式,包括标题.文章元信息.分隔线等样式. .am-article 文章内容容器 .a ...
- UML和模式应用学习笔记-1(面向对象分析和设计)
UML和模式应用学习笔记-1(面向对象分析和设计) 而只是对情节的记录:此处的用例场景为:游戏者请求掷骰子.系统展示结果:如果骰子的总点数是7,则游戏者赢得游戏,否则为输 (2)定义领域模型:在领域模 ...
- Netty学习笔记(二) 实现服务端和客户端
在Netty学习笔记(一) 实现DISCARD服务中,我们使用Netty和Python实现了简单的丢弃DISCARD服务,这篇,我们使用Netty实现服务端和客户端交互的需求. 前置工作 开发环境 J ...
- Netty 学习笔记(1)通信原理
前言 本文主要从 select 和 epoll 系统调用入手,来打开 Netty 的大门,从认识 Netty 的基础原理 —— I/O 多路复用模型开始. Netty 的通信原理 Netty 底层 ...
- amazeui学习笔记--css(常用组件15)--CSS动画Animation
amazeui学习笔记--css(常用组件15)--CSS动画Animation 一.总结 1.css3动画封装:CSS3 动画封装,浏览器需支持 CSS3 动画. Class 描述 .am-anim ...
- amazeui学习笔记--css(常用组件14)--缩略图Thumbnail
amazeui学习笔记--css(常用组件14)--缩略图Thumbnail 一.总结 1.基本样式:在 <img> 添加 .am-thumbnail 类:也可以在 <img> ...
随机推荐
- CtsSecurityTestCases#ListeningPortsTest定位tcp端口与pid
CtsSecurityTestCases#ListeningPortsTest定位tcp端口与pid [问题描述] cts失败项 armeabi-v7a CtsSecurityTestCases an ...
- Phoenix and Distribution(字典序贪心)
\(给定一串字母,分成k份,使得最大字典序最小.(字母可以任意组合)\) \(------------------------------issue~------------------------\ ...
- E - 梦幻岛宝珠 HYSBZ - 1190 变形01背包 难
E - 梦幻岛宝珠 HYSBZ - 1190 这个题目我觉得很难,看题解都看了很久. 首先可以得到一个大概的思路就是分组,每一个数都可以分成 a*2^b 所以把b相同的数都分成一个组. 在每一组内部 ...
- C#并发编程之初识并行编程
写在前面 之前微信公众号里有一位叫sara的朋友建议我写一下Parallel的相关内容,因为手中商城的重构工作量较大,一时之间无法抽出时间.近日,这套系统已有阶段性成果,所以准备写一下Parallel ...
- 面向开发者的Docker实践
show me the code and talk to me,做的出来更要说的明白 本文源码,请点击learnSpringboot 我是布尔bl,你的支持是我分享的动力! 一. 引入 有开发经验的都 ...
- 《C程序设计语言》 练习2-4
问题描述 重新编写函数squeeze(s1,s2),将字符串s1中任何与字符串s2中字符匹配的字符都删除. Write an alternate version of squeeze(s1,s2) t ...
- 《Vue.js 2.x实践指南》 已出版
<Vue.js 2.x实践指南>其实在一年前就已经完稿了,只是由于疫情的缘故耽搁了很久才下厂印刷.阅读本书需要具备HTML.CSS和JS基础,本书针对的用户群体主要是:想要快速学习vue技 ...
- Excel+Word:Jupyter
直接打开Excel,可以增改删,但如果只是查了?Jupyter Lab/Jupyter Notebook是件利器. 工作内容之一,是复制Excel的一条记录,姓名.身份证号.银行卡号,粘贴在Word的 ...
- 5、打断点(bpu)
前言 先给大家讲一则小故事,在我们很小的时候是没有手机的,那时候跟女神聊天都靠小纸条.某屌丝A男对隔壁小王的隔壁女神C倾慕已久,于是天天小纸条骚扰,无奈中间隔着一个小王,这样小王就负责传小纸条了.有一 ...
- linux下安装gmp遇到 configure:error:no usable m4 in$path or /user/5bin解决方案
安装过程中遇到如下报错: 上面的报错是因为你没有安装m4,安装m4就可以了:以下两种命令人选其一: #yum install m4 或 #apt-get install m4 ps:如果遇到权限问题就 ...