Netty简单介绍(非原创)
文章大纲
一、Netty基础介绍
二、Netty代码实战
三、项目源码下载
四、参考文章

一、Netty基础介绍
1. 简介
官方定义为:”Netty 是一款异步的事件驱动的网络应用程序框架,支持快速地开发可维护的高性能的面向协议的服务器和客户端”
2. 主要特性
Netty有很多重要的特性,主要特性如下:
(1)优雅的设计
(2)统一的API接口,支持多种传输类型,例如OIO,NIO
(3)简单而强大的线程模型
(4)丰富的文档
(5)卓越的性能
(6)拥有比原生Java API 更高的性能与更低的延迟
(7)基于池化和复用技术,使资源消耗更低
(8)安全性
(9)完整的SSL/TLS以及StartTLS支持
(10)可用于受限环境,如Applet以及OSGI
Netty的以上特性,比较适合客户端数据较大的请求/处理场景,例如web服务器等,要想知道有哪些系统使用了Netty,可以参考:http://netty.io/wiki/adopters.html
3. 主要名词介绍
3.1 BIO(Blocking IO):阻塞IO
早期的Java API(java.net)提供了由本地系统套接字库提供的所谓的阻塞函数,样例代码如下:
ServerSocket serverSocket = new ServerSocket(portNumber);
Socket clientSocket = serverSocket.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out =new PrintWriter(clientSocket.getOutputStream(), true);
String request, response;
while ((request = in.readLine()) != null) {
if ("Done".equals(request)) {
break;
}
response = processRequest(request);
out.println(response);
}
这段代码片段将只能同时处理一个连接,要管理多个并发客户端,需要为每个新的客户端 Socket 创建一个新的 Thread,线程模型如下图所示:

该种模型存在以下两个问题:
(1)在任何时候都可能有大量的线程处于休眠状态,只是等待输入或者输出数据就绪,这可能算是一种资源浪费
(2)需要为每个线程的调用栈都分配内存
(3)即使 Java 虚拟机(JVM) 在物理上可以支持非常大数量的线程, 但是远在到达该极限之前, 上下文切换所带来的开销就会带来麻烦
3.2 NIO(Non Blocking IO):非阻塞IO
Java的NIO特性在JDK 1.4中引入,其结构如下:

从该图可以看出Selector 是Java 的非阻塞 I/O 实现的关键。它使用了事件通知 API以确定在一组非阻塞套接字中有哪些已经就绪能够进行 I/O 相关的操作。因为可以在任何的时间检查任意的读操作或者写操作的完成状态。该种模型下,一个单一的线程便可以处理多个并发的连接。 与BIO相比,该模型有以下特点:
(1)使用较少的线程便可以处理许多连接,因此也减少了内存管理和上下文切换所带来开销
(2)当没有 I/O 操作需要处理的时候,线程也可以被用于其他任务
虽然Java 的NIO在性能上比BIO已经相当的优秀,但是要做到如此正确和安全并
不容易。特别是,在高负载下可靠和高效地处理和调度 I/O 操作是一项繁琐而且容易出错的任务,此时就是Netty上场的时间了
4. Netty好处
(1)使用多路复用技术,提高处理连接的并发性
(2)零拷贝:
a. Netty的接收和发送数据采用DIRECT BUFFERS,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝
b. Netty提供了组合Buffer对象,可以聚合多个ByteBuffer对象进行一次操作
c. Netty的文件传输采用了transferTo方法,它可以直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环write方式导致的内存拷贝问题
d. 内存池:为了减少堆外直接内存的分配和回收产生的资源损耗问题,Netty提供了基于内存池的缓冲区重用机制
e. 使用主从Reactor多线程模型,提高并发性
f. 采用了串行无锁化设计,在IO线程内部进行串行操作,避免多线程竞争导致的性能下降
g. 默认使用Protobuf的序列化框架
h. 灵活的TCP参数配置
详细说明,可参考: http://www.infoq.com/cn/articles/netty-high-performance#anch111813
二、Netty代码实战
在本节中,我们将前面讲解NIO编程时的时间服务案例,改成用Netty来实现。TimeClient发送“QUERY TIME ORDER”请求,TimeServer接受到这个请求后,返回当前时间。
1. 新建maven项目




