看了几天高并发和NIO 今晚终于要开始学习Netty

http://ifeve.com/netty5-user-guide/

Netty实现通信的步骤

1.创建两个NIO线程组,一个专门用于网络事件的处理(接收客户端的连接), 另一个则进行网络通信读写

2.创建一个ServerBootStarp对象,配置Netty的一些列参数,例如接受传出数据内存的大小等。

3.创建一个世纪处理数据的类ChannelInitializer,进行初始化的工作,例如设置接收传出数据的字符集·格式·已经实现处理数据的接口

4.绑定端口,执行同步阻塞方法等待服务端的启动

TCP粘包拆包

TCP是一个流的协议,数据没有分界线 如果传了三个包 ABC  DEF  GHI   TCP会根据缓冲区实际情况进行包的划分就容易 解读为 AB  CDEF  GHI

解决办法:1.消息定长,例如每个报文大小固定为200,如果不够,空位补空格

     2.在包尾加特殊字符进行分割

     3.将消息分为消息头和消息体,在消息头中包含消息总长度的字段,然后进行业务逻辑的处理

public class Server {

    public static void main(String[] args) throws Exception {
//1 创建线两个程组
//一个是用于处理服务器端接收客户端连接的
//一个是进行网络通信的(网络读写的)
EventLoopGroup pGroup = new NioEventLoopGroup();
EventLoopGroup cGroup = new NioEventLoopGroup(); //2 创建辅助工具类,用于服务器通道的一系列配置
ServerBootstrap b = new ServerBootstrap();
b.group(pGroup, cGroup) //绑定俩个线程组
.channel(NioServerSocketChannel.class) //指定NIO的模式
.option(ChannelOption.SO_BACKLOG, 1024) //设置tcp缓冲区
.option(ChannelOption.SO_SNDBUF, 32*1024) //设置发送缓冲大小
.option(ChannelOption.SO_RCVBUF, 32*1024) //这是接收缓冲大小
.option(ChannelOption.SO_KEEPALIVE, true) //保持连接
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
//3 在这里配置具体数据接收方法的处理
sc.pipeline().addLast(new ServerHandler());
}
}); //4 进行绑定
ChannelFuture cf1 = b.bind(8765).sync();
//ChannelFuture cf2 = b.bind(8764).sync();
//5 等待关闭
cf1.channel().closeFuture().sync();
//cf2.channel().closeFuture().sync();
pGroup.shutdownGracefully();
cGroup.shutdownGracefully();
}
}
public class ServerHandler extends ChannelHandlerAdapter {

    @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("server channel active... ");
} @Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req, "utf-8");
System.out.println("Server :" + body );
String response = "进行返回给客户端的响应:" + body ;
ctx.writeAndFlush(Unpooled.copiedBuffer(response.getBytes()))
.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
System.out.println("write is success");
} else {
future.cause().printStackTrace();
}
}
})
.addListener(ChannelFutureListener.CLOSE); // 关闭
} @Override
public void channelReadComplete(ChannelHandlerContext ctx)
throws Exception {
System.out.println("读完了");
ctx.flush();
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable t)
throws Exception {
ctx.close();
} }
public class Client {

    public static void main(String[] args) throws Exception{

        EventLoopGroup group = new NioEventLoopGroup();
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
sc.pipeline().addLast(new ClientHandler());
}
}); ChannelFuture cf1 = b.connect("127.0.0.1", 8765).sync();

   //这个对象可以看作是一个异步操
  //作的结果的占位符;它将在未来的某个时刻完成,并提供对其结果的访问

//ChannelFuture cf2 = b.connect("127.0.0.1", 8764).sync();
//发送消息
Thread.sleep(1000);
cf1.channel().writeAndFlush(Unpooled.copiedBuffer("777".getBytes()));
cf1.channel().writeAndFlush(Unpooled.copiedBuffer("666".getBytes()));
//cf2.channel().writeAndFlush(Unpooled.copiedBuffer("888".getBytes()));
Thread.sleep(2000);
cf1.channel().writeAndFlush(Unpooled.copiedBuffer("888".getBytes()));
//cf2.channel().writeAndFlush(Unpooled.copiedBuffer("666".getBytes())); cf1.channel().closeFuture().sync();
//cf2.channel().closeFuture().sync();
group.shutdownGracefully(); }
}
public class ClientHandler extends ChannelHandlerAdapter{

