Netty入门实例-编写服务器端程序

按照以前的套路,一般学习一项新的IT技术,首先得来个 ‘Hello,World!‘之类的,以向世界宣告你要学习某项技术了。但在世界上最简单的协议不是’Hello,World!‘而是 DISCARD。它是一种丢弃任何接收到的数据而没有任何响应的协议(暂时就叫它”装死协议”吧)。

要实现DISCARD协议,只需要忽略所有接收到的数据。让我们从处理程序实现直接开始,这个处理程序实现处理Netty生成的I/O事件。先撸下面几串代码吧 -

package cn.netty;

import io.netty.buffer.ByteBuf;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/**
 * 处理服务器端通道
 */
public class DiscardServerHandler extends ChannelInboundHandlerAdapter { // (1)

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) { // (2)
        // 以静默方式丢弃接收的数据
        ((ByteBuf) msg).release(); // (3)
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4)
        // 出现异常时关闭连接。
        cause.printStackTrace();
        ctx.close();
    }
}


一些重要的解释:

  1. DiscardServerHandler扩展了ChannelInboundHandlerAdapter,它是ChannelInboundHandler的一个实现。ChannelInboundHandler提供了可以覆盖的各种事件处理程序方法。 现在,它只是扩展了ChannelInboundHandlerAdapter,而不是自己实现处理程序接口。

  2. 我们在这里覆盖channelRead()事件处理程序方法。每当从客户端接收到新数据时,使用该方法来接收客户端的消息。 在此示例中,接收到的消息的类型为ByteBuf

  3. 要实现DISCARD协议,处理程序必须忽略接收到的消息。ByteBuf是引用计数的对象,必须通过release()方法显式释放。请记住,处理程序负责释放传递给处理程序的引用计数对象。 通常,channelRead()处理程序方法实现如下:

  4. @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
     try {
         // Do something with msg
     } finally {
         ReferenceCountUtil.release(msg);
     }
    }

    当Netty由于I/O错误或由处理事件时抛出的异常而导致的处理程序实现引发异常时,使用Throwable调用exceptionCaught()事件处理程序方法。 在大多数情况下,捕获的异常会被记录,并且其相关的通道应该在这里关闭,这种方法的实现可以根据想要什么样的方式来处理异常情况而有所不同。 例如,您可能希望在关闭连接之前发送带有错误代码的响应消息。


到现在如果没有问题,我们已经实现了DISCARD服务器的前半部分。 现在剩下的就是编写main()方法,并使用DiscardServerHandler启动服务器。

package cn.netty;

import io.netty.bootstrap.ServerBootstrap;

import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/**
 * Discards any incoming data.
 */
public class DiscardServer {

    private int port;

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

    public void run() throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap(); // (2)
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class) // (3)
             .childHandler(new ChannelInitializer<SocketChannel>() { // (4)
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ch.pipeline().addLast(new DiscardServerHandler());
                 }
             })
             .option(ChannelOption.SO_BACKLOG, 128)          // (5)
             .childOption(ChannelOption.SO_KEEPALIVE, true); // (6)

            // Bind and start to accept incoming connections.
            ChannelFuture f = b.bind(port).sync(); // (7)

            // Wait until the server socket is closed.
            // In this example, this does not happen, but you can do that to gracefully
            // shut down your server.
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.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 DiscardServer(port).run();
    }
}


  1. NioEventLoopGroup是处理I/O操作的多线程事件循环。 Netty为不同类型的传输提供了各种EventLoopGroup实现。 在此示例中,实现的是服务器端应用程序,因此将使用两个NioEventLoopGroup。 第一个通常称为“boss”,接受传入连接。 第二个通常称为“worker”,当“boss”接受连接并且向“worker”注册接受连接,则“worker”处理所接受连接的流量。 使用多少个线程以及如何将它们映射到创建的通道取决于EventLoopGroup实现,甚至可以通过构造函数进行配置。

  2. ServerBootstrap是一个用于设置服务器的助手类。 您可以直接使用通道设置服务器。 但是,请注意,这是一个冗长的过程,在大多数情况下不需要这样做。

  3. 在这里,我们指定使用NioServerSocketChannel类,该类用于实例化新的通道以接受传入连接。

  4. 此处指定的处理程序将始终由新接受的通道计算。 ChannelInitializer是一个特殊的处理程序,用于帮助用户配置新的通道。 很可能要通过添加一些处理程序(例如DiscardServerHandler)来配置新通道的ChannelPipeline来实现您的网络应用程序。 随着应用程序变得复杂,可能会向管道中添加更多处理程序,并最终将此匿名类提取到顶级类中。

  5. 还可以设置指定Channel实现的参数。这里编写的是一个TCP/IP服务器,所以我们允许设置套接字选项,如tcpNoDelaykeepAlive。 请参阅ChannelOption的apidocs和指定的ChannelConfig实现,以了解关于ChannelOptions

  6. 你注意到option()childOption()没有? option()用于接受传入连接的NioServerSocketChannelchildOption()用于由父ServerChannel接受的通道,在这个示例中为NioServerSocketChannel

  7. 现在准备好了。剩下的是绑定到端口和启动服务器。 这里,我们绑定到机器中所有NIC(网络接口卡)的端口:8080。 现在可以根据需要多次调用bind()方法(使用不同的绑定地址)。
    恭喜!这就完成了一个基于 Netty 的第一个服务器。