创建后项目结构如下:

2. pom.xml文件添加依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.wxc</groupId>
<artifactId>netty-test</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.0.28.Final</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
3. 编写服务端代码
3.1 创建TimeServer.java
时间服务器TimeServer在8080端口监听客户端请求,如下
package server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
public class TimeServer {
private int port=8080;
public void run() throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap(); // (2)
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class) // (3)
.childHandler(new ChannelInitializer<SocketChannel>() { // (4)
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new TimeServerHandler());
}
});
ChannelFuture f = b.bind(port).sync(); // (5)
System.out.println("TimeServer Started on 8080...");
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new TimeServer().run();
}
}
说明:
1、首先我们创建了两个EventLoopGroup实例:bossGroup和workerGroup,目前可以将bossGroup和workerGroup理解为两个线程池。其中bossGroup用于接受客户端连接,bossGroup在接受到客户端连接之后,将连接交给workerGroup来进行处理。
2、接着,我们创建了一个ServerBootstrap实例,从名字上就可以看出来这是一个服务端启动类,我们需要给设置一些参数,包括第1步创建的bossGroup和workerGroup。
3、我们通过channel方法指定了NioServerSocketChannel,这是netty中表示服务端的类,用于接受客户端连接,对应于java.nio包中的ServerSocketChannel。
4、我们通过childHandler方法,设置了一个匿名内部类ChannelInitializer实例,用于初始化客户端连接SocketChannel实例。在第3步中,我们提到NioServerSocketChannel是用于接受客户端连接,在接收到客户端连接之后,netty会回调ChannelInitializer的initChannel方法需要对这个连接进行一些初始化工作,主要是告诉netty之后如何处理和响应这个客户端的请求。在这里,主要是添加了3个ChannelHandler实例:LineBasedFrameDecoder、StringDecoder、TimeServerHandler。其中LineBasedFrameDecoder、StringDecoder是netty本身提供的,用于解决TCP粘包、解包的工具类。
LineBasedFrameDecoder在解析客户端请求时,遇到字符”\n”或”\r\n”时则认为是一个完整的请求报文,然后将这个请求报文的二进制字节流交给StringDecoder处理。
StringDecoder将字节流转换成一个字符串,交给TimeServerHandler来进行处理。
TimeServerHandler是我们自己要编写的类,在这个类中,我们要根据用户请求返回当前时间。
5、在所有的配置都设置好之后,我们调用了ServerBootstrap的bind(port)方法,开启真正的监听在8080端口,接受客户端请求。
3.2 创建TimeServerHandler.java
TimeServerHandler用户处理客户端的请求,每当接收到"QUERY TIME ORDER”请求时,就返回当前时间,否则返回"BAD REQUEST”。
package server;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.util.Date;
public class TimeServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { // 1
String request = (String) msg; //2
String response = null;
if ("QUERY TIME ORDER".equals(request)) { // 3
response = new Date(System.currentTimeMillis()).toString();
} else {
response = "BAD REQUEST";
}
response = response + System.getProperty("line.separator"); // 4
ByteBuf resp = Unpooled.copiedBuffer(response.getBytes()); // 5
ctx.writeAndFlush(resp); // 6
}
}
说明:
1、TimeServerHandler继承了ChannelInboundHandlerAdapter,并覆盖了channelRead方法,当客户端发送了请求之后,channelRead方法会被回调。参数ChannelHandlerContext包含了当前发送请求的客户端的一些上下文信息,msg表示客户端发送的请求信息。
2、我们直接msg强制转换成了String类型。这是因为我们在前面已经添加过了StringDecoder,其已经将二进制流转换成了一个字符串
3、构建响应。会判断请求是否合法,如果请求信息是"QUERY TIME ORDER”,则返回当前时间,否则返回"BAD REQUEST”
4、在响应内容中加上了System.getProperty("line.separator”),也就是所谓的换行符。在linux操作系统中,就是”\n”,在windows操作系统是”\r\n”。加上换行符,主要是因为客户端也要对服务端的响应进行解码,当遇到一个换行符时,就认为是一个完整的响应。
5、调用了Unpooled.copiedBuffer方法创建了一个缓冲区对象ByteBuf。在java nio包中,使用ByteBuffer类来表示一个缓冲区对象。在netty中,使用ByteBuf表示一个缓冲区对象。在后面的章节中,我们会对ByteBuf进行详细讲解。
6、调用ChannelHandlerContext的writeAndFlush方法,将响应刷新到客户端
4. 编写客户端代码
4.1 创建TimeClient .java
TimeClient负责与服务端的8080端口建立连接
public class TimeClient {
public static void main(String[] args) throws Exception {
String host = "localhost";
int port = 8080;
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap(); // (1)
b.group(workerGroup); // (2)
b.channel(NioSocketChannel.class); // (3)
b.handler(new ChannelInitializer<SocketChannel>() {// (4)
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
ch.pipeline().addLast(new StringDecoder());
ch.pipeline().addLast(new TimeClientHandler());
}
});
// Start the client.
ChannelFuture f = b.connect(host, port).sync(); // (5)
// Wait until the connection is closed.
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
}
}
}
说明:
1、首先我们创建了一个Bootstrap实例,与ServerBootstrap相对应,这表示一个客户端的启动类
2、我们调用group方法给Bootstrap实例设置了一个EventLoopGroup实例。前面提到,EventLoopGroup的作用是线程池。前面在创建ServerBootstrap时,设置了一个bossGroup,一个wrokerGroup,这样做主要是为将接受连接和处理连接请求任务划分开,以提升效率。对于客户端而言,则没有这种需求,只需要设置一个EventLoopGroup实例即可。
3、通过channel方法指定了NioSocketChannel,这是netty在nio编程中用于表示客户端的对象实例。
4、类似server端,在连接创建完成,初始化的时候,我们也给SocketChannel添加了几个处理器类。其中TimeClientHandler是我们自己编写的给服务端发送请求,并接受服务端响应的处理器类。
5、所有参数设置完成之后,调用Bootstrap的connect(host, port)方法,与服务端建立连接。
4.2 创建TimeClientHandler.java
TimeClientHandler主要用于给Server端发送"QUERY TIME ORDER”请求,并接受服务端响应。
public class TimeClientHandler extends ChannelInboundHandlerAdapter {
private byte[] req=("QUERY TIME ORDER" + System.getProperty("line.separator")).getBytes();
@Override
public void channelActive(ChannelHandlerContext ctx) {//1
ByteBuf message = Unpooled.buffer(req.length);
message.writeBytes(req);
ctx.writeAndFlush(message);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String body = (String) msg;
System.out.println("Now is:" + body);
}
}
说明:
1、TimeClientHandler继承了ChannelInboundHandlerAdapter,并同时覆盖了channelActive和channelRead方法。
2、当客户端与服务端连接建立成功后,channelActive方法会被回调,我们在这个方法中给服务端发送"QUERY TIME ORDER”请求。
3、当接受到服务端响应后,channelRead方法会被会回调,我们在这个方法中打印出响应的时间信息。
创建后项目结构如下:

