一、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入门的更多相关文章

  1. Netty入门之客户端与服务端通信(二)

    Netty入门之客户端与服务端通信(二) 一.简介 在上一篇博文中笔者写了关于Netty入门级的Hello World程序.书接上回,本博文是关于客户端与服务端的通信,感觉也没什么好说的了,直接上代码 ...

  2. Netty入门之HelloWorld

    Netty系列入门之HelloWorld(一) 一. 简介 Netty is a NIO client server framework which enables quick and easy de ...

  3. netty入门(一)

    1. netty入门(一) 1.1. 传统socket编程 在任何时候都可能有大量的线程处于休眠状态,只是等待输入或者输出数据就绪,这可能算是一种资源浪费. 需要为每个线程的调用栈都分配内存,其默认值 ...

  4. Netty入门(三)之web服务器

    Netty入门(三)之web服务器 阅读前请参考 Netty入门(一)之webSocket聊天室 Netty入门(二)之PC聊天室 有了前两篇的使用基础,学习本文也很简单!只需要在前两文的基础上稍微改 ...

  5. Netty入门(二)之PC聊天室

    参看Netty入门(一):Netty入门(一)之webSocket聊天室 Netty4.X下载地址:http://netty.io/downloads.html 一:服务端 1.SimpleChatS ...

  6. Netty入门(一)之webSocket聊天室

    一:简介 Netty 是一个提供 asynchronous event-driven (异步事件驱动)的网络应用框架,是一个用以快速开发高性能.高可靠性协议的服务器和客户端. 换句话说,Netty 是 ...

  7. netty同时做http和websocket(netty入门)

    ---恢复内容开始--- http://www.jianshu.com/p/5c29c6c6d28c ---恢复内容结束--- http://www.jianshu.com/p/5c29c6c6d28 ...

  8. Netty入门教程——认识Netty

    什么是Netty? Netty 是一个利用 Java 的高级网络的能力,隐藏其背后的复杂性而提供一个易于使用的 API 的客户端/服务器框架. Netty 是一个广泛使用的 Java 网络编程框架(N ...

  9. Netty 系列(三)Netty 入门

    Netty 系列(三)Netty 入门 Netty 是一个提供异步事件驱动的网络应用框架,用以快速开发高性能.高可靠性的网络服务器和客户端程序.更多请参考:Netty Github 和 Netty中文 ...

随机推荐

  1. DB2数据库常用命令

    --创建数据库 CREATE DATABASE example AUTOMATIC STORAGE YES --自动存储 ON 'D:\' DBPATH ON'D:\' --指定数据库控制文件的存储路 ...

  2. iOS开发之五:常用控件--UITextField的使用

    UITextField 是iOS开发中用的非常多的一种控件,主要是供用户输入单行信息的.下面来详细介绍UITextField. 1.常用属性 <span style="font-siz ...

  3. Spring+Hibernate4 Junit 报错No Session found for current thread

    论坛上有另外一篇更全面的帖子,jinnianshilongnian写的:http://www.iteye.com/topic/1120924 本文的环境是:  spring-framework-3.1 ...

  4. Android5.1设备无法识别exFAT文件系统的64G TF卡问题

    64G TF卡刚买回来的时候默认exFAT文件系统,在电脑端(XP和WIN7)可以识别,但在我们Android5.1S设备无法识别,采用guiformat工具格式化为FAT32文件系统后才可以正常识别 ...

  5. java实现:将一个数逆序输出

    前面我们用C语言实现过这个程序,其实java也一样的,很多步骤跟C差不多,但是有些接口和特性可能不同: import java.util.Scanner;//要使用scanner这个类,就需要导入一个 ...

  6. android官方技术文档翻译——工具属性

    本文译自androd官方技术文档<Tools Attributes>:http://tools.android.com/tech-docs/tools-attributes 本文地址:ht ...

  7. python面向对象小练习

    就是几个动物,自动排列生成什么的 class Animal(object): def __init__(self,name,weight): self.name = name self.weight ...

  8. UGUI实现NGUI的UIEventListener功能

    在unity中处理UI事件时,习惯了使用NGUI的UIEventListener,虽然UGUI也有AddListener,但是一个组件只能对应一个函数,不能在一个函数中同时处理多个事件,显得有些麻烦 ...

  9. Mahout fp-growth

    FP-growth Apriori算法的一个主要瓶颈在于,为了获得较长的频繁模式,需要生成大量的候选短频繁模式.FP-Growth算法是针对这个瓶颈提出来的全新的一种算法模式.目前,在数据挖掘领域,A ...

  10. STL算法设计理念 - 函数对象和函数对象当参数和返回值

    函数对象: 重载函数调用操作符的类,其对象常称为函数对象(function object),即它们是行为类似函数的对象.一个类对象,表现出一个函数的特征,就是通过"对象名+(参数列表)&qu ...