百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

如何通过Netty写出一个客户端和服务器?

wxin55 2024-10-26 16:35 11 浏览 0 评论

Netty是一个提供 asynchronous event-driven (异步事件驱动)的网络应用框架,是一个用以快速开发高性能、高可靠性协议的服务器和客户端。换句话说,Netty 是一个 NIO 客户端服务器框架,使用它可以快速简单地开发网络应用程序,比如服务器和客户端的协议。Netty 大大简化了网络程序的开发过程比如 TCP 和 UDP 的 socket 服务的开发。 “快速和简单”并不意味着应用程序会有难维护和性能低的问题,Netty 是一个精心设计的框架,它从许多协议的实现中吸收了很多的经验比如 FTP、SMTP、HTTP、许多二进制和基于文本的传统协议.因此,Netty 已经成功地找到一个方式,在不失灵活性的前提下来实现开发的简易性,高性能,稳定性。推荐了解黑马程序员java培训课程。

围绕Netty 的核心架构,通过简单的示例带你快速入门。当你读完本章节,你马上就可以用 Netty 写出一个客户端和服务器。

开始之前

在开始之前我们先说明下开发环境,我们使用netty-4.1.30这个版本,jdk使用1.8及以上版本。

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

先来个丢弃服务

世上最简单的协议不是'Hello, World!' 而是 DISCARD(丢弃)。这个协议将会丢掉任何收到的数据,而不响应。 为了实现 DISCARD 协议,你只需忽略所有收到的数据。让我们从 handler (处理器)的实现开始,handler 是由Netty 生成用来处理 I/O 事件的。

先创建一个处理器

package com.netty.first;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
* 处理服务端 channel.
*/
public class DiscardServerHandler extends ChannelInboundHandlerAdapter { // (1)
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) { // (2)
System.out.println(msg);
// 默默地丢弃收到的数据
((ByteBuf) msg).release(); // (3)
   }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4)
// 当出现异常就关闭连接
cause.printStackTrace();
ctx.close();
   }
}

(1) DiscardServerHandler 继承自 ChannelInboundHandlerAdapter ,这个类实现了ChannelInboundHandler接口,ChannelInboundHandler提供了许多事件处理的接口方法,然后你可以覆盖这些方法。现在仅仅只需要继承ChannelInboundHandlerAdapter 类而不是你自己去实现接口方法。

(2)这里我们覆盖了chanelRead() 事件处理方法。每当从客户端收到新的数据时,这个方法会在收到消息时被调用,这个例子中,收到的消息的类型是 ByteBuf。

(3)为了实现DISCARD协议,处理器不得不忽略所有接收到的消息。ByteBuf是一个引用计数对象,这个对象必须显示地调用release() 方法来释放。请记住处理器的职责是释放所有传递到处理器的引用计数对象。通常, channelRead() 方法的实现就像下面的这段代码:

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

(4)exceptionCaught()事件处理方法是当出现Throwable对象才会被调用,即当Netty由于IO错误或者处理器在处理事件时抛出的异常时。在大部分情况下,捕获的异常应该被记录下来并且把关联的 channel 给关闭掉。然而这个方法的处理方式会在遇到不同异常的情况下有不同的实现,比如你可能想在关闭连接之前发送一个错误码的响应消息。

编写服务端代码

目前为止一切都还不错,我们已经实现了DISCARD服务器的一半功能,剩下的需要编写一个main()方法来启动服务端的DiscardServerHandler 。

package com.netty.first;
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;
/**
* 丢弃任何进入的数据
*/
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)
            // 绑定端口,开始接收进来的连接
            ChannelFuture f = b.bind(port).sync(); // (7)
             // 等待服务器 socket 关闭 。
            // 在这个例子中,这不会发生,但你可以优雅地关闭你的服务器。
            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的实现用来处理不同的传输。在这个例子中我们实现了一个服务端的应用,因此会有2个 NioEventLoopGroup 会被使用。第一个经常被叫做‘boss’,用来接收进来的连接。第二个经常被叫做‘worker’,用来处理已经被接收的连接,一旦‘boss’接收到连接,就会把连接信息注册到‘worker’上。如何知道多少个线程已经被使用,如何映射到已经创建的 Channel上都需要依赖于 EventLoopGroup 的实现,并且可以通过构造函数来配置它们的关系。