5. 项目运行与访问
5.1 运行服务端


5.2 运行客户端


三、项目源码下载
链接:https://pan.baidu.com/s/1v7QuR0ycWSCkDpaAd-tZhQ
提取码:0nyf
四、参考文章
Netty简单介绍(非原创)的更多相关文章
- Java之Spring Cloud概念介绍(非原创)
文章大纲 一.理解微服务二.Spring Cloud知识介绍三.Spring Cloud全家桶四.参考资料下载五.参考文章 一.理解微服务 我们通过软件架构演进过程来理解什么是微服务,软件架构的发 ...
- FastDFS介绍(非原创)
文章大纲 一.FastDFS介绍二.FastDFS安装与启动(Linux系统)三.Java客户端上传图片四.参考文章 一.FastDFS介绍 1. 什么是FastDFS FastDFS是用C语言编 ...
- TCP同步与异步及阻塞模式,多线程+阻塞模式,非阻塞模式简单介绍
首先我简单介绍一下同步TCP编程 与异步TCP编程. 在服务端我们通常用一个TcpListener来监听一个IP和端口.客户端来一个请求的连接,在服务端可以用同步的方式来接收,也可以用异步的方式去接收 ...
- 服务器端IO模型的简单介绍及实现 阻塞 / 非阻塞 VS 同步 / 异步 内核实现的拷贝效率
小结: 1.在多线程的基础上,可以考虑使用"线程池"或"连接池","线程池"旨在减少创建和销毁线程的频率,其维持一定合理数量的线程,并让空闲 ...
- [原创]关于mybatis中一级缓存和二级缓存的简单介绍
关于mybatis中一级缓存和二级缓存的简单介绍 mybatis的一级缓存: MyBatis会在表示会话的SqlSession对象中建立一个简单的缓存,将每次查询到的结果结果缓存起来,当下次查询的时候 ...
- dubbo学习过程、使用经验分享及实现原理简单介绍
一.前言 部门去年年中开始各种改造,第一步是模块服务化,这边初选dubbo试用在一些非重要模块上,慢慢引入到一些稍微重要的功能上,半年时间,学习过程及线上使用遇到的些问题在此总结下. 整理这篇文章差不 ...
- jQuery系列 第一章 jQuery框架简单介绍
第一章 jQuery框架简单介绍 1.1 jQuery简介 jQuery是一款优秀的javaScript库(框架),该框架凭借简洁的语法和跨平台的兼容性,极大的简化了开发人员对HTML文档,DOM,事 ...
- 第一篇:SpringBoot2.0简单介绍
距离Spring Boot1.0发布已经4年了,今年3月份SpringBoot2.0正式发布.让我们一起来了解一下它. Spring Boot主要依赖于Spring,整合了很多框架的使用方式,帮助开发 ...
- C# 基础知识 (四).C#简单介绍及托管代码
暑假转瞬即逝,从10天的支教生活到1周的江浙沪旅游,在这个漫长的暑假中我经历了非常多东西,也学到了非常多东西,也认识到了非常多不足之处!闲暇之余我准备又一次进一步巩固C#相关知识,包含 ...
随机推荐
- 利用set特性判断list是否存在重复的值
List<String> list2=new ArrayList();//存放很多值的list //根据set不能存储相同的值该特性来判断list2中的值是否重复 HashSet set ...
- MAVEN - 生命周期(1)
三套生命周期: MAVEN拥有三套互相独立的生命周期,分别是:clean.default和site. clean - 清理项目 default - 构建项目 site - 简历项目站点 这其中 ...
- 阿里巴巴矢量库IconFont__使用小录
使用阿里巴巴矢量库方法虽然不难,但本人记性不好,遂在次记录几笔 阿里巴巴矢量库地址:http://www.iconfont.cn/plus 阿里巴巴矢量库图标好处: 1:图标矢量化 2:自己总结:ic ...
- Ceph 文件系统的安装
yum install -y wget wget https://pypi.python.org/packages/source/p/pip/pip-1.5.6.tar.gz#md5=01026f87 ...
- C# 获取 IEnumerable 集合的个数
IEnumerable<DocApply> data1 = data.Where(n => n.DocName.Contains(search)); if (data1.GetEnu ...
- vue中引入json数据,不用本地请求
1.我的项目结构,需要在Daily.vue中引入daily.js中的json数据 2.把json数据放入一个js文件中,用exports导出,vscode的json格式太严格了,很多数据,调了一个多小 ...
- python tkinter模块小工具界面
代码 #-*-coding:utf-8-*- import os from tkinter import * root=Tk() root.title('小工具') #清空文本框内容 def clea ...
- 51nod1289 大鱼吃小鱼
有N条鱼每条鱼的位置及大小均不同,他们沿着X轴游动,有的向左,有的向右.游动的速度是一样的,两条鱼相遇大鱼会吃掉小鱼.从左到右给出每条鱼的大小和游动的方向(0表示向左,1表示向右).问足够长的时间之后 ...
- [Ynoi2015]此时此刻的光辉
题目大意: 给定一个序列,每次询问一段区间的数的乘积的约数个数. 解题思路: 在太阳西斜的这个世界里,置身天上之森.等这场战争结束之后,不归之人与望眼欲穿的众人, 人人本着正义之名,长存不灭的过去.逐 ...
- 实用的 鼠标滑上显示提示信息的jq插件
使用非常简单, 引用 css js文件, 将需要显示提示信息的元素 添加class="tooltip"类名, 在title属性填写提示信息就好了title="啊啊啊啊&q ...