    @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
} @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try {
ByteBuf buf = (ByteBuf) msg; byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req); String body = new String(req, "utf-8");
System.out.println("Client :" + body );
String response = "收到服务器端的返回信息:" + body;
} finally {
ReferenceCountUtil.release(msg);
}
} @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { } @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
ctx.close();
} }

这里面的Bootstarp 相当于配置引导类

对于 ChannelInitializer 是ChannelHandler  Netty 程序都是基于 ChannelPipeline。ChannelPipeline 和 EventLoop 和EventLoopGroup 密切相关,因为它们三个都和事件处理相关,所以这就是为什么它们处理IO 的工作由 EventLoop 管理的原因

当注册一个Channel后 Netty 将这个 Channel 绑定到一个 EventLoop,在 Channel 的生 命周期内总是被绑定到一个 EventLoop。在 Netty IO 操作中,你的程序不需要同步,因为

一个指定通道的所有 IO 始终由同一个线程来执行

ServerBootstrap 使用 2 个 EventLoopGroup

一个 ServerBootstrap 可以认为有 2个 channels 组,
第一组包含一个单例 ServerChannel,代表持有一个绑定了本地端口的socket;
第二组包含所有的 Channel,代表服务器已接受了的连接

EventLoopGroup A 唯一的目的就是接受连接然后交给 EventLoopGroup B,

//1 创建线两个程组
//一个是用于处理服务器端接收客户端连接的
//一个是进行网络通信的(网络读写的)

Netty可以使用两个不同的 Group,因为服务器程序需要接受很多客户端连接的情况下,一个EventLoopGroup 将是程序性能的瓶颈,因为事件循环忙于处理连接请求,没有多余的资源
和空闲来处理业务逻辑,最后的结果会是很多连接请求超时。若有两 EventLoops, 即使在高负载下,所有的连接也都会被接受,因为 EventLoops 接受连接不会和哪些已经连接了的处理共享资源

EventLoopGroup 和 EventLoop 是什么关系?EventLoopGroup 可以包含很多个EventLoop,每个 Channel 绑定一个 EventLoop 不会被改变,因为 EventLoopGroup 包含少量的 EventLoop 的 Channels,很多 Channel 会共享同一个 EventLoop。这意味着在一个Channel 保持 EventLoop 繁忙会禁止其他 Channel 绑定到相同的 EventLoop。我们可以理解为 EventLoop 是一个事件循环线程,而 EventLoopGroup 是一个事件循环集合。

 ChannelFuture 

这个对象可以看作是一个异步操作的结果的占位符;它将在未来的某个时刻完成,并提供对其结果的访问

每个 Netty 的出站 I/O 操作都将返回一个 ChannelFuture;也就是说,它们都不会阻塞。正如我们前面所提到过的一样,Netty 完全是异步和事件驱动的

Netty 中发送消息有两种方法:直接写入通道或写入ChannelHandlerContext 对象。这两种方法的主要区别如下:

  直接写入通道导致处理消息从 ChannelPipeline 的尾部开始(ChannelOutboundHandler 出站)

  写入 ChannelHandlerContext 对象导致处理消息从 ChannelPipeline 的下一个handler 开始(ChannelInboundHandler入站)

对于这些Handler  常 有“ByteToMessageDecoder”、“MessageToByteEncoder”,还有 Google 的协议“ProtobufEncoder”和“ProtobufDecoder”

我们可以在添加 ChannelHandler 到 ChannelPipeline 中时指定一个 EventExecutorGroup,EventExecutorGroup 会获得一个 EventExecutor,EventExecutor将执行ChannelHandler 的所有方法。EventExecutor 将使用不同的线程来执行和释放EventLoop

