Netty从零开始(二)

14.Netty框架的C#实战使用 文章目录博客概述场景与实现C#服务器端实现C#客户端的写法 博客概述 我是java线的工程师,但是技术栈有点全栈的意思。在某次项目中需要用到c#的socket通讯,查询之后惊喜的发现c#也有netty框架。dotnetty,github托管地址:https://github.com/Azure/DotNetty/blob/dev/examples/SecureChat.Client/Program... 阅读详情

我们继续昨天的点,学习Netty。

写一个时间客户端

与DISCARD和ECHO服务器不同,我们需要TIME协议的客户端,因为人类无法将32位二进制数据转换为日历上的日期。在本节中,我们将讨论如何确保服务器正常工作,并学习如何使用Netty编写客户端。

 

Netty中服务器和客户端之间最大和唯一的区别是使用了不同的Bootstrap和Channel实现。请看下面的代码:

package netty_beginner;

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

/**
 * Created by moon on 2017/4/6.
 */
public class TimeClient {
    public static void main(String[] args) {
        String host = args[0];
        int port = Integer.parseInt(args[1]);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            Bootstrap b = new Bootstrap(); // (1)
            b.group(workerGroup); // (2)
            b.channel(NioSocketChannel.class); // (3)
            b.option(ChannelOption.SO_KEEPALIVE, true); // (4)
            b.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) throws Exception {
                    ch.pipeline().addLast(new TimeClientHandler());
                }
            });

            // Start the client.
            ChannelFuture f = b.connect(host, port).sync(); // (5)

            // Wait until the connection is closed.
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
        }


    }
}

 

  1. Bootstrap类似于ServerBootstrap,但是它用于非服务器通道,如客户端或无连接通道。
  2. 如果只指定一个EventLoopGroup,它将同时用作一个老板组和一个工人组。虽然老板的工作人员不用于客户端。
  3. NioSocketChannel代替NioServerSocketChannel,用于创建客户端通道。
  4. 请注意,我们不使用childOption(),这与ServerBootstrap不同,因为客户端SocketChannel没有父级。
  5. 我们应该调用connect()方法而不是bind()方法。

正如你所看到的,与服务器端代码没有什么不同。ChannelHandler的实现如何?它应该从服务器接收一个32位整数,将其翻译成可读的格式,打印翻译时间,并关闭连接:

package netty_beginner;

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

import java.util.Date;

/**
 * Created by moon on 2017/4/6.
 */
public class TimeClientHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//        super.channelRead(ctx, msg);
        ByteBuf m = (ByteBuf) msg; // (1)
        try {
            long currentTimeMills = (m.readUnsignedInt() - 2208988800L) * 1000L;
            System.out.println(new Date(currentTimeMills));
            ctx.close();
        } finally {
            m.release();
        }

    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//        super.exceptionCaught(ctx, cause);
        cause.printStackTrace();
        ctx.close();
    }
}

 

  1. 在TCP / IP中,Netty将从对等体发送的数据读入ByteBuf。

它看起来很简单,与服务器端的示例没有任何不同。但是,这个处理程序有时会拒绝工作并抛出IndexOutOfBoundsException异常。我们会在下一节讨论为什么会发生这种情况。

 

处理基于流的运输

套接字缓冲区的一个小小注意事项

在诸如TCP / IP的基于流的传输中,接收的数据被存储到套接字接收缓冲器中。不幸的是,基于流的传输的缓冲区不是数据包的队列,而是字节队列。这意味着,即使您发送两个消息作为两个独立数据包,操作系统也不会将它们视为两个消息,而只是一堆字节。因此,您无法保证您所读取的内容正是您远程对等人写的内容。例如,假设操作系统的TCP / IP堆栈已经收到三个数据包:

由于基于流的协议的这种一般属性,在应用程序中以下列分片形式读取它们的可能性很大:

因此,无论服务器端或客户端如何,接收部分都应将接收的数据进行碎片整理为一个或多个可以被应用逻辑轻松识别的有意义的框架。在上述示例的情况下,接收到的数据应该如下所示:

第一个解决方案

现在让我们回到TIME客户端的例子。我们在这里也有同样的问题。32位整数是非常少量的数据,它不太可能经常碎片化。然而,问题是它可以分散,碎片化的可能性会随着流量的增加而增加。

