Netty异步NIO框架(一)java服务端与客户端实现聊天 websocket通道

简介: Netty异步NIO框架(一)java服务端与客户端实现聊天 websocket通道

介绍

Netty 是 一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。

Netty 是一个 NIO 客户端服务器框架,可以快速轻松地开发协议服务器和客户端等网络应用程序。它极大地简化和流线了网络编程,例如 TCP 和 UDP 套接字服务器。

“快速和简单”并不意味着生成的应用程序会受到可维护性或性能问题的影响。Netty 是根据从实现许多协议(如 FTP、SMTP、HTTP 以及各种二进制和基于文本的旧协议)中获得的经验精心设计的。因此,Netty 成功地找到了一种方法,可以在不妥协的情况下实现易于开发、性能、稳定性和灵活性。


设计

各种传输类型的统一 API - 阻塞和非阻塞套接字

基于灵活且可扩展的事件模型,允许明确分离关注点

高度可定制的线程模型——单线程、一个或多个线程池,例如 SEDA

真正的无连接数据报套接字支持(自 3.1 起)


Performance

更高的吞吐量,更低的延迟

更少的资源消耗

最小化不必要的内存拷贝

话不多说,上代码


java代码

maven依赖

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

服务端创建

自定义服务端处理类

package com.netty.server;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.util.UUID;
/**
 * @author wuzhenyong
 * ClassName:NettySocketServerHandler.java
 * date:2022-04-29 10:24
 * Description: 服务端自定义处理器
 */
public class NettySocketServerHandler extends SimpleChannelInboundHandler<Object> {
    /**
     * 收到消息请求调用此方法
     *
     * @param ctx ctx
     * @param msg 味精
     * @throws Exception 异常
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("接收到客户端发来消息:" + ctx.channel().remoteAddress() + ":" + msg);
        //返回数据
        ctx.channel().writeAndFlush("服务端发送数据:" + UUID.randomUUID());
    }
    /**
     * 出现异常调用此方法
     *
     * @param ctx   ctx
     * @param cause 导致
     * @throws Exception 异常
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        // 关闭通道
        ctx.close();
    }
    /**
     * 客户端创建连接请求调用此方法
     *
     * @param ctx ctx
     * @throws Exception 异常
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println(ctx.channel().remoteAddress()  + "已上线");
    }
}

自定义服务端初始化信息

package com.netty.server;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.util.UUID;
/**
 * @author wuzhenyong
 * ClassName:NettySocketServerHandler.java
 * date:2022-04-29 10:24
 * Description: 服务端自定义处理器
 */
public class NettySocketServerHandler extends SimpleChannelInboundHandler<Object> {
    /**
     * 收到消息请求调用此方法
     *
     * @param ctx ctx
     * @param msg 味精
     * @throws Exception 异常
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("接收到客户端发来消息:" + ctx.channel().remoteAddress() + ":" + msg);
        //返回数据
        ctx.channel().writeAndFlush("服务端发送数据:" + UUID.randomUUID());
    }
    /**
     * 出现异常调用此方法
     *
     * @param ctx   ctx
     * @param cause 导致
     * @throws Exception 异常
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        // 关闭通道
        ctx.close();
    }
    /**
     * 客户端创建连接请求调用此方法
     *
     * @param ctx ctx
     * @throws Exception 异常
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println(ctx.channel().remoteAddress()  + "已上线");
    }
}

服务端启动类

package com.netty.server;
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.nio.NioServerSocketChannel;
import lombok.SneakyThrows;
/**
 * @author wuzhenyong
 * ClassName:NettySocketServer.java
 * date:2022-04-29 10:22
 * Description: 服务端
 */
public class NettySocketServer {
    public static void main(String[] args) throws  Exception {
        //定义线程组 EventLoopGroup为死循环
        //boss线程组一直在接收客户端发起的请求,但是不对请求做处理,boss会将接收到的请i交给worker线程组来处理
        //实际可以用一个线程组来做客户端的请求接收和处理两件事,但是不推荐
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            //启动类定义
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    //指定在服务端启动过程中的一些逻辑,通常情况下我们用不着这个方法
                    .handler(new ChannelInitializer<NioServerSocketChannel>() {
                        @Override
                        protected void initChannel(NioServerSocketChannel nioServerSocketChannel) throws Exception {
                            System.out.println("服务端启动中...");
                        }
                    })
                    //子处理器,自定义处理器
                    .childHandler(new NettySocketServerInitializer());
            //绑定端口
            ChannelFuture channelFuture = serverBootstrap.bind(8899).sync();
            System.out.println("服务器启动成功");
            channelFuture.channel().closeFuture().sync();
        } finally {
            //Netty提供的优雅关闭
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

启动服务端

E:\java\jdk1.8\bin\java.exe -javaagent:D:\idea\IntelliJIDEA2021.2.3\lib\idea_rt.jar=6526:D:\idea\IntelliJIDEA2021.2.3\bin -Dfile.encoding=UTF-8 -classpath E:\java\jdk1.8\jre\lib\charsets.jar;E:\java\jdk1.8\jre\lib\deploy.jar;E:\java\jdk1.8\jre\lib\ext\access-bridge-64.jar;E:\java\jdk1.8\jre\lib\ext\cldrdata.jar;E:\java\jdk1.8\jre\lib\ext\dnsns.jar;E:\java\jdk1.8\jre\lib\ext\jaccess.jar;E:\java\jdk1.8\jre\lib\ext\jfxrt.jar;E:\java\jdk1.8\jre\lib\ext\localedata.jar;E:\java\jdk1.8\jre\lib\ext\nashorn.jar;E:\java\jdk1.8\jre\lib\ext\sunec.jar;E:\java\jdk1.8\jre\lib\ext\sunjce_provider.jar;E:\java\jdk1.8\jre\lib\ext\sunmscapi.jar;E:\java\jdk1.8\jre\lib\ext\sunpkcs11.jar;E:\java\jdk1.8\jre\lib\ext\zipfs.jar;E:\java\jdk1.8\jre\lib\javaws.jar;E:\java\jdk1.8\jre\lib\jce.jar;E:\java\jdk1.8\jre\lib\jfr.jar;E:\java\jdk1.8\jre\lib\jfxswt.jar;E:\java\jdk1.8\jre\lib\jsse.jar;E:\java\jdk1.8\jre\lib\management-agent.jar;E:\java\jdk1.8\jre\lib\plugin.jar;E:\java\jdk1.8\jre\lib\resources.jar;E:\java\jdk1.8\jre\lib\rt.jar;F:\WorkTools\ws\parent\netty-websocket\target\classes;F:\Install\repository\org\projectlombok\lombok\1.18.12\lombok-1.18.12.jar;F:\Install\repository\com\alibaba\fastjson\1.2.69\fastjson-1.2.69.jar;F:\Install\repository\org\springframework\boot\spring-boot-starter-web\2.2.5.RELEASE\spring-boot-starter-web-2.2.5.RELEASE.jar;F:\Install\repository\org\springframework\boot\spring-boot-starter\2.2.5.RELEASE\spring-boot-starter-2.2.5.RELEASE.jar;F:\Install\repository\org\springframework\boot\spring-boot\2.2.5.RELEASE\spring-boot-2.2.5.RELEASE.jar;F:\Install\repository\org\springframework\boot\spring-boot-autoconfigure\2.2.5.RELEASE\spring-boot-autoconfigure-2.2.5.RELEASE.jar;F:\Install\repository\org\springframework\boot\spring-boot-starter-logging\2.2.5.RELEASE\spring-boot-starter-logging-2.2.5.RELEASE.jar;F:\Install\repository\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;F:\Install\repository\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;F:\Install\repository\org\apache\logging\log4j\log4j-to-slf4j\2.12.1\log4j-to-slf4j-2.12.1.jar;F:\Install\repository\org\apache\logging\log4j\log4j-api\2.12.1\log4j-api-2.12.1.jar;F:\Install\repository\org\slf4j\jul-to-slf4j\1.7.30\jul-to-slf4j-1.7.30.jar;F:\Install\repository\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;F:\Install\repository\org\yaml\snakeyaml\1.25\snakeyaml-1.25.jar;F:\Install\repository\org\springframework\boot\spring-boot-starter-json\2.2.5.RELEASE\spring-boot-starter-json-2.2.5.RELEASE.jar;F:\Install\repository\com\fasterxml\jackson\core\jackson-databind\2.10.2\jackson-databind-2.10.2.jar;F:\Install\repository\com\fasterxml\jackson\core\jackson-annotations\2.10.2\jackson-annotations-2.10.2.jar;F:\Install\repository\com\fasterxml\jackson\core\jackson-core\2.10.2\jackson-core-2.10.2.jar;F:\Install\repository\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.10.2\jackson-datatype-jdk8-2.10.2.jar;F:\Install\repository\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.10.2\jackson-datatype-jsr310-2.10.2.jar;F:\Install\repository\com\fasterxml\jackson\module\jackson-module-parameter-names\2.10.2\jackson-module-parameter-names-2.10.2.jar;F:\Install\repository\org\springframework\boot\spring-boot-starter-tomcat\2.2.5.RELEASE\spring-boot-starter-tomcat-2.2.5.RELEASE.jar;F:\Install\repository\org\apache\tomcat\embed\tomcat-embed-core\9.0.31\tomcat-embed-core-9.0.31.jar;F:\Install\repository\org\apache\tomcat\embed\tomcat-embed-el\9.0.31\tomcat-embed-el-9.0.31.jar;F:\Install\repository\org\apache\tomcat\embed\tomcat-embed-websocket\9.0.31\tomcat-embed-websocket-9.0.31.jar;F:\Install\repository\org\springframework\boot\spring-boot-starter-validation\2.2.5.RELEASE\spring-boot-starter-validation-2.2.5.RELEASE.jar;F:\Install\repository\jakarta\validation\jakarta.validation-api\2.0.2\jakarta.validation-api-2.0.2.jar;F:\Install\repository\org\hibernate\validator\hibernate-validator\6.0.18.Final\hibernate-validator-6.0.18.Final.jar;F:\Install\repository\org\jboss\logging\jboss-logging\3.4.1.Final\jboss-logging-3.4.1.Final.jar;F:\Install\repository\com\fasterxml\classmate\1.5.1\classmate-1.5.1.jar;F:\Install\repository\org\springframework\spring-web\5.2.4.RELEASE\spring-web-5.2.4.RELEASE.jar;F:\Install\repository\org\springframework\spring-beans\5.2.4.RELEASE\spring-beans-5.2.4.RELEASE.jar;F:\Install\repository\org\springframework\spring-webmvc\5.2.4.RELEASE\spring-webmvc-5.2.4.RELEASE.jar;F:\Install\repository\org\springframework\spring-aop\5.2.4.RELEASE\spring-aop-5.2.4.RELEASE.jar;F:\Install\repository\org\springframework\spring-context\5.2.4.RELEASE\spring-context-5.2.4.RELEASE.jar;F:\Install\repository\org\springframework\spring-expression\5.2.4.RELEASE\spring-expression-5.2.4.RELEASE.jar;F:\Install\repository\org\springframework\boot\spring-boot-starter-test\2.2.5.RELEASE\spring-boot-starter-test-2.2.5.RELEASE.jar;F:\Install\repository\org\springframework\boot\spring-boot-test\2.2.5.RELEASE\spring-boot-test-2.2.5.RELEASE.jar;F:\Install\repository\org\springframework\boot\spring-boot-test-autoconfigure\2.2.5.RELEASE\spring-boot-test-autoconfigure-2.2.5.RELEASE.jar;F:\Install\repository\com\jayway\jsonpath\json-path\2.4.0\json-path-2.4.0.jar;
............省略...........
服务端启动中...
服务器启动成功

客户端创建

自定义客户端处理器

package com.netty.client;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.time.LocalDateTime;
/**
 * @author wuzhenyong
 * ClassName:NettyClientHandler.java
 * date:2022-04-29 10:29
 * Description:
 */
public class NettyClientHandler extends SimpleChannelInboundHandler<Object> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("接收到客户端:" + ctx.channel().remoteAddress() + "发送消息:" + msg);
        ctx.writeAndFlush("客户端发送消息:" + "一个小浪吴啊 >>" + LocalDateTime.now());
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        // 关闭请求
        ctx.close();
    }
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush("客户端连接初始化");
    }
}

自定义客户端初始化信息

package com.netty.client;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
/**
 * @author wuzhenyong
 * ClassName:NettyClientInitializer.java
 * date:2022-04-29 10:28
 * Description:
 */
public class NettyClientInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        //声明管道
        ChannelPipeline pipeline = socketChannel.pipeline();
        //绑定自带的解码器,就是对二进制数据的解析工具,至于解码器构造方法的参数之后详细分析
        pipeline.addLast("lengthFieldBasedFrameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
        //编码器
        pipeline.addLast("lengthFieldPrepender", new LengthFieldPrepender(4));
        //由于涉及到服务端和客户端的字符串数据,需要绑定字符串的编解码
        pipeline.addLast("stringDecoder", new StringDecoder(CharsetUtil.UTF_8));
        pipeline.addLast("stringEncoder", new StringEncoder(CharsetUtil.UTF_8));
        //自定义处理器
        pipeline.addLast("myClientHandler", new NettyClientHandler());
    }
}