Transport API

如上图所示,每个 Channel 都会分配一个 ChannelPipeline 和 ChannelConfig

ChannelConfig 负责设置并存储配置,并允许在运行期间更新它们, ChannelPipeline 容纳了使用的 ChannelHandler 实例

ChannelHandler 

 传输数据时,将数据从一种格式转换到另一种格式
 异常通知
 Channel 变为有效或无效时获得通知
 Channel 被注册或从 EventLoop 中注销时获得通知
 通知用户特定事件

Channel 

 eventLoop(),返回分配给 Channel 的 EventLoop
 pipeline(),返回分配给 Channel 的 ChannelPipeline
 isActive(),返回 Channel 是否激活,已激活说明与远程连接对等
 localAddress(),返回已绑定的本地 SocketAddress
 remoteAddress(),返回已绑定的远程 SocketAddress
 write(),写数据到远程客户端,数据通过 ChannelPipeline 传输过去

 不同类型的 ByteBuf

Heap Buffer(堆缓冲区)

存储在JVM的堆上,存储在数组中,清除也很快 ByteBuf.array()获取数据   hasArray 检查

Direct Buffer(直接缓冲区)

直接缓冲区,在堆之外直接分配内存。直接缓冲区不会占用堆空间容量。直接缓冲区在使用 Socket 传递数据时性能很好,因为若使用间接缓冲区,JVM 会先将数据复制到直接缓冲区再进行传递;缺点是在分配内存空间和释放内存时比堆缓冲区更复杂,而 Netty 使用内存池来解决这样的问题,这也是 Netty 使用内存池的原因之一。直接缓冲区不支持数组访问数据,但是我们可以间接的访问数据数组

  ByteBuf directBuf = Unpooled.directBuffer(16);
if(!directBuf.hasArray()){
int len = directBuf.readableBytes();
byte[] arr = new byte[len];
directBuf.getBytes(0, arr);
}

Composite Buffer(复合缓冲区)

CompositeByteBuf compBuf = Unpooled.compositeBuffer();
ByteBuf heapBuf = Unpooled.buffer(8);
ByteBuf directBuf = Unpooled.directBuffer(16);
// 添加 ByteBuf 到 CompositeByteBuf
compBuf.addComponents(heapBuf, directBuf);
// 删除第一个 ByteBuf
compBuf.removeComponent(0);
Iterator<ByteBuf> iter = compBuf.iterator();
while (iter.hasNext()) {
System.out.println(iter.next().toString());
}
// 使用数组访问数据
if (!compBuf.hasArray()) {
int len = compBuf.readableBytes();
byte[] arr = new byte[len];
compBuf.getBytes(0, arr);
}

ByteBuf 提供两个指针变量支付读和写操作,读操作是使用 readerIndex(),写操作时使用 writerIndex()。这和 JDK 的 ByteBuffer 不同,ByteBuffer 只有一个方法来设置索引所以需要使用 flip()方法来切换读和写模式

discardReadBytes()可以用来清空 ByteBuf 中已读取的数据, 

isReadable()  判断 writerIndex > readerIndex;

writableBytes   capacity() - writerIndex 能否写

clear() 重设索引    readerIndex = writerIndex = 0;

调用 duplicate()、slice()、slice(int index, int length)、order(ByteOrder endianness)
会创建一个现有缓冲区的视图

ByteBufAllocator 负责分配 ByteBuf 实例  ByteBufAllocator.heapBuffer()

可以从 Channel 的 alloc()获取,也可以从ChannelHandlerContext 的 alloc()获取

Unpooled 也能创建ByteBuf实例    Unpooled.compositeBuffer();   Unpooled.buffer(8);

ChannelHandler

ChannelPipeline

ChannelPipeline是ChannelHandler实例的列表,用于处理或截获通道的接收和发送数据。