2、ServerBootstrap是一个启动NIO服务的辅助启动类。你可以在这个服务中直接使用 Channel,但是这会是一个复杂的处理过程,在很多情况下你并不需要这样做。3、这里我们指定使用NioServerSocketChannel类来举例说明一个新的 Channel 如何接收进来的连接。

4、这里的事件处理类经常会被用来处理一个最近的已经接收的Channel。ChannelInitializer是一个特殊的处理类,他的目的是帮助使用者配置一个新的Channel。也许你想通过增加一些处理类比如DiscardServerHandler 来配置一个新的 Channel 或者其对应的ChannelPipeline 来实现你的网络程序。当你的程序变的复杂时,可能你会增加更多的处理类到 pipline 上,然后提取这些匿名类到最顶层的类上。

5、你可以设置这里指定的Channel实现的配置参数。我们正在写一个TCP/IP的服务端,因此我们被允许设置socket的参数选项比如tcpNoDelay 和 keepAlive。请参考 ChannelOption 和详细的ChannelConfig实现的接口文档以此可以对ChannelOption 的有一个大概的认识。

6、你关注过 option() 和 childOption() 吗?option() 是提供给NioServerSocketChannel 用来接收进来的连接。childOption() 是提供给由父管道 ServerChannel接收到的连接,在这个例子中也是 NioServerSocketChannel。

7、我们继续,剩下的就是绑定端口然后启动服务。这里我们在机器上绑定了机器所有网卡上的8080端口。当然现在你可以多次调用bind() 方法(基于不同绑定地址)。恭喜!你已经熟练地完成了第一个基于 Netty 的服务端程序。

查看收到的数据

现在我们已经编写出我们第一个服务端,我们需要测试一下它是否真的可以运行。最简单的测试方法是用telnet命令。例如,你可以在命令行上输入telnet localhost 8080或者其他类型参数。

在telnet终端中输入任意字符,服务端向控制台输出信息。证明服务端接收到客户端发送的消息了。但是我们并不能看到服务端接收到了什么东西,我们可以把channelRead方法改成如下内容:

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) { // (2)
    //System.out.println(msg);
    ByteBuf message = (ByteBuf) msg;
    System.out.println(message.toString(CharsetUtil.US_ASCII));
    // 默默地丢弃收到的数据
   ((ByteBuf) msg).release(); // (3)
}

这样控制台就可以看到客户端发送的数据了。

写个应答服务器

到目前为止,我们虽然接收到了数据,但没有做任何的响应。然而一个服务端通常会对一个请求作出响应。让我们学习怎样在ECHO协议的实现下编写一个响应消息给客户端,这个协议针对任何接收的数据都会返回一个响应。

和 discard server 唯一不同的是把在此之前我们实现的channelRead()方法,返回所有的数据替代打印接收数据到控制台上的逻辑。因此,需要把channelRead()方法修改如下:

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    ctx.write(msg); // (1)
    ctx.flush(); // (2)
}

(1)ChannelHandlerContext对象提供了许多操作,使你能够触发各种各样的I/O事件和操作。这里我们调用了write(Object) 方法来逐字地把接受到的消息写入。请注意不同于DISCARD的例子我们并没有释放接受到的消息,这是因为当写入的时候 Netty 已经帮我们释放了。

(2)ctx.write(Object)方法不会使消息写入到通道上,它被缓冲在了内部,你需要调用 ctx.flush() 方法来把缓冲区中数据强行输出。或者你可以用更简洁的cxt.writeAndFlush(msg)以达到同样的目的。