查看接收的数据

现在我们已经编写了第一个服务器,还需要测试它是否真的有效地运行工作。测试它的最简单的方法是使用telnet命令。 例如,可以在命令行中输入telnet localhost 8080并键入内容。

但是,能验证服务器工作正常吗? 其实不能真正知道,因为它是一个”丢弃“(什么也不处理)服务器。所以发送什么请求根本不会得到任何反应。 为了证明它是真的在运行工作,我们还修改一点服务器端上的代码 - 打印它收到了什么东西。

前面我们已经知道,只要接收到数据,就调用channelRead()方法。现在把一些代码放到DiscardServerHandlerchannelRead()方法中,如下所示:

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    ByteBuf in = (ByteBuf) msg;
    try {
        while (in.isReadable()) { // (1)
            System.out.print((char) in.readByte());
            System.out.flush();
        }
    } finally {
        ReferenceCountUtil.release(msg); // (2)
    }
    // 或者直接打印
    System.out.println("Yes, A new client in = " + ctx.name());
}


这个低效的循环实际上可以简化为:

System.out.println(in.toString(io.netty.util.CharsetUtil.US_ASCII))


或者,可以在这里写上:in.release()

运行 DiscardServer ,输出结果如下 -

三月 01, 2017 00:02:07 上午 io.netty.handler.logging.LoggingHandler channelRegistered
信息: [id: 0x4f99602f] REGISTERED
三月 01, 2017 00:02:07 上午 io.netty.handler.logging.LoggingHandler bind
信息: [id: 0x4f99602f] BIND: 0.0.0.0/0.0.0.0:8080
三月 01, 2017 00:02:07 上午 io.netty.handler.logging.LoggingHandler channelActive
信息: [id: 0x4f99602f, L:/0:0:0:0:0:0:0:0:8080] ACTIVE


如果再次运行telnet localhost 8080命令,将会看到服务器打印接收到的内容。

三月 01, 2017 2:14:14 上午 io.netty.handler.logging.LoggingHandler channelRegistered
信息: [id: 0xd63646da] REGISTERED
三月 01, 2017 2:14:14 上午 io.netty.handler.logging.LoggingHandler bind
信息: [id: 0xd63646da] BIND: 0.0.0.0/0.0.0.0:8080
三月 01, 2017 2:14:14 上午 io.netty.handler.logging.LoggingHandler channelActive
信息: [id: 0xd63646da, L:/0:0:0:0:0:0:0:0:8080] ACTIVE
三月 01, 2017 2:14:32 上午 io.netty.handler.logging.LoggingHandler channelRead
信息: [id: 0xd63646da, L:/0:0:0:0:0:0:0:0:8080] RECEIVED: [id: 0x452d2ebd, L:/0:0:0:0:0:0:0:1:8080 - R:/0:0:0:0:0:0:0:1:58248]
Yes, A new client in = DiscardServerHandler#0
Yes, A new client in = DiscardServerHandler#0


上面的输出证明,这个服务器程序是可以正常运行工作的。


2019-04-20 15:39

相关知识点

开源项目

知识点

相关教程

更多

Netty入门实例-时间服务器

Netty中服务器和客户端之间最大的和唯一的区别是使用了不同的Bootstrap和Channel实现

服务器端通过http请求区分是mobile还是pc

