Nettyava服务端与客户端实现聊天(一)
介绍
- 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();
}
}
}
启动服务端与客户端
服务端控制台打印
客户端启动后建立连接请求并发送一条信息,服务端打印日志
以下是客户端发送消息,服务端打印接收到消息日志
客户端控制台打印
客户端接收到服务端的信息打印
为什么服务端和客户端会一直打印消息?
1.服务端自定义处理器重新read0方法,此方法代码是接收到消息请求后会对发送消息的通道(也就是创建连接的客户端初始化时会发送一个连接请求,及发送一条“客户端连接初始化”信息)
2.然后服务端执行此方法,控制台输出接收到的消息后,又对发送消息的通道进行回复消息,所以客户端也会收到服务端的消息
我们来看客户端的自定义处理器
1.①代码是建立连接请求执行,然后发送一条消息
2.服务端接收到消息后进行打印,然后也会写一条数据发送给客户端
3.客户端接收到消息进行打印,回再写一条数据进行回复
所以就会造成了死循环,而我在客户端启动类线程等待三秒结束连接请求