简单的解决方案是创建内部累积缓冲区,并等待所有4个字节都被接收到内部缓冲区。以下是修改的TimeClientHandler实现,可以解决问题:

  1. ChannelHandler有两个生命周期监听器方法:handlerAdded()和handlerRemoved()。只要不长时间阻止,您可以执行任意初始化或非初始化任务。
  2. 首先,所有收到的数据应该被累积到buf。
  3. 然后,处理程序必须检查buf是否有足够的数据,在此示例中为4个字节,并转到实际的业务逻辑。否则,Netty会在更多数据到达时再次调用channelRead()方法,最终所有4个字节都将被累积。

第二个解决方案

虽然第一个解决方案已经解决了TIME客户端的问题,但修改后的处理程序看起来并不那么干净。想象一个更复杂的协议,由多个字段组成,如可变长度字段。您的ChannelInboundHandler实现将很快变得不可靠。

您可能已经注意到,您可以向ChannelPipeline添加多个ChannelHandler,因此,您可以将一个单一的ChannelHandler拆分成多个模块化的,以减少应用程序的复杂性。例如,您可以将TimeClientHandler拆分为两个处理程序:

  • 处理碎片问题的TimeDecoder
  • 初始简单版本的TimeClientHandler

幸运的是,Netty提供了一个可扩展的类,可帮助您编写开箱即用的第一个处理程序:

package netty_beginner;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;

import java.util.List;

/**
 * Created by moon on 2017/4/6.
 */
public class TimeDecoder extends ByteToMessageDecoder { // (1)
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) { // (2)
        if (in.readableBytes() < 4) {
            return; // (3)
        }

        out.add(in.readBytes(4)); // (4)
    }
}

 

 

  1. ByteToMessageDecoder是ChannelInboundHandler的一个实现,它可以很容易地解决分片问题。
  2. 当接收到新数据时,ByteToMessageDecoder会在内部维护的累积缓冲区中调用decode()方法。
  3. decode()可以决定在累积缓冲区中没有足够数据的地方添加任何东西。当接收到更多数据时,ByteToMessageDecoder将再次调用decode()。
  4. 如果decode()将一个对象添加到out中,则表示解码器成功解码了消息。ByteToMessageDecoder将丢弃累积缓冲区的读取部分。请记住,您不需要解码多条消息。 ByteToMessageDecoder将继续调用decode()方法,直到它不添加任何内容。

现在我们有另一个处理程序插入ChannelPipeline,我们应该修改TimeClient中的ChannelInitializer实现:

b.handler(new ChannelInitializer<SocketChannel>() {
    @Override
    public void initChannel(SocketChannel ch) throws Exception {
        ch.pipeline().addLast(new TimeDecoder(),new TimeClientHandler());
    }
});

如果你是一个冒险的人,你可能想尝试ReplayingDecoder,它更加简化了解码器。但是,您需要参考API参考资料。

public class TimeDecoder extends ReplayingDecoder<Void> {
    @Override
    protected void decode(
            ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
        out.add(in.readBytes(4));
    }
}

 

此外,Netty还提供了开箱即用的解码器,使您能够轻松实现大多数协议,并帮助您避免一个不可维护的处理程序实现。有关更多详细示例,请参阅以下软件包:

Speaking in POJO instead of ByteBuf

 

我们迄今为止审查的所有示例都使用ByteBuf作为协议消息的主要数据结构。在本节中,我们将改进TIME协议客户端和服务器示例,以使用POJO而不是ByteBuf。

在您的ChannelHandlers中使用POJO的优势是显而易见的。通过分离从处理程序中提取ByteBuf信息的代码,你的处理程序变得更加可维护和可重用。在TIME客户端和服务器示例中,我们只读取一个32位整数,直接使用ByteBuf不是一个主要问题。但是,您将发现在实现真实世界的协议时需要进行分离。

首先,我们定义一个名为UnixTime的新类型。

package netty_beginner;

import java.util.Date;

/**
 * Created by moon on 2017/4/6.
 */
public class UnixTime {
    private final long value;

    public UnixTime() {
        this(System.currentTimeMillis() / 1000L + 2208988800L);
    }

    public UnixTime(long value) {
        this.value = value;
    }

    public long value() {
        return value;
    }

    @Override
    public String toString() {
        return new Date((value() - 2208988800L) * 1000L).toString();
    }
}

 

我们现在可以修改TimeDecoder来生成UnixTime而不是ByteBuf。

@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) { // (2)
    if (in.readableBytes() < 4) {
        return; // (3)
    }

    out.add(new UnixTime(in.readUnsignedInt()));
}

 

使用更新的解码器,TimeClientHandler不再使用ByteBuf:

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    UnixTime m = (UnixTime) msg;
    System.out.println(m);
    ctx.close();
}

