Netty入门
一、NIO
Netty框架底层是对NIO的高度封装,所以想要更好的学习Netty之前,应先了解下什么是NIO - NIO是non-blocking的简称,
在jdk1.4 里提供的新api,他的他的特性如下:
* 为所有的原始类型提供(Buffer)缓存支持,字符集编码解码解决方案。
* Channel :一个新的原始I/O 抽象。支持锁和内存映射文件的文件访问接口。提供多路(non-bloking)非阻塞式的高伸缩
性网络I/O 。
NIO是一个非阻塞式的I/O,它由一个专门的线程来处理所有的IO事件,并负责分发,并且它只有在事件到达的时候才会触发,
而不是去同步的监视事件;线程之间通过wait,notify 等方式通讯。保证每次上下文切换都是有意义的。减少无谓的线程切换。
NIO和IO最大的区别是数据打包和传输方式。IO是以流的方式处理数据,而NIO是以块的方式处理数据。NIO的核心部分由
Channels、Buffers、Selectors三部分组成。
(一)Channel和Buffer
正常的情况下,所有的IO在NIO中都从一个Channel 开始。Channel有点像流。数据可以从Channel读到Buffer中,也可以从
Buffer写到Channel中。JAVA NIO中的一些主要Channel的实现:FileChannel、DatagramChannel、SocketChannel、
ServerSocketChannel。这些实现类覆盖了UDP和TCP网络IO,以及文件IO。
而Buffer的一些实现类:ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer,则覆盖
了能通过IO发送的基本数据类型:byte,short,int,long,float,double和char。
(二)Selector
Selector允许单线程处理多个Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector
就会很方便。而要使用Selector,就得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的
通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新连接进来,数据接收等。
二、Netty
(一)Netty入门
大数据,高访问场景的互联网项目或者多系统的协同工作,使用一个服务器根本不能胜任。就需要把系统拆分成了多个服务,
根据需要部署在多个机器上,这些服务非常灵活,可以随着访问量弹性扩展。但是多个模块的跨服务通信,时间和资源都是极大
地浪费。传统的Blocking IO不能解决,因为会有线程阻塞的问题,而使用非阻塞IO(NIO),则需要耗费太多的精力。而Netty框架
(RPC框架)则很好的解决了这个问题。
Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、
高可靠性的网络服务器和客户端程序。他就是一个程序,是封装java socket noi的,我们直接拿来用就好了。
Netty通信服务端的步骤:
1、创建两个NIO线程组,一个专门用于网络事件处理(接受客户端的连接),另一个则进行网络通信的读写。
2、创建一个ServerBootstrap对象,配置Netty的一系列参数,例如接受传出数据的缓存大小等。
3、创建一个用于实际处理数据的类ChannelInitializer,进行初始化的准备工作,比如设置接受传出数据的字符集、
格式以及实际处理数据的接口。
4、绑定端口,执行同步阻塞方法等待服务器端启动即可。
5、关闭相应的资源。
服务端栗子:
服务端的管理者:
/**
* 服务端处理通道.这里只是打印一下请求的内容,并不对请求进行任何的响应
* 继承自ChannelHandlerAdapter, 这个类实现了ChannelHandler接口,
* ChannelHandler提供了许多事件处理的接口方法,然后你可以覆盖这些方法。
* @author lcy
*
*/
public class DiscartServiceHandler extends ChannelHandlerAdapter {
/**
* 客户端收到新消息时,这个方法会被调用
*
* @param ctx
* 通道处理的上下文信息
* @param msg
* 接受的消息
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try {
// 将接收到的信息转换为缓冲区
ByteBuf str = (ByteBuf) msg;
// 打印传输过来的信息
System.out.print(str.toString(CharsetUtil.UTF_8));
} finally {
// 释放ByteBuf对象
ReferenceCountUtil.release(msg);
}
} /**
* 在异常时触发
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//输出错误信息
cause.printStackTrace();
ctx.close();
}
}
服务端:
/**
* 服务端
* @author lcy
*
*/
public class DiscartServer {
private int port; public DiscartServer(int port) {
super();
this.port = port;
} public void run() throws Exception {
//(一)设置两个线程组
//用来接收进来的连接
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
// 用来处理已经接受的连接
NioEventLoopGroup workGroup = new NioEventLoopGroup();
System.out.println("准备运行的端口" + port);
try {
//(二)辅助工具类,用于服务器通道的一系列配置
ServerBootstrap bootstrap = new ServerBootstrap();
//(三)绑定两个线程组
// 设置group,这一步是必须的,如果没有设置group将会报java.lang.IllegalStateException:group not set异常
bootstrap = bootstrap.group(bossGroup, workGroup);
//(四)指定NIO的模式
/***
* ServerSocketChannel以NIO的selector为基础进行实现的,用来接收新的连接
* 这里告诉Channel如何获取新的连接.
*/
bootstrap = bootstrap.channel(NioServerSocketChannel.class);
//(五)配置具体的数据处理方式,就是往里添加规则
bootstrap = bootstrap.childHandler(new ChannelInitializer<SocketChannel>() { @Override
protected void initChannel(SocketChannel arg0) throws Exception {
//与50秒内都没有与服务端进行通信的客户端断连
arg0.pipeline().addLast(new ReadTimeoutHandler(50));
arg0.pipeline().addLast(new HttpObjectAggregator(1048576));
// 添加实际处理数据的类
arg0.pipeline().addLast(new DiscartServiceHandler());
}
});
//(六)设置TCP缓冲区
bootstrap = bootstrap.option(ChannelOption.SO_BACKLOG, 128);
//保持连接
bootstrap = bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
//(七)绑定端口,启动接收进来的连接
ChannelFuture sync = bootstrap.bind(port).sync();
//(八) 这里会一直等待,直到socket被关闭
sync.channel().closeFuture().sync();
} finally {
//(九)关闭资源
workGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
//服务开启
public static void main(String[] args) throws Exception {
int port;
if (args.length > 0) {
port = Integer.parseInt(args[0]);
} else {
port = 8080;
}
new DiscartServer(port).run();
System.out.println("server:run()");
}
}
客户端栗子:
实际处理数据的类:
public class ChannelClient extends ChannelInitializer{ @Override
protected void initChannel(Channel arg0) throws Exception {
//与50秒内都没有与服务端进行通信的客户端断连
arg0.pipeline().addLast(new ReadTimeoutHandler(50));
arg0.pipeline().addLast(new HttpObjectAggregator(1048576));
//设置Channel
arg0.pipeline().addLast(new ChannelHandlerAdapter(){ @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try {
// 将接收到的信息转换为缓冲区
ByteBuf str = (ByteBuf) msg;
// 打印传输过来的信息
System.out.print(str.toString(CharsetUtil.UTF_8));
} finally {
// 释放ByteBuf对象
ReferenceCountUtil.release(msg);
}
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//输出错误信息
cause.printStackTrace();
ctx.close();
}
}); }
}
客户端:
/**
* 客户端
* @author lcy
*/
public class Client {
@SuppressWarnings("resource")
public static void main(String[] args) throws Exception {
//创建一个新的线程组
NioEventLoopGroup workGroup = new NioEventLoopGroup();
//初始化Netty
Bootstrap bootstrap = new Bootstrap();
//指定工作的线程组
bootstrap = bootstrap.group(workGroup);
//指定 Channel的类型。因为是客户端, 因此使用了 NioSocketChannel。
bootstrap.channel(NioSocketChannel.class);
/**
* 设置链接的一些属性
*/
//降低延迟,禁用了禁用nagle算法。nagle算法受TCP延迟确认影响,会导致相继两次向连接发送请求包。
bootstrap.option(ChannelOption.TCP_NODELAY, true);
//保持连接检测对方主机是否崩溃,避免(服务器)永远阻塞于TCP连接的输入
bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
//使用netty默认的解码器会出现读取不完整,不会执行channelRead方法。设置这个属性可惜保证Netty读取的完整
bootstrap.option(ChannelOption.MAX_MESSAGES_PER_READ, Integer.MAX_VALUE);
//设置数据处理器
bootstrap.handler(new ChannelClient());
//同步的链接
Channel channel = bootstrap.connect("127.0.0.1", 8080).sync().channel();
channel.writeAndFlush(Unpooled.copiedBuffer("Hello Netty...".getBytes()));
channel.closeFuture().sync();
workGroup.shutdownGracefully();
}
}
(二)Netty的数据通讯:
1、使用长连接通道不断开的形式进行通信,也就是服务器和客户端的通道一直处于开启状态,如果服务器性能足够好,
并且客户端数量也比较多的情况下,推荐这种方式。
2、一次性批量提交数据,采用短连接方式。也就是说先把数据保存到本地临时缓存区或者临时表,当达到界值时进行一
次性批量提交,又或者根据定时任务轮询提交。
3、使用一种特殊的长连接,在某一指定时间段内,服务器与某台客户端没有任何通信,则断开连接。下次连接则是客户
端向服务器发送请求的时候,再次建立连接。
(三)Netty的编解码器:
1. Decoder 解码器 负责将消息从字节或其他序列形式转成指定的消息对象。
2. Encoder 编码器 将消息对象转成字节或其他序列形式在网络上传输。
入站”ByteBuf读取bytes后由 ToIntegerDecoder 进行解码,然后将解码后的消息存入List集合中,然后传递到ChannelPipeline
中的下一个ChannelInboundHandler。
解码器:
1)ByteToMessageDecoder,需自己判断ByteBuf读取前是否有足够的字节,否则会出现沾包的现象。
2)ReplayingDecoder,无需自己检查字节长度,但是使用起来具有局限性:
* 不是所有的操作都被ByteBuf支持,如果调用一个不支持的操作会抛出DecoderException。
* ByteBuf.readableBytes()大部分时间不会返回期望值。
3)MessageToMessageDecoder(message-to-message)
解码器是用来处理入站数据,Netty提供了很多解码器的实现,可以根据需求详细了解。
编码器:
1)MessageToByteEncoder
2)MessageToMessageEncoder 需要将消息编码成其他的消息时可以使用Netty提供的MessageToMessageEncoder抽象类
来实现。例如将Integer编码成String。
(四)Netty中解决TCP粘包/拆包问题
想要解决TCP的粘包/拆包问题,首先要知道什么是TCP粘包、拆包:
TCP是一个“流”协议,所谓流就是没有界限的遗传数据。大家可以想象一下,如果河水就好比数据,他们是连成一片的,没有
分界线,TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的具体情况进行包的划分,也就是说,在业务上一个完整的
包可能会被TCP分成多个包进行发送,也可能把多个小包封装成一个大的数据包发送出去,这就是所谓的粘包/拆包问题。
解决方案:
1、消息定长,例如每个报文的大小固定为200个字节,如果不够,空位补空格。
2、在包尾部增加特殊字符进行分割,例如加回车等。
3、将消息分为消息头和消息体,在消息头中包含表示消息总长度的字段,然后进行业务逻辑的处理。
Netty中解决TCP粘包/拆包的方法:
1、分隔符类:DelimiterBasedFrameDecoder(自定义分隔符)
2、定长:FixedLengthFrameDecoder
Netty入门的更多相关文章
- Netty入门之客户端与服务端通信(二)
Netty入门之客户端与服务端通信(二) 一.简介 在上一篇博文中笔者写了关于Netty入门级的Hello World程序.书接上回,本博文是关于客户端与服务端的通信,感觉也没什么好说的了,直接上代码 ...
- Netty入门之HelloWorld
Netty系列入门之HelloWorld(一) 一. 简介 Netty is a NIO client server framework which enables quick and easy de ...
- netty入门(一)
1. netty入门(一) 1.1. 传统socket编程 在任何时候都可能有大量的线程处于休眠状态,只是等待输入或者输出数据就绪,这可能算是一种资源浪费. 需要为每个线程的调用栈都分配内存,其默认值 ...
- Netty入门(三)之web服务器
Netty入门(三)之web服务器 阅读前请参考 Netty入门(一)之webSocket聊天室 Netty入门(二)之PC聊天室 有了前两篇的使用基础,学习本文也很简单!只需要在前两文的基础上稍微改 ...
- Netty入门(二)之PC聊天室
参看Netty入门(一):Netty入门(一)之webSocket聊天室 Netty4.X下载地址:http://netty.io/downloads.html 一:服务端 1.SimpleChatS ...
- Netty入门(一)之webSocket聊天室
一:简介 Netty 是一个提供 asynchronous event-driven (异步事件驱动)的网络应用框架,是一个用以快速开发高性能.高可靠性协议的服务器和客户端. 换句话说,Netty 是 ...
- netty同时做http和websocket(netty入门)
---恢复内容开始--- http://www.jianshu.com/p/5c29c6c6d28c ---恢复内容结束--- http://www.jianshu.com/p/5c29c6c6d28 ...
- Netty入门教程——认识Netty
什么是Netty? Netty 是一个利用 Java 的高级网络的能力,隐藏其背后的复杂性而提供一个易于使用的 API 的客户端/服务器框架. Netty 是一个广泛使用的 Java 网络编程框架(N ...
- Netty 系列(三)Netty 入门
Netty 系列(三)Netty 入门 Netty 是一个提供异步事件驱动的网络应用框架,用以快速开发高性能.高可靠性的网络服务器和客户端程序.更多请参考:Netty Github 和 Netty中文 ...
随机推荐
- Docker教程:dokcer machine的概念和安装
http://blog.csdn.net/pipisorry/article/details/50920982 Docker machine介绍 做为Docker容器集群管理三剑客之一的Docker ...
- 学习笔记-JS公开课二
typeof运算符的使用 JS中内置对象Array/Date/Math/String可以看成引用类型 做如下测试: <scripttype="text/javascript" ...
- SDK目录结构
android sdk里的各目录作用 AVD Manager.exe:虚拟机管理工具,用于建立和管理虚拟机. SDK Manager.exe:sdk管理工具,用于管理.下载sdk.sdk工具,能及扩展 ...
- 基于java自身技术实现消息方式的系统间通信
这篇博客基本照搬了分布式java应用基础与实践一书的内容 java自带的远程调用分两种一种是rmi,一种是webservice 我们先看rmi(remote method invoke)# 使用rmi ...
- React Native控件只TextInput
TextInput是一个允许用户在应用中通过键盘输入文本的基本组件.本组件的属性提供了多种特性的配置,譬如自动完成.自动大小写.占位文字,以及多种不同的键盘类型(如纯数字键盘)等等. 比如官网最简单的 ...
- Ubuntu 14 安装MySQL指南
ubuntu 14 安装MySQL指南安装MySQLsudo apt-get install mysql-server这个应该很简单了,而且我觉得大家在安装方面也没什么太大问题,所以也就不多说了,下面 ...
- 一个可以拖动的自定义Gridview代码
这个可以拖动的gridview继承于gridview,所以,用法和gridview一样, 代码如下: public class DragGridView extends GridView { priv ...
- ORM对象关系映射之GreenDAO自定义属性转换器PropertyConverter
在使用GreenDAO定义实体的属性时候,通常来说定义的实体属性名就是对应的表的字段名.实体中属性的类型(如Long.String等)就是表的字段名类型,但是我们难免会有不一样的需求,比如实体中我定义 ...
- 【翻译】使用Sencha Touch创建基于Tizen应用程序
原文:Building a Tizen App With Sencha Touch 作者:Gautam Agrawal Gautam Agrawal is Sencha's Sr. Product M ...
- 关于精灵帧(Sprite Frame)的尺寸大小
一个对象的精灵帧(Sprite Frame)有若干关于大小的尺寸. 比较容易混淆,这里记录下来区别: CCSpriteFrame *spriteFrame = self.spriteFrame; CG ...