Netty学习笔记(一)--- 初识Netty_Lincain的博客-程序员秘密

技术标签: Netty  

什么是Netty

如果在网络上搜索它,你可以在官网上看到如下内容:

Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients.

简单说,Netty 是一个基于NIO的客户、服务器端编程框架,它支持快速、简单地开发面向协议的服务器和客户端等网络应用程序,简化和流线化了网络应用的编程开发过程,例如:基于TCP和UDP的socket服务开发。

为什么使用Netty

阻塞I/O

对于早期的网络编程,Java API只支持由本地系统套接字库提供的所谓的阻塞函数,通过它们虽然能实现网络编程,但是问题也随之而来。在这种阻塞的I/O模型中,一个Thread只能同时处理一个连接,要想实现多个并发客户端,就需要为每个客户端创建一个新的Thread。这种并发方案对于支撑中小数量的客户端来说还算可以接受,但是为了支撑 100000 或者更多的并发连接所需要的资源使得它很不理想。

非阻塞I/O

Java 对于非阻塞 I/O 的支持是在 2002 年引入的,位于 JDK 1.4 的 java.nio 包中。该模型使用了事件通知API来确定在一组非阻塞套接字中有哪些通道已经就绪能够进行I/O相关的操作,使得可以在任何的时间检查任意I/O操作的完成情况,然后再交由指定的Thread进行处理,这样就可以使用一个单一的线程处理多个并发的客户端连接。

相对阻塞 I/O 来说,这种模型为网络编程提供了更好的方案,使用较少的线程便可以处理多个连接,减少了内存管理和上下文切换所带来开销,避免了资源的浪费。

两种I/O模型的对比

尽管我们已经可以通过NIO来进行应用程序的构建,但是NIO的类库和API繁杂,使用麻烦;且编写高质量的NIO程序,你还需要对多线程和网络编程等方面非常熟悉,对于大多数的程序员要做到如此正确和安全并不容易。特别是,在高负载下可靠和高效地处理和调度 I/O 操作是一项繁琐而且容易出错的任务。

于是,使用Netty进行网络编程也就顺理成章了,它封装了网络编程的复杂性,API使用简单,更容易上手,使网络编程和Web技术的最新进展能够被比以往更广泛的开发人员接触到。

Netty的特点

在学习Netty之前,先仔细看看Netty的特点(优势),虽然现在可能对此理解不深,但在后面的学习和使用中,我们将不断验证这些特点,正是这些特点让Netty逐渐成为了Java NIO变成的首选框架。

分类 Netty的特点
设计 统一的API,支持多种传输类型,阻塞的和非阻塞的
简单而强大的线程模型
真正的无连接数据报套接字支持
链接逻辑组件以支持复用
易于使用 翔实的Javaodc和大量的代码实例
不需要超过JDK1.6+的依赖(部分特性可能需要Java1.7+的依赖)
性能 拥有比Java核心API更高的吞吐量和更低的延迟
得益于池化和复用,拥有更低的资源消耗
最少的内存复制
健壮性 不会因为慢速、快速或者超载的连接而导致OutOfMemoryError
消除在高速网络中NIO应用程序常见的不公平读/写比
安全性 完整的SSL/TLS和StartTLS支持
可用于受限环境下,如Applet和OSGI
社区驱动 发布快速而且频繁

Netty入门案例

在了解了Netty的相关知识后,我们通过一个 Echo客户端和服务器应用程序来了解Netty的工作方式(该Demo参考了 第二章的案例,稍微进行了修改)。

本文的Netty版本为4.0.33.Final,下面是maven的配置:

<?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.lincain</groupId>
  <artifactId>maven-parent</artifactId>
  <version>1.0-SNAPSHOT</version>

  <name>maven-parent</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>io.netty</groupId>
      <artifactId>netty-all</artifactId>
      <version>4.0.33.Final</version>
    </dependency>

    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.3.2</version>
    </dependency>
  </dependencies>  