addFirst(...),添加ChannelHandler在ChannelPipeline的第一个位置  addBefore(...),在ChannelPipeline中指定的ChannelHandler名称之前添加ChannelHandler

每个ChannelHandler被添加到ChannelPipeline后,都会创建一个ChannelHandlerContext并与之创建的ChannelHandler关联绑定。ChannelHandlerContext允许ChannelHandler与其他的ChannelHandler实现进行交互。

   如果你想有一些事件流全部通过ChannelPipeline,有两个不同的方法可以做到:
  • 调用Channel的方法
  • 调用ChannelPipeline的方法
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//Event via Channel
Channel channel = ctx.channel();
channel.write(Unpooled.copiedBuffer("netty in action", CharsetUtil.UTF_8));
//Event via ChannelPipeline
ChannelPipeline pipeline = ctx.pipeline();
pipeline.write(Unpooled.copiedBuffer("netty in action", CharsetUtil.UTF_8));

    //为了节省开销,不感兴趣的ChannelHandler不让通过 直接跳过前面

    //排除一些ChannelHandler

    ctx.write(Unpooled.copiedBuffer("Netty in Action", CharsetUtil.UTF_8));

        }
});
}

ChannelHandler实例如果带有@Sharable注解则可以被添加到多个ChannelPipeline。也就是说单个ChannelHandler实例可以有多个ChannelHandlerContext,因此可以调用不同ChannelHandlerContext获取同一个ChannelHandler

Channel的状态在其生命周期中变化

  • handlerAdded,ChannelHandler添加到实际上下文中准备处理事件
  • handlerRemoved,将ChannelHandler从实际上下文中删除,不再处理事件
  • exceptionCaught,处理抛出的异常

ChannelInboundHandler\

  • channelRegistered,ChannelHandlerContext的Channel被注册到EventLoop;
  • channelUnregistered,ChannelHandlerContext的Channel从EventLoop中注销
  • channelActive,ChannelHandlerContext的Channel已激活
  • channelInactive,ChannelHanderContxt的Channel结束生命周期
  • channelRead,从当前Channel的对端读取消息
  • channelReadComplete,消息读取完成后执行
  • userEventTriggered,一个用户事件被处罚
  • channelWritabilityChanged,改变通道的可写状态,可以使用Channel.isWritable()检查
  • exceptionCaught,重写父类ChannelHandler的方法,处理异常

常用的 SimpleChannelInboundHandler

ChannelOutboundHandler

        ChannelOutboundHandler用来处理“出站”的数据消息。ChannelOutboundHandler提供了下面一些方法:
  • bind,Channel绑定本地地址
  • connect,Channel连接操作
  • disconnect,Channel断开连接
  • close,关闭Channel
  • deregister,注销Channel
  • read,读取消息,实际是截获ChannelHandlerContext.read()
  • write,写操作,实际是通过ChannelPipeline写消息,Channel.flush()属性到实际通道
  • flush,刷新消息到通道

 ByteToMessageDecoder

MessageToByteEncoder

 ByteToMessageCodec   用来处理 byte-to-message 和 message-to-byte

MessageToMessageCodec

CombinedChannelDuplexHandler 来结合解码器和编码器   组合