更简单优雅,对吧?同样的技术可以在服务器端应用。这次我们先来更新TimeServerHandler:

@Override
public void channelActive(final ChannelHandlerContext ctx) throws Exception {
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        ChannelFuture f = ctx.writeAndFlush(new UnixTime());
        f.addListener(ChannelFutureListener.CLOSE);
    }
}

 

现在,唯一缺少的部分是编码器,它是ChannelOutboundHandler的一个实现,它将UnixTime转换为ByteBuf。它比编写解码器简单得多,因为在编码消息时不需要处理数据包碎片和汇编。

package netty_beginner;

/**
 * Created by moon on 2017/4/6.
 */

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;package io.netty.example.time;

public class TimeEncoder extends ChannelOutboundHandlerAdapter {
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
        UnixTime m = (UnixTime) msg;
        ByteBuf encoded = ctx.alloc().buffer(4);
        encoded.writeInt((int) m.value());
        ctx.write(encoded, promise); // (1)
    }
}

 

  1. 这一行中有很多重要的事情。

首先,我们按原样通过原来的ChannelPromise,以便当编码数据实际写入电线时,Netty将其标记为成功或失败。

其次,我们没有调用ctx.flush()。有一个单独的处理方法void flush(ChannelHandlerContext ctx),用于覆盖flush()操作。

为了进一步简化,您可以使用MessageToByteEncoder:

public class TimeEncoder extends MessageToByteEncoder<UnixTime> {
    @Override
    protected void encode(ChannelHandlerContext ctx, UnixTime msg, ByteBuf out) {
        out.writeInt((int)msg.value());
    }
}

剩下的最后一个任务是在TimeServerHandler之前的服务器端的ChannelPipeline中插入一个TimeEncoder,并将其作为一个简单的练习。

关闭您的应用程序

关闭Netty应用程序通常就像关闭通过shutdownGracefully()创建的所有EventLoopGroups一样简单。当EventLoopGroup已完全终止,并且属于该组的所有通道都已关闭,他返回一个future来通知你。

 

概要

在本章中,我们快速浏览了Netty,并展示了如何在Netty上编写完整的网络应用程序。有关Netty的更多详细信息在即将到来的章节。我们还鼓励您查看io.netty.example包中的Netty示例。还请注意,社区一直在等待您的问题和想法,以帮助您,并根据您的反馈不断改进Netty及其文档。

 

好了 以上就是Netty官网给出的入门级别介绍。希望对大家有所帮助。

Netty框架的C#实现:一个高性能网络编程框架 通过这个简单的例子,我们展示了如何使用C#实现一个类似于Netty框架的高性能网络编程框架。当然,这只是一个初步的实现,如果需要更加复杂和健壮的功能,可以进一步拓展和优化代码。上述代码展示了一个简单的C#网络客户端,使用了TcpClient类来与服务器建立连接,并通过发送消息与服务器进行通信。首先,让我们创建一个名为"CSharpNetty"的C#解决方案,并添加一个名为"CSharpNettyServer"的项目。在这篇文章中,我们将介绍一个差不多等效的C#版本的Netty框架实现,并附上相应的源代码。 阅读详情

相关推荐

C#基于DotNetty实现高性能服务端应用

随着网络技术的飞速发展,实时通信成为了现代应用的标配。DotNetty,作为由Netty移植到.NET平台的高性能异步通信框架,吸引了无数开发者的眼球。DotNetty不仅继承了Netty的性能优势,还针对.NET环境进行了优化,使之能够更好地支持C#等语言。在这一章,我们将从宏观角度对DotNetty框架进行介绍。我们会从它的基本概念、设计思想、以及架构优势等方面展开。对于初学者而言,这将为他们搭建起 DotNetty的知识框架;

weixin_33205138的博客 900

C#使用Netty demo

【代码】C#使用Netty demo。

m0_50302965的博客 1081

C# Netty 客户端,服务器端包含接双向接收

C# Netty 客户端,服务器端 自己研究一才研究出来的与大家分享一下

《Netty》从零开始学netty(二)之NioEventLoopGroup

《Netty》从零开始学netty(二)之NioEventLoopGroup

Lanna_w的博客 308

Unity嵌套ScrollView滑动冲突解决方案:ExecuteEvents.Execute事件透传

