1. 服务端
1.1 新建Spring Boot项目
Group : com.hahashou.netty
Artifact : server
Type : Maven
1.2 pom.xml信息
<?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.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.hahashou.netty</groupId>
<artifactId>server</artifactId>
<version>1.0-SNAPSHOT</version>
<name>server</name>
<description>Netty Server Project For Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.100.Final</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
1.3 修改application.properties为application.yml
server:
port: 32000
logging:
level:
com.hahashou.netty: info
1.4 增加logback-spring.xml
<?xml version="1.0" encoding="utf-8" ?>
<!-- 日志级别(低->高) : TRACE < DEBUG < INFO < WARN < ERROR < FATAL -->
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!-- 彩色日志依赖的渲染类 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
<conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
<!-- 基本属性配置, 更换项目时修改此处 -->
<property name="rootPath" value="server-logs"/>
<property name="applicationName" value="server"/>
<contextName>${applicationName}</contextName>
<!-- 控制台日志: 彩色渲染输出 -->
<property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- Info日志 -->
<appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${rootPath}/${applicationName}-info.log</file>
<encoder>
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50}-%msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<FileNamePattern>${rootPath}/${applicationName}-info.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<maxFileSize>50MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
</filter>
</appender>
<!-- Error日志 -->
<appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${rootPath}/${applicationName}-error.log</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<FileNamePattern>${rootPath}/${applicationName}-error.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<maxFileSize>50MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="INFO"/>
<appender-ref ref="ERROR"/>
</root>
<!-- 异步打印 -->
<appender name="FILE_ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="STDOUT"/>
<appender-ref ref="INFO"/>
<appender-ref ref="ERROR"/>
</appender>
</configuration>
1.5 新建config包, 放入4个类
package com.hahashou.netty.server.config;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import io.netty.util.concurrent.EventExecutorGroup;
import io.netty.util.concurrent.RejectedExecutionHandlers;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @description: Netty线程组
* @author: 哼唧兽
* @date: 9999/9/21
**/
@Configuration
public class EventLoopGroupConfig {
private int bossNum = 1;
private int workerNum = 4;
private int businessNum = 1;
private int maxPending = 100000;
/**
* TCP连接处理
* @return
*/
@Bean("bossGroup")
public NioEventLoopGroup bossGroup() {
return new NioEventLoopGroup(bossNum);
}
/**
* Socket数据读写
* @return
*/
@Bean("workerGroup")
public NioEventLoopGroup workerGroup() {
return new NioEventLoopGroup(workerNum);
}
/**
* Handler业务处理
* @return
*/
@Bean("businessGroup")
public EventExecutorGroup businessGroup() {
return new DefaultEventExecutorGroup(businessNum, new BusinessThreadFactory(),
maxPending, RejectedExecutionHandlers.reject());
}
static class BusinessThreadFactory implements ThreadFactory {
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
BusinessThreadFactory() {
SecurityManager securityManager = System.getSecurityManager();
group = (securityManager != null) ? securityManager.getThreadGroup() : Thread.currentThread().getThreadGroup();
namePrefix = "netty-server-";
}
@Override
public Thread newThread(Runnable runnable) {
Thread thread = new Thread(group, runnable, namePrefix + threadNumber.getAndIncrement(), 0);
if (thread.isDaemon()) {
thread.setDaemon(false);
}
if (thread.getPriority() != Thread.NORM_PRIORITY) {
thread.setPriority(Thread.NORM_PRIORITY);
}
return thread;
}
}
}
package com.hahashou.netty.server.config;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* @description:
* @author: 哼唧兽
* @date: 9999/9/21
**/
@Component
@ChannelHandler.Sharable
@Slf4j
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
/** 因为只有一个客户端 */
public static Channel CHANNEL;
@Override
public void channelActive(ChannelHandlerContext ctx) {
CHANNEL = ctx.channel();
String channelId = CHANNEL.id().asLongText();
log.info("有客户端连接, channelId : " + channelId);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) {
CHANNEL = null;
String channelId = ctx.channel().id().asLongText();
log.info("有客户端断开连接, channelId : {}", channelId);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
log.info("客户端的消息: {}", msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
CHANNEL = null;
String channelId = ctx.channel().id().asLongText();
log.error("有客户端发生异常, channelId : {}", channelId);
}
}
package com.hahashou.netty.server.config;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.concurrent.EventExecutorGroup;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.net.InetAddress;
import java.nio.charset.Charset;
/**
* @description: Netty-TCP服务
* @author: 哼唧兽
* @date: 9999/9/21
**/
@Component
@Slf4j
public class NettyServer implements ApplicationListener<ApplicationStartedEvent> {
/** 查看当前主机可用端口范围 cat /proc/sys/net/ipv4/ip_local_port_range
* 端口分为知名端口(0~1023)、注册端口(1024~49511)、动态/私有端口(49152~65535) */
public static int PORT = 35000;
@Resource
private NioEventLoopGroup bossGroup;
@Resource
private NioEventLoopGroup workerGroup;
@Resource
private EventExecutorGroup businessGroup;
@Resource
private NettyServerHandler nettyServerHandler;
@Override
public void onApplicationEvent(ApplicationStartedEvent event) {
createServer(bossGroup, workerGroup, businessGroup, nettyServerHandler, PORT);
}
public void createServer(NioEventLoopGroup bossGroup, NioEventLoopGroup workerGroup, EventExecutorGroup businessGroup,
NettyServerHandler nettyServerHandler, int port) {
try {
String hostAddress = InetAddress.getLocalHost().getHostAddress();
log.info("本机IP: {}", hostAddress);
ServerBootstrap serverBootstrap = new ServerBootstrap();
ChannelFuture channelFuture = serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new StringDecoder(Charset.forName("UTF-8")));
pipeline.addLast(new StringEncoder(Charset.forName("UTF-8")));
pipeline.addLast(businessGroup, nettyServerHandler);
}
})
// 服务端可连接队列数,对应TCP/IP协议listen函数中backlog参数
.option(ChannelOption.SO_BACKLOG, 1024)
.childOption(ChannelOption.TCP_NODELAY, true)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.bind(hostAddress, port)
.sync();
if (channelFuture.isSuccess()) {
log.info("TCP服务启动成功, port={}", port);
}
channelFuture.channel().closeFuture().sync();
} catch (Exception exception) {
log.error("TCP服务启动失败: {}", exception.getMessage());
}
}
@PreDestroy
public void destroy() {
bossGroup.shutdownGracefully().syncUninterruptibly();
workerGroup.shutdownGracefully().syncUninterruptibly();
log.info("TCP服务关闭成功");
}
}
package com.hahashou.netty.server.config;
import lombok.Data;
/**
* @description:
* @author: 哼唧兽
* @date: 9999/9/21
**/
@Data
public class Message {
private String message;
}
1.6 新建controller包, 放入1个类
package com.hahashou.netty.server.controller;
import com.hahashou.netty.server.config.Message;
import com.hahashou.netty.server.config.NettyServerHandler;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @description:
* @author: 哼唧兽
* @date: 9999/9/21
**/
@RestController
@RequestMapping("/server")
@Slf4j
public class ServerController {
@PostMapping("/send")
public String send(@RequestBody Message dto) {
Channel channel = NettyServerHandler.CHANNEL;
if (channel == null) {
return "无客户端连接";
}
channel.writeAndFlush(Unpooled.copiedBuffer(dto.getMessage(), CharsetUtil.UTF_8));
return "success";
}
}
1.7 启动服务端, 测试
2. 客户端
2.1 新建Spring Boot项目
Group : com.hahashou.netty
Artifact : client
Type : Maven
2.2 pom.xml信息
<?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.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.hahashou.netty</groupId>
<artifactId>client</artifactId>
<version>1.0-SNAPSHOT</version>
<name>client</name>
<description>Netty Client Project For Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.100.Final</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
2.3 修改application.properties为application.yml
server:
port: 32001
logging:
level:
com.hahashou.netty: info
2.4 增加logback-spring.xml
<?xml version="1.0" encoding="utf-8" ?>
<!-- 日志级别(低->高) : TRACE < DEBUG < INFO < WARN < ERROR < FATAL -->
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!-- 彩色日志依赖的渲染类 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
<conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
<!-- 基本属性配置, 更换项目时修改此处 -->
<property name="rootPath" value="client-logs"/>
<property name="applicationName" value="client"/>
<contextName>${applicationName}</contextName>
<!-- 控制台日志: 彩色渲染输出 -->
<property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- Info日志 -->
<appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${rootPath}/${applicationName}-info.log</file>
<encoder>
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50}-%msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<FileNamePattern>${rootPath}/${applicationName}-info.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<maxFileSize>50MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
</filter>
</appender>
<!-- Error日志 -->
<appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${rootPath}/${applicationName}-error.log</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<FileNamePattern>${rootPath}/${applicationName}-error.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
<maxFileSize>50MB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="INFO"/>
<appender-ref ref="ERROR"/>
</root>
<!-- 异步打印 -->
<appender name="FILE_ASYNC" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="STDOUT"/>
<appender-ref ref="INFO"/>
<appender-ref ref="ERROR"/>
</appender>
</configuration>
2.5 新建config包, 放入4个类
package com.hahashou.netty.client.config;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import io.netty.util.concurrent.EventExecutorGroup;
import io.netty.util.concurrent.RejectedExecutionHandlers;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @description: Netty线程组
* @author: 哼唧兽
* @date: 9999/9/21
**/
@Configuration
public class EventLoopGroupConfig {
private int workerNum = 4;
private int businessNum = 1;
private int maxPending = 100000;
@Bean("workerGroup")
public NioEventLoopGroup workerGroup() {
return new NioEventLoopGroup(workerNum);
}
/**
* Handler业务处理
* @return
*/
@Bean("businessGroup")
public EventExecutorGroup businessGroup() {
return new DefaultEventExecutorGroup(businessNum, new BusinessThreadFactory(), maxPending, RejectedExecutionHandlers.reject());
}
static class BusinessThreadFactory implements ThreadFactory {
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
BusinessThreadFactory() {
SecurityManager securityManager = System.getSecurityManager();
group = securityManager != null ? securityManager.getThreadGroup() : Thread.currentThread().getThreadGroup();
namePrefix = "client-thread-";
}
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
if (thread.isDaemon()) {
thread.setDaemon(false);
}
if (thread.getPriority() != Thread.NORM_PRIORITY) {
thread.setPriority(Thread.NORM_PRIORITY);
}
return thread;
}
}
}
package com.hahashou.netty.client.config;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* @description:
* @author: 哼唧兽
* @date: 9999/9/21
**/
@Component
@ChannelHandler.Sharable
@Slf4j
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
public static Channel SERVER;
@Override
public void channelActive(ChannelHandlerContext ctx) {
SERVER = ctx.channel();
}
@Override
public void channelInactive(ChannelHandlerContext ctx) {
SERVER = null;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
log.info("服务端的消息: {}", msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
SERVER = null;
}
}
package com.hahashou.netty.client.config;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.concurrent.EventExecutorGroup;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationStartedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.net.InetAddress;
import java.nio.charset.Charset;
/**
* @description: Netty-TCP服务
* @author: 哼唧兽
* @date: 9999/9/21
**/
@Component
@Slf4j
public class NettyClient implements ApplicationListener<ApplicationStartedEvent> {
public static int PORT = 35000;
@Resource
private NioEventLoopGroup workerGroup;
@Resource
private EventExecutorGroup businessGroup;
@Resource
private NettyClientHandler nettyClientHandler;
public static Channel CHANNEL;
@SneakyThrows
@Override
public void onApplicationEvent(ApplicationStartedEvent event) {
String host = InetAddress.getLocalHost().getHostAddress();
log.info("本机IP: {}", host);
createClient(workerGroup, businessGroup, nettyClientHandler, host, PORT);
}
public void createClient(NioEventLoopGroup workerGroup, EventExecutorGroup businessGroup,
NettyClientHandler nettyClientHandler, String host, int port) {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(workerGroup)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new StringDecoder(Charset.forName("UTF-8")));
pipeline.addLast(new StringEncoder(Charset.forName("UTF-8")));
pipeline.addLast(businessGroup, nettyClientHandler);
}});
try {
CHANNEL = bootstrap.connect(host, port).sync().channel();
} catch (InterruptedException exception) {
log.error("客户端中断异常 : {}", exception.getMessage());
}
}
@PreDestroy
public void destroy() {
workerGroup.shutdownGracefully().syncUninterruptibly();
log.info("客户端关闭成功");
}
}
package com.hahashou.netty.client.config;
import lombok.Data;
/**
* @description:
* @author: 哼唧兽
* @date: 9999/9/21
**/
@Data
public class Message {
private String message;
}
2.6 新建controller包, 放入1个类
package com.hahashou.netty.client.controller;
import com.hahashou.netty.client.config.Message;
import com.hahashou.netty.client.config.NettyClientHandler;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @description:
* @author: 哼唧兽
* @date: 9999/9/21
**/
@RestController
@RequestMapping("/client")
@Slf4j
public class ClientController {
@PostMapping("/send")
public String send(@RequestBody Message dto) {
Channel channel = NettyClientHandler.CHANNEL;
if (channel == null) {
return "服务端已下线";
}
channel.writeAndFlush(Unpooled.copiedBuffer(dto.getMessage(), CharsetUtil.UTF_8));
return "success";
}
}
2.7 启动客户端, 测试
2.8 服务端向客户端发消息
2.9 服务端停止, 客户端向服务端发消息