</project>

1 .Echo服务器

1.1引导服务器

package com.lincain.echo;

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;

public class EchoServer {
    
    private final int port;

    public EchoServer(int port){
    
        this.port = port;
    }

    public void start() throws  Exception{
    
        // 创建EventLoopGroup的实现类NioEventLoopGroup对象
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        try {
    
            // 创建服务器引导ServerBootstrap的示例对象
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup);
            // 指定使用NIO的传输Channel
            bootstrap.channel(NioServerSocketChannel.class);
            // 使用指定的端口设置套接字地址
            bootstrap.localAddress(port);
            // 通过ChannelInitializer添加一个EchoServerHandler到子Channel的ChannelPipeLine上
            bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
    
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
    
                    ch.pipeline().addLast(new EchoServerHandler());
                }
            });
            // 异步地绑定服务器,调用sync()方法阻塞等待直到绑定完成
            ChannelFuture future = bootstrap.bind().sync();
            System.out.println(EchoServer.class.getSimpleName() +
                    " started and listen on " + future.channel().localAddress());
            // 获取Channel的CloseFuture,并阻塞当前线程直至它完成
            future.channel().closeFuture().sync();
        }finally {
    
            // 关闭EventLoopGroup,并释放所有的资源
            bossGroup.shutdownGracefully().sync();
        }
    }

    public static void main(String[] args) throws Exception{
    
        // 设置端口号,并调用服务器的start()方法
        new EchoServer(20000).start();
    }
}

1.2 服务器业务处理逻辑

package com.lincain.echo;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

public class EchoServerHandler extends ChannelInboundHandlerAdapter {
    
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)
        throws Exception {
    
        ByteBuf in = (ByteBuf)msg;
        // 将入站的消息打印在控制台
        System.out.println("Server received:" + in.toString(CharsetUtil.UTF_8));
        // 将接收到的消息写给发送者,而不冲刷出站消息
        ctx.write(in);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx)
        throws Exception {
    
        // 将未决消息冲刷到远程节点,并且关闭该Channel
        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).
                addListener(ChannelFutureListener.CLOSE);
    }
    
    // 打印异常栈跟踪,并关闭该Channel
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
        throws Exception {
    
        cause.printStackTrace();
        ctx.close();
    }
}

2.1 引导客户端

package com.lincain.echo;

import io.netty.bootstrap.Bootstrap;
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.NioSocketChannel;

public class EchoClient {
    
    private final int port;
    private final String host;

    public EchoClient(int port, String host) {
    
        this.port = port;
        this.host = host;
    }

    public void start() throws Exception {
    
        // 创建EventLoopGroup的实现类NioEventLoopGroup对象
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
    
            // 创建客户端引导Bootstrap的实例对象
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(workerGroup);
            // 指定所使用额额NIO传输Channel
            bootstrap.channel(NioSocketChannel.class);
            // 设置需要连接的服务器的IP和端口
            bootstrap.remoteAddress(host,port);
            // 通过ChannelInitializer添加一个EchoClientHandler到子Channel的ChannelPipeLine上
            bootstrap.handler(new ChannelInitializer<SocketChannel>() {
    
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
    
                    ch.pipeline().addLast(new EchoClientHandler());
                }
            });
            // 连接到远程服务器,阻塞等待直到连接完成
            ChannelFuture future = bootstrap.connect().sync();
            // 获取Channel的CloseFuture,并阻塞当前线程直至它完成
            future.channel().closeFuture().sync();
        }finally {
    
            // 关闭EventLoopGroup,并释放所有的资源
            workerGroup.shutdownGracefully().sync();
        }
    }

    public static void main(String[] args) throws Exception{
    
        // 设置IP和端口号,并调用客户端的start()方法
        new EchoClient(20000, "localhost").start();
    }
}

2.2 客户端业务处理逻辑

package com.lincain.echo;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;