近来在开发一个基于手机上网的项目。遇到的第一件事情就是,该项目需要同时支持手机和PC。详细的说:当pc登陆上来,访问的是html;当手机登陆上来就是wml。    那我们怎么来判断呢?    入正题,下面说说我的解决办法。  /**  	 * 传入http请求的UserAgent  	 * 根据它判断是手机还是电脑发送过来的请求  	 * @ ...

comet基于长轮询实现服务器端反向推数据的方式

我们上节已经说了,基于长连接流实现服务器端反向推数据的方式有些浏览器不支持,原因在于状态码是在3的时候出来数据,对于很多浏览器而言并不支持在状态为处理中的时候传数据,​comet基于长轮询实现服务器端反向推数据的方式是每一次发送完信息,都会建立一个新的请求

comet基于长连接流实现服务器端反向推数据的方式

comet用来解决服务器端反向推数据,目前有多种方式可以实现。1.长连接:基于流的方式;基于长轮询的方式。2.websocket。这一节我们说基于长连接流的方式实现服务端反向推数据

silverLight 读取服务器端文本文件显示

请问下怎么用silverlight4 读取 服务器上的文本文件,然后显示出来下。最好给个demo,完整的代码。谢谢

xfire服务器端返回自定义对象中包含hashmap的问题

我在项目中遇到这样一个问题,webservice服务器段用xfire实现,暴露的service方法返回一个自定义的对象ClaimNodeTimes(后面会贴出代码),这个对象中一个属性refusePayReason是HashMap<string,string>类型。 然后我用xfire的eclipse插件生成的测试代码进行测试,发现ClaimNodeTimes对象中的基本类型属性都能取

web服务器介绍

这些服务器端的程序通常产生一个HTML的响应来让浏览器可以浏览

jsp程序在服务器上运行,如何将本地文件上传到ftp上

jsp程序在服务器上运行,如何将本地文件上传到ftp上  jsp页面传过去String fileString 应该不行的,它会执行服务器的那个地址,可能会报错  我感觉应该穿过去一个File file 文件的吧,不知道如何实现, 一般的网页都是如何实现的呢?  有些迷茫,忘爱好者给指点下!

Netty入门实例-使用POJO代替ByteBuf

使用TIME协议的客户端和服务器示例,让它们使用POJO来代替原来的ByteBuf。

搭建Git服务器-git入门教程

在远程仓库一节中,我们讲了远程仓库实际上和本地仓库没啥不同,纯粹为了7x24小时开机并交换大家的修改。 GitHub就是一个免费托管开源代码的远程仓库。但是对于某些视源代码如生命的商业公司来说,既不想公开源代码,又舍不得给GitHub交保护费,那就只能自己搭建一台Git服务器作为私有仓库使用。 搭建Git服务器需要准备一台运行Linux的机器,强烈推荐用Ubuntu或Debian,这样,通过几条简

服务器编程

我想问下,我想给自己写的服务器发请求,让他帮我做一些计算,然后再把结果返回给我,属于什么方面的编程啊?然后有什么比较好的框架或者资源介绍一下,想学习下这方面的知识,充点电!

全文搜索服务器solr之客户端 - solrj二次开发

Solrj已经是很强大的solr客户端了。它本身就包装了httpCliet,以完全对象的方式对solr进行交互。很小很好很强大。 不过在实际使用中,设置SolrQuery 的过程中,为了设置多个搜索条件和排序规则等等参数,我们往往会陷入并接字符串的地步,实在是很丑陋,不符合面向对象的思想。扩展性几乎为0,。基于这点,开发了一个小东西,我们只需要设置搜索对象,将对象扔给后台就可以了。 比如,我们搭建

利用微信的公众平台监控管理linux服务器[python实例]

利用 微信公众平台 查看管理linux服务器  前沿: 最近懒得做运维自动化了,看到市场部门的同事在调用公众平台的接口,感觉很有意思~就想试试用这个管理简单的管理linux主机,并推送告警信息。 打算这段时间主要再扩展下这个应用~     1 增加一些危险字符的判断,可以自己指定一个转义字符  2 针对post的数据进行加密下 比如 555ifconfig 这个555就是密码,在后端会有判断的,没

如何构建一个属于自己私有的git服务器

现在java大学网的项目也是基于git管理,当时建的git服务器是基于gitosis,本教程是基于gitolite构建一个属于自己私有的git服务器

Solr 搭建搜索服务器

Solr已经发布3.5版本了,同时它是基于Lucene 3.5的。我们在基于Solr进行二次开发之前,首先要搭建起一个搜索服务器,在熟悉Solr的基本功能的基础上,可以根据实际应用的需要进行个性化定制开发。因为Solr提供了一种插件机制,我们可以根据自己的需要进行定制,然后在Solr的配置文件中(solrconfig.xml)进行配置即可达到预期的要求。在Solr的发行包中给出了一个配置的例子,我

最新教程

更多

java线程状态详解(6种)

java线程类为:java.lang.Thread,其实现java.lang.Runnable接口。 线程在运行过程中有6种状态,分别如下: NEW:初始状态,线程被构建,但是还没有调用start()方法 RUNNABLE:运行状态,Java线程将操作系统中的就绪和运行两种状态统称为“运行状态” BLOCK:阻塞状态,表示线程阻塞

redis从库只读设置-redis集群管理

默认情况下redis数据库充当slave角色时是只读的不能进行写操作,如果写入,会提示以下错误:READONLY You can't write against a read only slave.  127.0.0.1:6382> set k3 111  (error) READONLY You can't write against a read only slave. 如果你要开启从库

Netty环境配置

netty是一个java事件驱动的网络通信框架,也就是一个jar包,只要在项目里引用即可。

Netty基于流的传输处理

​在TCP/IP的基于流的传输中,接收的数据被存储到套接字接收缓冲器中。不幸的是,基于流的传输的缓冲器不是分组的队列,而是字节的队列。 这意味着,即使将两个消息作为两个独立的数据包发送,操作系统也不会将它们视为两个消息,而只是一组字节(有点悲剧)。 因此,不能保证读的是您在远程定入的行数据

Netty入门实例-使用POJO代替ByteBuf

使用TIME协议的客户端和服务器示例,让它们使用POJO来代替原来的ByteBuf。

Netty入门实例-时间服务器

Netty中服务器和客户端之间最大的和唯一的区别是使用了不同的Bootstrap和Channel实现

Netty开发环境配置

最新版本的Netty 4.x和JDK 1.6及更高版本

电商平台数据库设计

电商平台数据库表设计:商品分类表、商品信息表、品牌表、商品属性表、商品属性扩展表、规格表、规格扩展表

HttpClient 上传文件

我们使用MultipartEntityBuilder创建一个HttpEntity。 当创建构建器时,添加一个二进制体 - 包含将要上传的文件以及一个文本正文。 接下来,使用RequestBuilder创建一个HTTP请求,并分配先前创建的HttpEntity。

MongoDB常用命令

查看当前使用的数据库    > db    test  切换数据库   > use foobar    switched to db foobar  插入文档    > post={"title":"领悟书生","content":"这是一个分享教程的网站","date":new

快速了解MongoDB【基本概念与体系结构】

什么是MongoDB MongoDB is a general purpose, document-based, distributed database built for modern application developers and for the cloud era. MongoDB是一个基于分布式文件存储的数据库。由C++语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。

windows系统安装MongoDB

安装 下载MongoDB的安装包:mongodb-win32-x86_64-2008plus-ssl-3.2.10-signed.msi,按照提示步骤安装即可。 安装完成后,软件会安装在C:\Program Files\MongoDB 目录中 我们要启动的服务程序就是C:\Program Files\MongoDB\Server\3.2\bin目录下的mongod.exe,为了方便我们每次启动,我

Spring boot整合MyBatis-Plus 之二:增删改查

基于上一篇springboot整合MyBatis-Plus之后,实现简单的增删改查 创建实体类 添加表注解TableName和主键注解TableId import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.annotations.TableName;
import com.baom

分布式ID生成器【snowflake雪花算法】

基于snowflake雪花算法分布式ID生成器 snowflake雪花算法分布式ID生成器几大特点: 41bit的时间戳可以支持该算法使用到2082年 10bit的工作机器id可以支持1024台机器 序列号支持1毫秒产生4096个自增序列id 整体上按照时间自增排序 整个分布式系统内不会产生ID碰撞 每秒能够产生26万ID左右 Twitter的 Snowflake分布式ID生成器的JAVA实现方案

Spring boot整合mybatis plus

快速了解mybatis plus 是对Mybatis框架的二次封装和扩展 纯正血统:完全继承原生 Mybatis 的所有特性 最少依赖:仅仅依赖Mybatis以及Mybatis-Spring 性能损耗小:启动即会自动注入基本CURD ,性能无损耗,直接面向对象操作 自动热加载:Mapper对应的xml可以热加载,大大减少重启Web服务器时间,提升开发效率 性能分析:自带Sql性能分析插件,开发测试