在Unity UGUI中,竖向与横向ScrollView嵌套时出现滑动失效,本质是EventSystem事件分发机制的拦截限制。其原理在于PointerDown被外层捕获后,后续PointerMove默认不向下透传;ExecuteEvents.Execute提供了一条绕过常规冒泡流程的官方事件旁路,支持低延迟、方向自适应的精准透传。该技术具备高兼容性与零侵入特性,广泛应用于电商商品页、内容流卡片组、多级导航等需‘列表中嵌套轮播/标签’的真实场景,是解决UGUI嵌套滚动问题的核心工程实践方案。

weixin_33164198的博客 634

华为昇腾Atlas 200I DK A2实战:从开箱到运行首个AI应用

本文详细介绍了华为昇腾Atlas 200I DK A2开发板从开箱到运行首个AI应用的全过程。包括硬件准备、系统烧录、开发板连接方案、AI应用运行及性能优化技巧,帮助开发者快速上手部署AI项目,提升开发效率。

weixin_27038245的博客 593

从零开始学Netty(二)netty为什么成为了高人气的服务器框架

网络通信其实就是干这么几件事 1.建立连接 2.客户端发送数据 3.服务器接受到数据,然后根据数据内容进行处理,然后返回数据 4.不停重复2,3 5.关闭连接 我们为什么要用netty来搭建服务器呢?或者说netty为什么成为了高人气的服务器框架呢? 下面我们就来仔细研究一下吧。 在网络编程中,我们至少得有一台服务器,有一台客户端,两者建立连接,然后进行通信。 在早期,我们的服务器都是采用bio的...

晓晨的博客 457

Oracle OCP 19c 认证1Z0-083考试大纲(Oracle官方版)

Oracle Database: Managing Multitenant Architecture Ed 1 course

#真题在线# 1591

从零开始学Netty (二)-- 初始Netty核心组件 及 聊天室Demo

我们今天将通过一个Demo来理解Netty的几个主要组件。Netty的开发,主要是配置和实现各种Handler处理器,而其他是由Netty来替我们实现,所以我更愿意称其为“模板化”的开发模型。 “模板化”的Netty 首先看一个聊天室的Demo 服务端代码 public class DemoServer { public static void main(String[] args) throws InterruptedException { EventLoopGroup

zwt122755527的博客 794

libjpeg-turbo图片解码 VS opencv

本文对比了libjpeg-turbo和opencv在图像解码层面的耗时情况,及解码数据是否存在diff

优快云_WHB的博客 834

分数间隔均衡器的基本原理及应用(使用Matlab)

通过对接收信号进行加权求和,分数间隔均衡器可以抵消信号在传输过程中受到的频率响应失真。Matlab提供了丰富的工具和函数,可以方便地实现分数间隔均衡器,并进行性能评估。分数间隔均衡器(Fractional Interval Equalizer)是一种数字信号处理技术,用于均衡带通信道中的信号失真。分数间隔均衡器的基本原理是通过对接收信号进行加权求和,以抵消信号在传输过程中受到的频率响应失真。分数间隔均衡器在通信系统中具有广泛的应用。最后,我们通过比较均衡后的信号和发送数据的阈值来进行解调,并计算误码率。

2301_79325657的博客 684

从零开始实现简易版Netty(二) MyNetty pipeline流水线

在上一篇博客中lab1版本的MyNetty参考netty实现了一个极其精简的reactor模型。按照计划,lab2版本的MyNetty需要实现pipeline流水线,以支持不同的逻辑处理模块的解耦。由于本文属于系列博客,读者需要对之前的博客内容有所了解才能更好地理解本文内容。从零开始实现简易版Netty(一) MyNetty Reactor模式。

weixin_36585518的博客 70

C# 工业视觉性能极致优化:多线程推理池 + 对象复用 + 批量检测,速度提升 10 倍实战

set;} // 输入图像set;} // 异步返回结果set;} // 用于监控延迟性能优化不是魔法,而是对资源生命周期的精细管理和对硬件特性的充分利用。多线程池解决了 CPU/GPU 闲置问题。对象池解决了 GC 停顿问题。零拷贝解决了总线带宽瓶颈。严格的 Dispose解决了内存泄漏问题。这套架构已在多个高速锂电、3C 电子检测产线落地,轻松应对 200ms 甚至 50ms 的严苛节拍。记住,在工业领域,稳定性与速度同等重要。

这里只有干货:从 Modbus 通信到 MES 对接,从 YOLO 训练到工控机部署,带你搞定工业软件开发全流程。 307
上一篇: Netty学习(三)-Netty重要接口讲解
下一篇: 浅析MVVM与MVC设计模式两者之间的优缺点
hxpjava1
博客等级 码龄19年 1365粉丝 511原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值