public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf>{
    
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) 
        throws Exception {
    
        // 将服务端应答的消息打印在控制台上
        System.out.println("Client received:" + msg.toString(CharsetUtil.UTF_8));
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx)
        throws Exception {
    
        // 当被通知Channel是活跃的时候,发送一条消息
        ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!",
                CharsetUtil.UTF_8));
    }

    // 打印异常栈跟踪,并关闭该Channel
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
        throws Exception {
    
        cause.printStackTrace();
        ctx.close();
    }
}

3. 运行程序

首先运行EchoServer让服务端先启动,然后运行EchoClient让客户端启动。

服务端控制台的打印内容:

EchoServer started and listen on /0:0:0:0:0:0:0:0:20000
Server received:Netty rocks!

客户端控制台的打印内容:

Client received:Netty rocks!

通过上面代码,即实现了运用Netty完成Echo服务端和客户端的交互。Echo 客户端和服务器之间的交互是非常简单的;在客户端建立一个连接之后,它会向服务器发送一个或多个消息,反过来,服务器又会将每个消息回送给客户端。虽然它本身看起来好像用处不大,但它充分地体现了客户端/服务器系统中典型的请求-响应交互模式。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Lincain/article/details/89891199

智能推荐

Golang获取Goroutine Id的最佳实践_golang 获取goroutineid_冷月醉雪的博客-程序员秘密

    Goroutine是Golang中轻量级线程的实现,由Go Runtime管理。Golang在语言级别支持轻量级线程,叫做协程。Golang标准库提供的所有系统调用操作,都会出让CPU给其他Goroutine。这让事情变得非常简单,让轻量级线程的切换管理不依赖于系统的线程和进程,也不依赖于CPU的核心数量。    Goroutine非常亮眼,但是自从go1.4版本以后,Goroutin...

中国石油大学-《大学英语一》第三阶段在线作业_chenjing-1125的博客-程序员秘密

第三次在线作业单选题 (共40道题)收起1.(2.5分) --How about taking a break?--______. A、Thank you B、Good idea C、See you D、Good-bye我的答案:B此题得分:2.5分2.(2.5分) Seldom ____ any mistakes during my past few years of working here. A、would I mak...

TOJ4309 表达式求值 stack_数据结构dr.kong的机器人流程图_TheWise_lzy的博客-程序员秘密

描述Dr.Kong设计的机器人卡多掌握了加减法运算以后,最近又学会了一些简单的函数求值,比如,它知道函数min(20,23)的值是20 ,add(10,98) 的值是108等等。经过训练,Dr.Kong设计的机器人卡多甚至会计算一种嵌套的更复杂的表达式。假设表达式可以简单定义为:1. 一个正的十进制数 x 是一个表达式。2. 如果 x 和 y 是 表达式,则 函数min(x,y )也是表达式,其值...

@interface java注解_Love吵吵闹闹的博客-程序员秘密

@Documented,@Retention,@Target,@Inherited1. 编写自定义@Todo注解经常我们在写程序时,有时候有些功能在当前的版本中并不提供,或由于某些其它原因,有些方法没有完成,而留待以后完成,我们在javadoc中用@TODO来描述这一行为,下面用java注解来实现。public @interface Todo { } // Todo.java

李开复:未来10年AI将取代人类?AI已让这7种职业加速消失_code小生_的博客-程序员秘密

12016年AlphaGo以总比分4:1轻松战胜围棋世界冠军李世石,这是AI第一次震惊世界。次年AlphaGo又以3:0的比分击败柯洁,面对强大的AI,柯洁遭遇职业生涯“...

matlab 简单算例,(简单算例)基于Matlab的电力系统潮流编程计算.pdf_刘万祥ExcelPro的博客-程序员秘密

