一、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. Java采用JDBC的方式连接Hive(SparkSQL)

    前两天,由于系统的架构设计的原因,想通过Java直接访问Hive数据库,对于我这个Java以及Hadoop平台的菜鸟来说,的确是困难重重,不过,还好是搞定了.感觉也不是很麻烦.这篇文章,作为一个感想记 ...

  2. Android初级教程理论知识(第六章广播接受者)

    总体概述: 广播接收者 现实中:电台要发布消息,通过广播把消息广播出去,使用收音机,就可以收听广播,得知这条消息 Android中:系统在运行过程中,会产生很多事件,那么某些事件产生时,比如:电量改变 ...

  3. (NO.00004)iOS实现打砖块游戏(三):游戏主场景和砖块

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 制作墙体 首先在SpriteBuilder中新建Wall.ccb ...

  4. JSP连接MySQL时出现--错误:Access denied for user 'root'@'localhost' (using password: YES)'解决方案

    用代码进行用户验证的时候总是出现这个错误,翻译一下,应该是root用户的是权限的问题没有放开. 那就想办法解决一下吧,具体的来说可以有这样的几种方式. 解决方法,首先想到的是先重启一下MySQL服务吧 ...

  5. iOS中 WGAFN_网络监控 技术分享

    需要用到第三方AFNetworking/SVProgressHUD 没有的可以关注我微博私信我.http://weibo.com/hanjunqiang AppDelegate.m #import & ...

  6. 【leetcode73】经典算法-Guess Number Higher or Lower

    题目描述: 从1-n中,随便的拿出一个数字,你来猜测. 提示 提供一个guess(int num)的api,针对猜测的数字,返回三个数值.0,-1,1 0;猜中返回num -1:比猜测的数值小 1:比 ...

  7. 解决UIScrollView,UIImageView等控件不能响应touch事件的问题

    关于UIScrollView,UIImageView等控件不能响应touch事件,主要涉及到事件响应者链的问题,如果在UIScrollView,UIImageView等控件添加了子View,这样事件响 ...

  8. 手把手带你做一个超炫酷loading成功动画view Android自定义view

    写在前面: 本篇可能是手把手自定义view系列最后一篇了,实际上我也是一周前才开始真正接触自定义view,通过这一周的练习,基本上已经熟练自定义view,能够应对一般的view需要,那么就以本篇来结尾 ...

  9. 《java入门第一季》之类(String类常见方法小叙)

    String类下面的构造方法和一些常见的方法: /* * 字符串:就是由多个字符组成的一串数据.也可以看成是一个字符数组. * 通过查看API,可以知道 * A:字符串字面值"abc&quo ...

  10. STL - vector容器

    1Vector容器简介 vector是将元素置于一个动态数组中加以管理的容器. vector可以随机存取元素(支持索引值直接存取, 用[]操作符或at()方法,这个等下会详讲). vector尾部添加 ...