如果你再一次运行telnet命令,你会看到服务端会发回一个你已经发送的消息。

喜欢记得关注一下哦

相关推荐

ES6中 Promise的使用场景?(es6promise用法例子)

一、介绍Promise,译为承诺,是异步编程的一种解决方案,比传统的解决方案(回调函数)更加合理和更加强大在以往我们如果处理多层异步操作,我们往往会像下面那样编写我们的代码doSomething(f...

JavaScript 对 Promise 并发的处理方法

Promise对象代表一个未来的值,它有三种状态:pending待定,这是Promise的初始状态,它可能成功,也可能失败,前途未卜fulfilled已完成,这是一种成功的状态,此时可以获取...

Promise的九大方法(promise的实例方法)

1、promise.resolv静态方法Promise.resolve(value)可以认为是newPromise方法的语法糖,比如Promise.resolve(42)可以认为是以下代码的语...

360前端一面~面试题解析(360前端开发面试题)

1.组件库按需加载怎么做的,具体打包配了什么-按需加载实现:借助打包工具(如Webpack的require.context或ES模块动态导入),在使用组件时才引入对应的代码。例如在V...

前端面试-Promise 的 finally 怎么实现的?如何在工作中使用?

Promise的finally方法是一个非常有用的工具,它无论Promise是成功(fulfilled)还是失败(rejected)都会执行,且不改变Promise的最终结果。它的实现原...

最简单手写Promise,30行代码理解Promise核心原理和发布订阅模式

看了全网手写Promise的,大部分对于新手还是比较难理解的,其中几个比较难的点:状态还未改变时通过发布订阅模式去收集事件实例化的时候通过调用构造函数里传出来的方法去修改类里面的状态,这个叫Re...

前端分享-Promise可以中途取消啦(promise可以取消吗)

传统Promise就像一台需要手动组装的设备,每次使用都要重新接线。而Promise.withResolvers的出现,相当于给开发者发了一个智能遥控器,可以随时随地控制异步操作。它解决了三大...

手写 Promise(手写输入法 中文)

前言都2020年了,Promise大家肯定都在用了,但是估计很多人对其原理还是一知半解,今天就让我们一起实现一个符合PromiseA+规范的Promise。附PromiseA+规范地址...

什么是 Promise.allSettled()!新手老手都要会?

Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的pr...

前端面试-关于Promise解析与高频面试题示范

Promise是啥,直接上图:Promise就是处理异步函数的API,它可以包裹一个异步函数,在异步函数完成时抛出完成状态,让代码结束远古时无限回掉的窘境。配合async/await语法糖,可...

宇宙厂:为什么前端离不开 Promise.withResolvers() ?

大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发。1.为什么需要Promise.with...

Promise 新增了一个超实用的 API!

在JavaScript的世界里,Promise一直是处理异步操作的神器。而现在,随着ES2025的发布,Promise又迎来了一个超实用的新成员——Promise.try()!这个新方法简...

一次搞懂 Promise 异步处理(promise 异步顺序执行)

PromisePromise就像这个词的表面意识一样,表示一种承诺、许诺,会在后面给出一个结果,成功或者失败。现在已经成为了主流的异步编程的操作方式,写进了标准里面。状态Promise有且仅有...

Promise 核心机制详解(promise机制的实现原理)

一、Promise的核心状态机Promise本质上是一个状态机,其行为由内部状态严格管控。每个Promise实例在创建时处于Pending(等待)状态,此时异步操作尚未完成。当异步操作成功...

javascript——Promise(js实现promise)

1.PromiseES6开始支持,Promise对象用于一个异步操作的最终完成(包括成功和失败)及结果值的表示。简单说就是处理异步请求的。之所以叫Promise,就是我承诺,如果成功则怎么处理,失败怎...

取消回复欢迎 发表评论: