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进行实现私聊或者群发消息敬请期待下一篇。

感谢浏览。


相关文章
|
1月前
|
JSON NoSQL Java
【Redis】2、Redis 的 Java 客户端(Jedis 和 SpringDataRedis)
【Redis】2、Redis 的 Java 客户端(Jedis 和 SpringDataRedis)
45 0
|
7天前
|
Java API Apache
ZooKeeper【基础 03】Java 客户端 Apache Curator 基础 API 使用举例(含源代码)
【4月更文挑战第11天】ZooKeeper【基础 03】Java 客户端 Apache Curator 基础 API 使用举例(含源代码)
24 11
|
16天前
|
网络协议 Java 物联网
Spring Boot与Netty打造TCP服务端(解决粘包问题)
Spring Boot与Netty打造TCP服务端(解决粘包问题)
26 1
|
1月前
|
前端开发 Java 数据库连接
探索Java中最常用的框架:Spring、Spring MVC、Spring Boot、MyBatis和Netty
探索Java中最常用的框架:Spring、Spring MVC、Spring Boot、MyBatis和Netty
|
1月前
|
监控 Java 网络安全
|
2月前
|
移动开发 编解码 网络协议
用Java的BIO和NIO、Netty来实现HTTP服务器(三) 用Netty实现
用Java的BIO和NIO、Netty来实现HTTP服务器(三) 用Netty实现
|
2月前
|
编解码 网络协议 Java
用Java的BIO和NIO、Netty实现HTTP服务器(一) BIO与绪论
用Java的BIO和NIO、Netty实现HTTP服务器(一) BIO与绪论
|
2月前
|
前端开发 Java 数据库连接
认识Java中最常用的框架:Spring、Spring MVC、Spring Boot、MyBatis和Netty
Spring框架 Spring是一个轻量级的开源框架,用于构建企业级应用。它提供了广泛的功能,包括依赖注入、面向切面编程、事务管理、消息传递等。Spring的核心思想是控制反转(IoC)和面向切面编程(AOP)。
78 3
|
2月前
|
安全 Java Go
springboot+netty化身Udp服务端,go化身客户端模拟设备实现指令联动
springboot+netty化身Udp服务端,go化身客户端模拟设备实现指令联动
70 0
|
3月前
|
测试技术
Netty4 websocket 开启服务端并设置IP和端口号
Netty4 websocket 开启服务端并设置IP和端口号
66 0