客户端启动类

package com.netty.client;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.SneakyThrows;
import java.util.Scanner;
/**
 * @author wuzhenyong
 * ClassName:NettySocketClient.java
 * date:2022-04-29 10:26
 * Description:
 */
public class NettySocketClient {
    public static void main(String[] args) throws  Exception  {
        //客户端只需要一个线程组
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        try {
            //声明客户端启动类
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class).handler(new NettyClientInitializer());
            ChannelFuture channelFuture = bootstrap.connect("localhost", 8899).sync();
            // 三秒后关闭连接请求
            Thread.sleep(1000 * 3);
            channelFuture.channel().close().sync();
        } finally {
            //优雅关闭
            eventLoopGroup.shutdownGracefully();
        }
    }
}

启动服务端与客户端

服务端控制台打印

客户端启动后建立连接请求并发送一条信息,服务端打印日志

03f5cf8907ca4bf18ca87d47a67422b3.png


以下是客户端发送消息,服务端打印接收到消息日志

2fe6febdf4db45e2bd08bce436ea026e.png

客户端控制台打印

客户端接收到服务端的信息打印

c32938cba9e041438a1204c0499295d0.png

为什么服务端和客户端会一直打印消息?

1.服务端自定义处理器重新read0方法,此方法代码是接收到消息请求后会对发送消息的通道(也就是创建连接的客户端初始化时会发送一个连接请求,及发送一条“客户端连接初始化”信息)

