一、Springboot+Netty环境搭建
1、引入依赖:
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
2、application.yml配置
server:
port:8080
netty:
host: 127.0.0.1
port: 7397
3、配置服务端
(1)定义nettyServer组件,用于绑定端口,关闭通道,获取通道操作
@Component("nettyServer")
public class NettyServer {
private Logger logger = LoggerFactory.getLogger(NettyServer.class);
private final EventLoopGroup parentGroup = new NioEventLoopGroup();
private final EventLoopGroup childGroup = new NioEventLoopGroup();
private Channel channel;
/**
* 绑定端口
* @param address
* @return
*/
public ChannelFuture bind(InetSocketAddress address) {
ChannelFuture channelFuture = null;
try {
ServerBootstrap b = new ServerBootstrap();
b.group(parentGroup, childGroup)
.channel(NioServerSocketChannel.class) //非阻塞模式
.option(ChannelOption.SO_BACKLOG, 128)
.childHandler(new MyChannelInitializer());
channelFuture = b.bind(address).syncUninterruptibly();
channel = channelFuture.channel();
} catch (Exception e) {
logger.error(e.getMessage());
} finally {
if (null != channelFuture && channelFuture.isSuccess()) {
logger.info("netty server start done.");
} else {
logger.error("netty server start error.");
}
}
return channelFuture;
}
/**
* 销毁
*/
public void destroy() {
if (null == channel) return;
channel.close();
parentGroup.shutdownGracefully();
childGroup.shutdownGracefully();
}
/**
* 获取通道
* @return
*/
public Channel getChannel() {
return channel;
}
}
(2)创建继承通道初始化类的类,用于初始化通道
public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {
/**
* 初始化channel
* @param channel
* @throws Exception
*/
@Override
protected void initChannel(SocketChannel channel) throws Exception {
// 日志打印
channel.pipeline().addLast(new LoggingHandler(LogLevel.INFO));
// 基于换行符号
channel.pipeline().addLast(new LineBasedFrameDecoder(1024));
// 解码转String,注意调整自己的编码格式GBK、UTF-8
channel.pipeline().addLast(new StringDecoder(Charset.forName("GBK")));
// 解码转String,注意调整自己的编码格式GBK、UTF-8
channel.pipeline().addLast(new StringEncoder(Charset.forName("GBK")));
// 在管道中添加我们自己的接收数据实现方法
channel.pipeline().addLast(new MyServerHandler());
}
}
(3)定义一个类继承通道的拦截类 ,处理通道连接请求
public class MyServerHandler extends ChannelInboundHandlerAdapter {
private Logger logger = LoggerFactory.getLogger(MyServerHandler.class);
/**
* 当客户端主动链接服务端的链接后,这个通道就是活跃的了。也就是客户端与服务端建立了通信通道并且可以传输数据
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
SocketChannel channel = (SocketChannel) ctx.channel();
//通知客户端链接建立成功
String str = "通知客户端链接建立成功" + " " + new Date() + " " + channel.localAddress().getHostString() + "\r\n";
ctx.writeAndFlush(str);
}
/**
* 当客户端主动断开服务端的链接后,这个通道就是不活跃的。也就是说客户端与服务端的关闭了通信通道并且不可以传输数据
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
logger.info("客户端断开链接{}", ctx.channel().localAddress().toString());
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//接收msg消息{与上一章节相比,此处已经不需要自己进行解码}
logger.info(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " 服务端接收到消息:" + msg);
//通知客户端链消息发送成功
String str = "服务端收到:" + new Date() + " " + msg + "\r\n";
ctx.writeAndFlush(str);
}
/**
* 抓住异常,当发生异常的时候,可以做一些相应的处理,比如打印日志、关闭链接
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
logger.info("异常信息:\r\n" + cause.getMessage());
}
}
(4)最后调用,启动服务端
@SpringBootApplication
@ComponentScan("com.shu")
public class NettyApplication implements CommandLineRunner {
@Value("${netty.host}")
private String host;
@Value("${netty.port}")
private int port;
@Autowired
private NettyServer nettyServer;
public static void main(String[] args) {
SpringApplication.run(NettyApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
InetSocketAddress address = new InetSocketAddress(host, port);
ChannelFuture channelFuture = nettyServer.bind(address);
Runtime.getRuntime().addShutdownHook(new Thread(() -> nettyServer.destroy()));
channelFuture.channel().closeFuture().syncUninterruptibly();
}
}
4.配置客户端
public class ApiTest {
public static void main(String[] args) {
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(workerGroup);
b.channel(NioSocketChannel.class);
b.option(ChannelOption.AUTO_READ, true);
b.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
// 日志打印
channel.pipeline().addLast(new LoggingHandler(LogLevel.INFO));
// 基于换行符号
channel.pipeline().addLast(new LineBasedFrameDecoder(1024));
// 解码转String,注意调整自己的编码格式GBK、UTF-8
channel.pipeline().addLast(new StringDecoder(Charset.forName("GBK")));
// 解码转String,注意调整自己的编码格式GBK、UTF-8
channel.pipeline().addLast(new StringEncoder(Charset.forName("GBK")));
// 在管道中添加我们自己的接收数据实现方法
channel.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//接收msg消息{与上一章节相比,此处已经不需要自己进行解码}
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " 客户端接收到消息:" + msg);
}
});
}
});
ChannelFuture f = b.connect("127.0.0.1", 7397).sync();
System.out.println(" client start done");
//向服务端发送信息
f.channel().writeAndFlush("你好,SpringBoot启动的netty服务端,“我的结尾是一个换行符,用于传输半包粘包处理”\r\n");
f.channel().writeAndFlush("你好,SpringBoot启动的netty服务端,“我的结尾是一个换行符,用于传输半包粘包处理”\r\n");
f.channel().writeAndFlush("你好,SpringBoot启动的netty服务端,“我的结尾是一个换行符,用于传输半包粘包处理”\r\n");
f.channel().writeAndFlush("你好,SpringBoot启动的netty服务端,“我的结尾是一个换行符,用于传输半包粘包处理”\r\n");
f.channel().writeAndFlush("你好,SpringBoot启动的netty服务端,“我的结尾是一个换行符,用于传输半包粘包处理”\r\n");
f.channel().closeFuture().syncUninterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully();
}
}
}
二、 Netty实现简单的文件传输
1、定义两个实体,一个用于接收一个用于返回
@Data
public class FileResponse implements Serializable {
private long length;
private File file;
public FileResponse() {
}
public FileResponse(long length, File file) {
this.length = length;
this.file = file;
}
public long getLength() {
return length;
}
public File getFile() {
return file;
}
}
@Data
public class FileRequest implements Serializable {
private String fileName;
public FileRequest() {
}
public FileRequest(String fileName) {
this.fileName = fileName;
}
public String getFileName() {
return fileName;
}
}
2、 配置服务端
(1)定义配置连接的组件
@Component("fileServer")
public class FileServer {
private Logger logger = LoggerFactory.getLogger(FileServer.class);
/**
* 绑定端口
*
* @param port
* @return
*/
public void bind(int port) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap()
.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline()
.addLast(new ObjectEncoder())
.addLast(new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.weakCachingConcurrentResolver(null)))
.addLast(new FileServerHandler());
}
});
ChannelFuture future = bootstrap.bind(port).sync();
future.channel().closeFuture().sync();
System.out.println("服务端启动成功,端口:" + port);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
(2)继承通道处理器,自定义通道处理器
public class FileServerHandler extends ChannelInboundHandlerAdapter {
private static final String FILE_PATH = "D:\\coreconfig.txt";
private Logger logger = LoggerFactory.getLogger(FileServerHandler.class);
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
logger.info("channelActive");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
logger.info("channelRead");
if (msg instanceof FileRequest) {
FileRequest request = (FileRequest) msg;
if (request.getFileName().equals(FILE_PATH)) {
File file = new File(FILE_PATH);
if (file.exists()) {
RandomAccessFile raf = new RandomAccessFile(file, "r");
long length = raf.length();
FileResponse response = new FileResponse(length, file);
ctx.writeAndFlush(response);
ChannelFuture sendFileFuture = ctx.writeAndFlush(new DefaultFileRegion(raf.getChannel(), 0, length), ctx.newProgressivePromise());
sendFileFuture.addListener(new ChannelProgressiveFutureListener() {
@Override
public void operationComplete(ChannelProgressiveFuture future) throws Exception {
System.out.println("File transfer completed.");
raf.close();
}
@Override
public void operationProgressed(ChannelProgressiveFuture future, long progress, long total) throws Exception {
if (total < 0) {
System.err.println("File transfer progress: " + progress);
} else {
System.err.println("File transfer progress: " + progress + " / " + total);
}
}
});
} else {
System.err.println("File not found: " + FILE_PATH);
}
} else {
System.err.println("Invalid file name: " + request.getFileName());
}
}
}
}
3、配置客户端
(1)配置客户端连接信息:
public class FileClient {
private static final int PORT = 8080;
private static final String HOST = "localhost";
private Logger logger = LoggerFactory.getLogger(FileClient.class);
private final EventLoopGroup parentGroup = new NioEventLoopGroup();
/**
* 连接服务端
*/
public void connect() {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap()
.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline()
.addLast(new ObjectEncoder())
.addLast(new ObjectDecoder(ClassResolvers.weakCachingConcurrentResolver(null)))
.addLast(new FileClientHandler());
}
});
ChannelFuture future = bootstrap.connect(HOST, PORT).sync();
future.channel().closeFuture().sync();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
group.shutdownGracefully();
}
}
}
(2) 定义客户端连接拦截器
public class FileClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
FileRequest request = new FileRequest("D:\\coreconfig.txt");
ctx.writeAndFlush(request);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof FileResponse) {
FileResponse response = (FileResponse) msg;
File file = response.getFile();
long fileLength = response.getLength();
FileOutputStream fos = new FileOutputStream(file);
FileChannel channel = fos.getChannel();
// channel.transferFrom(channel, 0, fileLength);
System.out.println("File " + file.getName() + " received.");
} else {
System.err.println("Invalid response type: " + msg.getClass());
}
}
}
转载:Netty+SpringBoot整合(一)-CSDN博客