(简单算例)基于Matlab的电力系统潮流编程计算基于Matlab的电力系统潮流编程计算口黄扬威吴喜春郭志峰张斯翔(三峡大学电气与新能源学院湖北·宜昌443002)摘要:通过介绍电力系统的实际运行情况,提出运用Matlab语言对电力系统潮流计算进行编程计算。在程序的编写过程中采用了数学模型建立,稀疏技术、节点编号顺序优化等方法。从潮流计算的基本方程出发。采用牛顿一拉夫逊法并通过建立矩阵的修正方程来...

随便推点

[Windows 10] 如何创建及使用Windows的恢复驱动器_winre_drv是怎么创建的_wyabc27的博客-程序员秘密

  如果您的计算机发生严重问题(例如无法进入操作系统),您可以使用恢复驱动器进入Windows恢复环境(WinRE),并协助您恢复Windows或是执行系统还原。若要使用恢复驱动器,首先您需要准备一个空的U盘(至少需要16GB的空间)来建立Windows恢复驱动器,且由于Windows会定期更新并改善安全性及计算机效能,建议您可以定期重新建立新的恢复驱动器。  ※ 注: 恢复驱动器不会备份个人档案以及计算机未随附的应用程序,了解更多如何通过文件历史记录备份文件。  建立恢复驱动器  1. 在Wi

FPGA之FIFO读写数据(发送接收模块,当发送模块检测到FIFO为空时,开始写入数据,当FIFO为满时,读出数据)_fifo数据读出后是清除吗_坚持每天写程序的博客-程序员秘密

1.c创建FIFO的IP核在IP catalog里面搜索FIFO并双击,保存为my_fifo然后一直点击next读和写的full和empty都要√上,不然后面定义要出错勾选inst文件2.对FIFO进行写入操作3.对FIFO进行读出操作4.顶层文件的编写及rtl结构图5.测试文件的编写6.仿真结果...

翻译:图数据库Apache TinkerPop Gremlin图遍历机器和语言_AI架构师易筋的博客-程序员秘密

说明Gremlin是Apache TinkerPop的图形遍历语言。Gremlin是一个功能,数据流 语言,使用户能够简洁地表达复杂的遍历(或查询)的应用程序的性能曲线图。每个Gremlin遍历都由一系列(可能嵌套的)步骤组成。步骤对数据流执行原子操作。每一步都是map步骤(转换流中的对象),filter步骤(从流中移除对象)或sideEffect-step(计算有关流的统计信息)。Gremlin步骤库在这3个基本操作的基础上扩展,为用户提供了丰富的步骤集,用户可以编写这些步骤,以询问他们可能对Greml

代码运行方法 deep sort yolov3: real-time Multi-person tracker using YOLO v3 and deep_sort with tensorflow_multi_person_tracker_计算机视觉-Archer的博客-程序员秘密

github网址:一:https://github.com/nwojke/deep_sort   #这个是论文代码deep sort 此论文的检测部分采取的是下面的这篇论文参数  ECCV2016  (https://blog.csdn.net/sunshinezhihuo/article/details/78885012)SORT:论文地址:http://arxiv.org/...

关于PB程序的开发_zhangjf197926的博客-程序员秘密

 用PB这个开发工具已经有几年了,自从毕业到现在,也一直在用它做为主要吃饭家伙了.呵呵.包括现在,虽然公司的项目要往J2EE平台上发展,可历史遗留程序还是要维护的,而且目前还是公司业务的主要支撑平台.近来,对于客户提出的一些新的需求,我把以前的代码全部整理了一下,以前的代码好至少有三到五个人写过,风格不一,我把所有代码用OO的思想进行了重新整理的安排.这个过程的比较困难的,有时候很烦,不过终于

Cash Machine POJ - 1276 ( 多重背包 )_cash machine poj_SEVENY_的博客-程序员秘密

Cash MachineDescriptionA Bank plans to install a machine for cash withdrawal. The machine is able to deliver appropriate @ bills for a requested cash amount. The machine uses exactly N distinct b...