2.然后服务端执行此方法,控制台输出接收到的消息后,又对发送消息的通道进行回复消息,所以客户端也会收到服务端的消息

a9fd3708b134440fb4aa6bbe859df15b.png

我们来看客户端的自定义处理器

1.①代码是建立连接请求执行,然后发送一条消息

2.服务端接收到消息后进行打印,然后也会写一条数据发送给客户端

3.客户端接收到消息进行打印,回再写一条数据进行回复

所以就会造成了死循环,而我在客户端启动类线程等待三秒结束连接请求

eb6bd0deb2684ca2a5e2a773f2c298df.png








以上服务端与客户端的聊天就结束了。

后端使用netty websocket通道,前端使用websocket进行实现私聊或者群发消息敬请期待下一篇。

感谢浏览。


相关文章
|
7月前
|
设计模式
Lettuce的特性和内部实现问题之Netty NIO的性能优于BIO的问题如何解决
Lettuce的特性和内部实现问题之Netty NIO的性能优于BIO的问题如何解决
|
4月前
|
存储 Java API
Java实现导出多个excel表打包到zip文件中,供客户端另存为窗口下载
Java实现导出多个excel表打包到zip文件中,供客户端另存为窗口下载
262 4
|
6月前
|
JSON NoSQL Java
redis的java客户端的使用(Jedis、SpringDataRedis、SpringBoot整合redis、redisTemplate序列化及stringRedisTemplate序列化)
这篇文章介绍了在Java中使用Redis客户端的几种方法,包括Jedis、SpringDataRedis和SpringBoot整合Redis的操作。文章详细解释了Jedis的基本使用步骤,Jedis连接池的创建和使用,以及在SpringBoot项目中如何配置和使用RedisTemplate和StringRedisTemplate。此外,还探讨了RedisTemplate序列化的两种实践方案,包括默认的JDK序列化和自定义的JSON序列化,以及StringRedisTemplate的使用,它要求键和值都必须是String类型。
redis的java客户端的使用(Jedis、SpringDataRedis、SpringBoot整合redis、redisTemplate序列化及stringRedisTemplate序列化)
|
5月前
|
分布式计算 Java Hadoop
Hadoop-30 ZooKeeper集群 JavaAPI 客户端 POM Java操作ZK 监听节点 监听数据变化 创建节点 删除节点
Hadoop-30 ZooKeeper集群 JavaAPI 客户端 POM Java操作ZK 监听节点 监听数据变化 创建节点 删除节点
127 1
|
6月前
|
Java
Netty BIO/NIO/AIO介绍
Netty BIO/NIO/AIO介绍
|
7月前
|
Java
Java使用FileInputStream&&FileOutputStream模拟客户端向服务器端上传文件(单线程)
Java使用FileInputStream&&FileOutputStream模拟客户端向服务器端上传文件(单线程)
119 1
|
8月前
|
消息中间件 Java Kafka
Java 客户端访问kafka
Java 客户端访问kafka
69 9
|
7月前
|
网络协议 C# 开发者
WPF与Socket编程的完美邂逅:打造流畅网络通信体验——从客户端到服务器端,手把手教你实现基于Socket的实时数据交换
【8月更文挑战第31天】网络通信在现代应用中至关重要,Socket编程作为其实现基础,即便在主要用于桌面应用的Windows Presentation Foundation(WPF)中也发挥着重要作用。本文通过最佳实践,详细介绍如何在WPF应用中利用Socket实现网络通信,包括创建WPF项目、设计用户界面、实现Socket通信逻辑及搭建简单服务器端的全过程。具体步骤涵盖从UI设计到前后端交互的各个环节,并附有详尽示例代码,助力WPF开发者掌握这一关键技术,拓展应用程序的功能与实用性。
267 0
|
7月前
|
存储 网络协议 Java
【Netty 神奇之旅】Java NIO 基础全解析:从零开始玩转高效网络编程!
【8月更文挑战第24天】本文介绍了Java NIO,一种非阻塞I/O模型,极大提升了Java应用程序在网络通信中的性能。核心组件包括Buffer、Channel、Selector和SocketChannel。通过示例代码展示了如何使用Java NIO进行服务器与客户端通信。此外,还介绍了基于Java NIO的高性能网络框架Netty,以及如何用Netty构建TCP服务器和客户端。熟悉这些技术和概念对于开发高并发网络应用至关重要。
128 0
|
7月前
|
Java
Java模拟文件发送给服务器,服务器将文件转发给其他用户,并保存到服务器本地,其他用户可以接收,并保存到本地磁盘,支持各种文件格式,并解决通信中服务器怎么区分客户端发来的文件类型
Java模拟文件发送给服务器,服务器将文件转发给其他用户,并保存到服务器本地,其他用户可以接收,并保存到本地磁盘,支持各种文件格式,并解决通信中服务器怎么区分客户端发来的文件类型