我们使用Netty来实现客户端和服务端之间的消息互相发送的功能,也就是类似聊天室的功能。
一、服务端
1.新建项目
2.pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.17</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>NettyServerDemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>NettyServerDemo</name>
<description>NettyServerDemo</description>
<properties>
<java.version>8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--netty-->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<builder>paketobuildpacks/builder-jammy-base:latest</builder>
</image>
</configuration>
</plugin>
</plugins>
</build>
</project>
3.application.properties配置
#netty监听端口
netty.port=8888
server.port=8080
4.创建服务端主服务类
package com.example.nettyserverdemo.netty;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* @author qx
* @date 2023/12/29
* @des Netty服务端主服务类
*/
@Component
public class NettyServer {
@Value("${netty.port}")
private int port;
public void start() {
// 用户监听及建立连接,并把每一个连接抽象成一个channel,最后将连接交给clientGroup处理
EventLoopGroup serverGroup = new NioEventLoopGroup();
EventLoopGroup clientGroup = new NioEventLoopGroup();
// 服务端启动时初始化操作
ServerBootstrap serverBootstrap = new ServerBootstrap();
// 将serverGroup和clientGroup注册到服务端的Channel上,注册一个服务端初始化器,初始化器中的initChannel()方法会在连接被注册到Channel后立即执行
serverBootstrap.group(serverGroup, clientGroup).channel(NioServerSocketChannel.class).childHandler(new NettyServerInitializer()).bind(port);
}
}
5.创建服务端初始化器
package com.example.nettyserverdemo.netty;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
/**
* @author qx
* @date 2023/12/29
* @des Netty服务端初始化器
*/
public class NettyServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) {
ChannelPipeline pipeline = socketChannel.pipeline();
// 使用字符串的编码解码器
pipeline.addLast("StringDecoder", new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast("StringEncoder", new StringEncoder(CharsetUtil.UTF_8));
// 添加自定义处理器
pipeline.addLast("handler", new NettyServerHandler());
}
}
6.创建自定义消息处理器
package com.example.nettyserverdemo.netty;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.util.Scanner;
/**
* @author qx
* @date 2023/12/29
* @des Netty服务端自定义处理器
*/
public class NettyServerHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
System.out.println("服务端接收消息来自:" + ctx.channel().remoteAddress() + ",消息内容:" + msg);
System.out.print("向客户端发送消息:");
String sendMsg = new Scanner(System.in).next();
ctx.channel().writeAndFlush(sendMsg);
}
}
7.自定义启动监听类
package com.example.nettyserverdemo.netty;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
/**
* @author qx
* @date 2023/12/29
* @des Netty服务端启动
*/
@Component
@Slf4j
public class NettyServerListener implements ApplicationRunner {
@Autowired
private NettyServer nettyServer;
@Override
public void run(ApplicationArguments args) {
nettyServer.start();
log.info("Netty服务端启动成功");
}
}
8.启动主程序,开启Netty服务端。
2023-12-29 10:10:12.683 INFO 10724 --- [ main] c.e.n.NettyServerDemoApplication : Starting NettyServerDemoApplication using Java 17.0.7 on DESKTOP-I8TUU6H with PID 10724 (D:\javaLearn\NettyServerDemo\target\classes started by qx in D:\javaLearn\NettyServerDemo)
2023-12-29 10:10:12.685 INFO 10724 --- [ main] c.e.n.NettyServerDemoApplication : No active profile set, falling back to 1 default profile: "default"
2023-12-29 10:10:13.166 INFO 10724 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2023-12-29 10:10:13.172 INFO 10724 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2023-12-29 10:10:13.172 INFO 10724 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.82]
2023-12-29 10:10:13.250 INFO 10724 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2023-12-29 10:10:13.250 INFO 10724 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 535 ms
2023-12-29 10:10:13.447 INFO 10724 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2023-12-29 10:10:13.454 INFO 10724 --- [ main] c.e.n.NettyServerDemoApplication : Started NettyServerDemoApplication in 1.007 seconds (JVM running for 1.47)
2023-12-29 10:10:13.603 INFO 10724 --- [ main] c.e.n.netty.NettyServerListener : Netty服务端启动成功
二、客户端
1.新建项目
2.pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.17</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>NettyClientDemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>NettyClientDemo</name>
<description>NettyClientDemo</description>
<properties>
<java.version>8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--netty-->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<builder>paketobuildpacks/builder-jammy-base:latest</builder>
</image>
</configuration>
</plugin>
</plugins>
</build>
</project>
3.application.properties配置
#netty监听端口
netty.port=8888
server.port=8081
4.创建客户端主服务类
package com.example.nettyclientdemo.netty;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* @author qx
* @date 2023/12/29
* @des Netty客户端主服务类
*/
@Component
public class NettyClient {
@Value("${netty.port}")
private int port;
public void start() {
// 客户端需要连接服务端即可
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
.handler(new NettyClientInitializer()).connect("127.0.0.1", port);
}
}
5.创建客户端初始化器
package com.example.nettyclientdemo.netty;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
/**
* @author qx
* @date 2023/12/29
* @des Netty客户端初始化器
*/
public class NettyClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) {
ChannelPipeline pipeline = socketChannel.pipeline();
// 使用字符串的编码解码器
pipeline.addLast("StringDecoder", new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast("StringEncoder", new StringEncoder(CharsetUtil.UTF_8));
// 添加自定义处理器
pipeline.addLast("handler", new NettyClientHandler());
}
}
6.创建客户端自定义消息处理器
package com.example.nettyclientdemo.netty;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.util.Scanner;
/**
* @author qx
* @date 2023/12/29
* @des Netty客户端自定义处理器
*/
public class NettyClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
System.out.println("客户端接收消息来自:" + ctx.channel().remoteAddress() + ",消息内容:" + msg);
System.out.print("向服务端发送消息:");
String sendMsg = new Scanner(System.in).next();
ctx.channel().writeAndFlush(sendMsg);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// 连接成功发送消息给服务端
ctx.writeAndFlush("第一条消息..");
}
}
7.自定义启动客户端监听类
package com.example.nettyclientdemo.netty;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
/**
* @author qx
* @date 2023/12/29
* @des Netty客户端启动
*/
@Component
@Slf4j
public class NettyClientListener implements ApplicationRunner {
@Autowired
private NettyClient nettyClient;
@Override
public void run(ApplicationArguments args) {
nettyClient.start();
log.info("Netty客户端启动成功");
}
}
8.启动客户端
2023-12-29 10:25:52.414 INFO 14740 --- [ main] c.e.n.NettyClientDemoApplication : Starting NettyClientDemoApplication using Java 1.8.0_291 on DESKTOP-I8TUU6H with PID 14740 (D:\javaLearn\NettyClientDemo\target\classes started by qx in D:\javaLearn\NettyClientDemo)
2023-12-29 10:25:52.416 INFO 14740 --- [ main] c.e.n.NettyClientDemoApplication : No active profile set, falling back to 1 default profile: "default"
2023-12-29 10:25:52.909 INFO 14740 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8081 (http)
2023-12-29 10:25:52.913 INFO 14740 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2023-12-29 10:25:52.913 INFO 14740 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.82]
2023-12-29 10:25:52.996 INFO 14740 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2023-12-29 10:25:52.996 INFO 14740 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 553 ms
2023-12-29 10:25:53.182 INFO 14740 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8081 (http) with context path ''
2023-12-29 10:25:53.188 INFO 14740 --- [ main] c.e.n.NettyClientDemoApplication : Started NettyClientDemoApplication in 1.01 seconds (JVM running for 1.5)
2023-12-29 10:25:53.352 INFO 14740 --- [ main] c.e.n.netty.NettyClientListener : Netty客户端启动成功
三、测试
分别启动服务端和客户端后,由于客户端连接成功就发送了一条数据给服务端,所以服务端接收到了客户端的数据。
服务端接收消息来自:/127.0.0.1:54997,消息内容:第一条消息..
并向客户端发送一条消息。
向客户端发送消息:我是服务端
这个时候我们去客户端的控制台查看日志发送接收到了来着服务端的消息。
客户端接收消息来自:/127.0.0.1:8888,消息内容:我是服务端
同时我们这个时候也可以像服务端继续发送消息,来实现客户端和服务端的聊天。
向服务端发送消息:你好
服务端接收消息来自:/127.0.0.1:54997,消息内容:你好