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> ...
随机推荐
- Java——Java中编码问题
在开发过程中经常会遇到一会乱码问题,不是什么大问题,但是也挺烦人的,今天来将我们开发总结的经验记录下来,希望可以给大家一些帮助. 一些概念: 字符:人们使用的记号,抽象意义上的一个符号.比如:‘1’, ...
- GoF23:工厂模式(Factory)
目录 GoF23:工厂模式(Factory) 工厂模式三种模式 简单工厂模式(静态工厂模式) 工厂方法模式 抽象工厂模式 举例说明 代码实现 GoF23:工厂模式(Factory) 核心本质: 实例化 ...
- keepalived高可用服务配置管理
实验环境: 主机 ipaddress 服务 备注 k8s-master1 10.0.0.63 nginx k8s-master2 10.0.0.64 nginx k8s-node1 10.0.0.65 ...
- Spring源码阅读 之 配置的读取,解析
在上文中我们已经知道了Spring如何从我们给定的位置加载到配置文件,并将文件包装成一个Resource对象.这篇文章我们将要探讨的就是,如何从这个Resouce对象中加载到我们的容器?加载到容器后又 ...
- 201771010113 李婷华 《面向java对象程序设计(Java)》第四章学习总结
一. 理论知识部分 第四章 对象与类 本章主要讲述面向对象程序设计.如何创建标准Java类库中的类对象.如何编写自己的类. 1.面向对象程序设计的几个主要概念: 抽象数据类型.类和对象.封装.类层次( ...
- 【Hadoop离线基础总结】伪分布模式环境搭建
伪分布模式环境搭建 服务规划 适用于学习测试开发集群模式 步骤 第一步:停止单节点集群,删除/export/servers/hadoop-2.7.5/hadoopDatas,重新创建文件夹 停止单节点 ...
- FOC 算法基础之欧拉公式
文章目录 欧拉公式 几何意义 复数平面 动态过程 加法 FOC电压矢量的推导 总结 参考 FOC中电压矢量合成的推导,对于欧拉公式的几何意义做了一个全面的回顾. 欧拉公式 欧拉是一个天才,欧拉公式甚至 ...
- 部署SSL站点 IIS+asp.net
使用SSL必须要有证书,今天我们就使用IIS内置的证书完成SSL的部署. 1.打开MMC证书管理器,文件->添加/删除管理单元->证书,双击->确定 2.找到:个人->证书下有 ...
- 一、线程 & 线程池
一.线程的介绍 1.1.概念 进程: 你的硬盘上有一个简单的程序,这个程序叫QQ.exe,这是一个程序,这个程序是一个静态的概念,它被扔在硬盘上也没人理他,但是当你双击它,弹出一个界面输入账号密码登录 ...
- 我的linux学习日记day5
一.vim 编辑器 有三种模式,命令模式,输入模式,末行模式 1.下面是命令模式常用的命令 2.末行模式常用命令 :w 保存 :q 退出 :q! 强制退出 :wq! 强制保存退出 :set nu 显示 ...