Netty的学习的更多相关文章

  1. netty深入学习之一: 入门篇

    netty深入学习之一: 入门篇 本文代码下载: http://download.csdn.net/detail/cheungmine/8497549 1)Netty是什么 Netty是Java NI ...

  2. 关于Netty的学习前总结

    摘要 前段时间一直在学习netty因为工作忙的原因没有写一个学习的总结,今天抽个空先把总结写了吧.事先声明,本文不会详细的介绍每一个部分不过每个部分都会附上讲解详细的url.本文只是为了解释通Nett ...

  3. netty深入学习之中的一个: 入门篇

    netty深入学习之中的一个: 入门篇 本文代码下载: http://download.csdn.net/detail/cheungmine/8497549 1)Netty是什么 Netty是Java ...

  4. 《精通并发与Netty》学习笔记(01 - netty介绍及环境搭建)

    一.Netty介绍     Netty是由JBOSS提供的一个java开源框架.Netty提供异步的.事件驱动的网络应用程序框架和工具,用以快速开发高性能.高可靠性的网络服务器和客户端程序.     ...

  5. Netty源代码学习——EventLoopGroup原理:NioEventLoopGroup分析

    类结构图: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd29ya2luZ19icmFpbg==/font/5a6L5L2T/fontsize/400/f ...

  6. Netty 框架学习 —— 第一个 Netty 应用

    概述 在本文,我们将编写一个基于 Netty 实现的客户端和服务端应用程序,相信通过学习该示例,一定能更全面的理解 Netty API 该图展示的是多个客户端同时连接到一台服务器.客户端建立一个连接后 ...

  7. Netty 框架学习 —— 引导

    概述 前面我们学习了 ChannelPipeline.ChannelHandler 和 EventLoop 之后,接下来的问题是:如何将它们组织起来,成为一个可实际运行的应用程序呢?答案是使用引导(B ...

  8. Netty 框架学习 —— 编解码器框架

    编解码器 每个网络应用程序都必须定义如何解析在两个节点之间来回传输的原始字节,以及如何将其和目标应用程序的数据格式做相互转换.这种转换逻辑由编解码器处理,编解码器由编码器和解码器组成,它们每种都可以将 ...

  9. Netty入门学习

    一.他山之石 Netty实现原理浅析 http://www.importnew.com/15656.html netty线程模型 http://www.infoq.com/cn/articles/ne ...

随机推荐

  1. vs2015 调试 无法启动程序

    应用程序输出在这个目录 E:\learn\3dlesson\lesson1_createwindow\build\Debug 项目属性 解决方案: 把项目设为启动项目.

  2. ICP、MRR、BKA等特性

    一.Index Condition Pushdown(ICP) Index Condition Pushdown (ICP)是 mysql 使用索引从表中检索行数据的一种优化方式,从mysql5.6开 ...

  3. html预加载之link标签

    我们之前提及过link rel 里面有preload和prefetch.modulepreload,都是用于预加载资源 <link rel="preload" href=&q ...

  4. Java 使用线程池分批插入或者更新数据

    需求:在开发业务报表时,需要从MySQL数据库读取数据后进行操作,然后写入数据库,使用定时任务跑批. 分析:①兼顾性能,②  MySQL没有Oracle那么方便.强大的存储过程.综上所述,使用线程池以 ...

  5. mysql端口3306无法访问

    mysql主备复制,show slave status显示IO一直connecting 一.查看了防火墙,已经处于关闭状态 二.查看使用的复制用户的权限,也已经开放 三.telnet访问另外一台机器端 ...

  6. based on Greenlets (via Eventlet and Gevent) fork 孙子worker 比较

    Design — Gunicorn 19.9.0 documentationhttp://docs.gunicorn.org/en/stable/design.html#async-workers e ...

  7. <JavaScript> 匿名函数和闭包的区别

    匿名函数:没有名字的函数:并没有牵扯到应用其他函数的变量问题.仅仅是没有名字. 定义方式: 1,var A = function(){ }; 2, (function (x,y){ })(2,3); ...

  8. 利用sorket实现聊天功能-服务端实现

    工具包 package loaderman.im.util; public class Constants { public static final String SERVER_IP = " ...

  9. 阶段5 3.微服务项目【学成在线】_day03 CMS页面管理开发_09-修改页面-服务端-接口开发

    需要写两个接口 api的接口内定义两个方法.修改的地方单独传了id @ApiOperation("根据页面id查询页面信息") public CmsPage findById(St ...

  10. xcode报错: 找不到路径或者资源错误:no such file or directory

    报错截图: 出现的问题: 运行项目页面图片不显示. 解决方法: 1>[COMMAND+shift+G],前往文件夹,输入:  ~/Library/Developer/Xcode/DerivedD ...