1.Netty传输工具

This commit is contained in:
JianGuo 2024-06-14 13:13:23 +08:00
commit 00d1dea5be
45 changed files with 1762 additions and 0 deletions

38
.gitignore vendored Normal file
View File

@ -0,0 +1,38 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

8
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -0,0 +1,40 @@
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.xiaoliu</groupId>
<artifactId>FileTransfer</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>FileTransferClient</artifactId>
<packaging>jar</packaging>
<name>FileTransferClient</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.java.version>21</project.java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.xiaoliu</groupId>
<artifactId>FileTransferCommon</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.formdev</groupId>
<artifactId>flatlaf</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>com.formdev</groupId>
<artifactId>flatlaf</artifactId>
<version>3.4.1</version>
<classifier>windows-x86_64</classifier>
<type>dll</type>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,120 @@
package com.xiaoliu;
import com.xiaoliu.codec.DecodeHandler;
import com.xiaoliu.codec.EncodeHandler;
import com.xiaoliu.handler.FilePacketClientHandler;
import com.xiaoliu.handler.FileSendClientHandler;
import com.xiaoliu.handler.LoginResponseHandler;
import com.xiaoliu.protocol.FilePacket;
import com.xiaoliu.protocol.request.LoginPacket;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.stream.ChunkedWriteHandler;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.util.concurrent.TimeUnit;
@Slf4j
public class Client {
private static final String HOST = System.getProperty("host", "127.0.0.1");
private static final int PORT = Integer.parseInt(System.getProperty("port", "8080"));
private static ChannelFuture future;
private static NioEventLoopGroup group;
public static void init() throws Exception{
init(HOST, PORT);
}
public static void init(String host, int port) throws Exception{
if(host == null || host.isEmpty()){
host = HOST;
}
if(port == 0){
port = PORT;
}
log.info("客户端启动开始...");
Bootstrap bootstrap = new Bootstrap();
group = new NioEventLoopGroup(2);
bootstrap.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
.option(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
//pipeline.addLast(new FileReceiveClientHandler());
pipeline.addLast(new FileSendClientHandler());
pipeline.addLast(new DecodeHandler());
pipeline.addLast(new EncodeHandler());
pipeline.addLast(new ChunkedWriteHandler());
pipeline.addLast(new LoginResponseHandler());
pipeline.addLast(new FilePacketClientHandler());
// pipeline.addLast(new MyClientHandler());
}
});
future = bootstrap.connect(host, port).sync();
if (future.isSuccess()) {
log.info("连接服务器成功");
Channel channel = future.channel();
joinCluster(channel);
} else {
log.info("连接服务器失败");
}
future.channel().closeFuture().sync();
}
private static void joinCluster(Channel channel){
LoginPacket loginPacket = new LoginPacket("node1");
channel.writeAndFlush(loginPacket);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public static ChannelFuture getFuture() {
if(future == null){
throw new RuntimeException("请先使用init方法连接到服务器...");
}
return future;
}
public static void send(File file){
ChannelFuture future1 = getFuture();
send(future1.channel(), file);
}
public static void send(Channel channel, File file) {
FilePacket filePacket = new FilePacket(file);
channel.writeAndFlush(filePacket);
}
public static void shutdown(){
if(future != null){
future.channel().close();
}
if(group != null){
group.shutdownGracefully(10, 30, TimeUnit.SECONDS);
}
}
}

View File

@ -0,0 +1,26 @@
package com.xiaoliu.console;
import com.xiaoliu.protocol.FilePacket;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.util.Scanner;
@Slf4j
public class SendFileConsole {
public static void exec(Channel channel) {
Scanner sc = new Scanner(System.in);
log.info("请输入文件路径:");
String path = sc.nextLine();
File file = new File(path);
log.info("文件存在吗:{}", file.exists());
log.info("文件大小:{}", file.length());
FilePacket filePacket = new FilePacket(file);
channel.writeAndFlush(filePacket);
}
}

View File

@ -0,0 +1,24 @@
package com.xiaoliu.handler;
import com.xiaoliu.protocol.FilePacket;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.io.FileOutputStream;
@Slf4j
public class FilePacketClientHandler extends SimpleChannelInboundHandler<FilePacket> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, FilePacket packet) throws Exception {
File file = packet.getFile();
log.info("receive file from server: {}", file.getName());
FileReceiveClientHandler.fileLength = file.length();
FileReceiveClientHandler.outputStream = new FileOutputStream(
new File("./client-receive-" + file.getName())
);
packet.setACK(packet.getACK() + 1);
ctx.writeAndFlush(packet);
}
}

View File

@ -0,0 +1,47 @@
package com.xiaoliu.handler;
import com.xiaoliu.codec.Codec;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;
import java.io.FileOutputStream;
import java.io.IOException;
@Slf4j
public class FileReceiveClientHandler extends ChannelInboundHandlerAdapter {
static FileOutputStream outputStream;
static long fileLength;
private static long readLength;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf = (ByteBuf) msg;
int type = byteBuf.getInt(0);
if (type != Codec.TYPE) {
readLength += byteBuf.readableBytes();
writeToFile(byteBuf);
sendComplete(readLength);
} else {
super.channelRead(ctx, msg);
}
}
private void writeToFile(ByteBuf byteBuf) throws IOException {
byte[] bytes = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(bytes);
outputStream.write(bytes);
byteBuf.release();
}
private void sendComplete(long readLength) throws IOException {
if (readLength >= fileLength) {
log.info("文件接收完成...");
outputStream.close();
}
}
}

View File

@ -0,0 +1,51 @@
package com.xiaoliu.handler;
import com.xiaoliu.codec.Codec;
import com.xiaoliu.protocol.FilePacket;
import com.xiaoliu.protocol.Packet;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.DefaultFileRegion;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
@Slf4j
@ChannelHandler.Sharable
public class FileSendClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf = (ByteBuf) msg;
int type = byteBuf.getInt(0);
if (type == Codec.TYPE) {
Packet packet = Codec.INSTANCE.decode(byteBuf);
if (packet instanceof FilePacket) {
FilePacket filePacket = (FilePacket) packet;
if (filePacket.getACK() != 0) {
writeAndFlushFileRegion(ctx, filePacket);
} else {
super.channelRead(ctx, packet);
}
} else {
super.channelRead(ctx, packet);
}
} else {
log.info("无法识别此类数据包");
}
}
private void writeAndFlushFileRegion(ChannelHandlerContext ctx, FilePacket packet) {
File file = packet.getFile();
DefaultFileRegion fileRegion = new DefaultFileRegion(file, 0, file.length());
ctx.writeAndFlush(fileRegion).addListener(future -> {
if (future.isSuccess()) {
log.info("{} 发送完成...", file.getName());
}
});
}
}

View File

@ -0,0 +1,17 @@
package com.xiaoliu.handler;
import com.xiaoliu.protocol.response.LoginResponsePacket;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import java.util.Date;
@ChannelHandler.Sharable
public class LoginResponseHandler extends SimpleChannelInboundHandler<LoginResponsePacket> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, LoginResponsePacket packet) throws Exception {
System.out.println(new Date() + " " + packet.getId() + " " + packet.getName() + " 登陆成功");
}
}

View File

@ -0,0 +1,30 @@
package com.xiaoliu.handler;
import com.xiaoliu.codec.Codec;
import com.xiaoliu.protocol.FilePacket;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.stream.ChunkedFile;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
@Slf4j
public class MyClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
log.info("msg : {}", msg.toString());
ByteBuf byteBuf = (ByteBuf) msg;
FilePacket filePacket = (FilePacket) Codec.INSTANCE.decode(byteBuf);
File file = filePacket.getFile();
log.info("prepared send: {}", file.getName());
Channel channel = ctx.channel();
channel.writeAndFlush(new ChunkedFile(filePacket.getFile()));
// channel.writeAndFlush(new DefaultFileRegion(file, 0, file.length()));
}
}

View File

@ -0,0 +1,185 @@
package com.xiaoliu.window;
import com.xiaoliu.Client;
import lombok.extern.slf4j.Slf4j;
import com.formdev.flatlaf.FlatDarculaLaf;
import javax.swing.*;
import java.awt.*;
import java.io.File;
@Slf4j
public class MainWindow extends JFrame {
private static JTextArea logTextArea;
private static JLabel statusLabel;
public MainWindow() {
// 设置窗口标题
setTitle("影像Netty客户端");
// 设置默认的关闭操作
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
// 设置窗口大小
setSize(500, 400);
// 居中显示窗口
setLocationRelativeTo(null);
//初始化状态组件
initStatusLabel();
// 初始化UI组件
initializeComponents();
}
private void initStatusLabel() {
statusLabel = new JLabel();
statusLabel.setForeground(Color.RED);
statusLabel.setText("未连接");
}
public void setStatusLabel(String text, Color color ){
if(statusLabel == null){
initStatusLabel();
}
statusLabel.setText(text);
statusLabel.setForeground(color);
}
private void initializeComponents() {
// 在这里添加你的组件初始化代码
// 创建一个JTextArea用于显示日志
logTextArea = new JTextArea();
logTextArea.setEditable(false); // 设置为不可编辑
logTextArea.setEnabled(false);
// 将JTextArea放入JScrollPane中以支持滚动
JScrollPane scrollPane = new JScrollPane(logTextArea);
// 将JScrollPane添加到窗口中
getContentPane().add(scrollPane, BorderLayout.CENTER);
JLabel adress = new JLabel();
adress.setText("地址:");
JTextField textField = new JTextField(20); // 参数20指定了文本框的列数
JButton sendButton = new JButton("连接");
JButton stopButton = new JButton("断开");
stopButton.setEnabled(false);
sendButton.addActionListener(e ->{
String text = textField.getText();
String[] split = text.split(":");
if(split.length < 2){
JOptionPane.showMessageDialog(this, "请输入正确地址", "输入错误", JOptionPane.ERROR_MESSAGE);
return;
}
String host = split[0];
String port = split[1];
setLogText("连接到: "+ host + ":" + port);
new Thread(() -> {
//新起一个县城去初始化netty
try {
sendButton.setEnabled(false);
setLogText("连接成功...");
setStatusLabel("已连接", Color.GREEN);
stopButton.setEnabled(true);
Client.init(host, Integer.parseInt(port));
} catch (Exception ex) {
setLogText("连接失败...");
stopButton.setEnabled(false);
setStatusLabel("已连接", Color.RED);
throw new RuntimeException(ex);
}
}).start();
});
JPanel northPanel = new JPanel();
northPanel.add(adress);
northPanel.add(textField);
northPanel.add(sendButton);
northPanel.add(statusLabel, BorderLayout.WEST);
getContentPane().add(northPanel, BorderLayout.NORTH);
JPanel bottomPanel = new JPanel();
// 示例添加一个按钮点击时向文本区域添加日志信息
JButton startButton = new JButton("发送");
startButton.addActionListener(e -> {
JFileChooser fileChooser = new JFileChooser();
fileChooser.setCurrentDirectory(new java.io.File(System.getProperty("user.home") + "/Desktop"));
// 显示文件选择对话框返回值为用户操作的结果
int result = fileChooser.showOpenDialog(this);
// 如果用户点击了"打开"按钮
if (result == JFileChooser.APPROVE_OPTION) {
// 获取选中的文件路径
String selectedFilePath = fileChooser.getSelectedFile().getPath();
File file = new File(selectedFilePath);
log.info("文件存在吗:{}", file.exists());
log.info("文件大小:{}", file.length());
new Thread(() -> {
//开一个新县城去泡发送程序
setLogText("文件发送开始: "+selectedFilePath);
Client.send(file);
}).start();
}
});
stopButton.addActionListener(e -> {
setLogText("连接中止...");
Client.shutdown();
setStatusLabel("未连接", Color.RED);
sendButton.setEnabled(true);
});
bottomPanel.add(startButton, BorderLayout.CENTER);
bottomPanel.add(stopButton, BorderLayout.CENTER);
getContentPane().add(bottomPanel, BorderLayout.SOUTH);
}
public static void main(String[] args) {
log.info("服务启动中...");
try {
UIManager.setLookAndFeel(new FlatDarculaLaf());
} catch (UnsupportedLookAndFeelException e) {
System.err.println("UnsupportedLookAndFeelException: " + e.getMessage());
}
SwingUtilities.invokeLater(() -> new MainWindow().setVisible(true));
}
public JTextArea getLogTextArea(){
if(logTextArea == null){
initLogTextArea();
}
return logTextArea;
}
public void setLogText(String text){
logTextArea.append(text + "\n");
}
public void initLogTextArea(){
// 在这里添加你的组件初始化代码
// 创建一个JTextArea用于显示日志
logTextArea = new JTextArea();
logTextArea.setEditable(false); // 设置为不可编辑
logTextArea.setEnabled(false);
// 将JTextArea放入JScrollPane中以支持滚动
JScrollPane scrollPane = new JScrollPane(logTextArea);
// 将JScrollPane添加到窗口中
getContentPane().add(scrollPane, BorderLayout.CENTER);
}
}

View File

@ -0,0 +1,14 @@
# ????????
org.slf4j.simpleLogger.defaultLogLevel=INFO
# ?????? class ????????????,?????? INFO
# org.slf4j.simpleLogger.log.com.baomidou.mybatisplus.generator=DEBUG
# ????
org.slf4j.simpleLogger.showDateTime=true
# ????
org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss
# ???
# org.slf4j.simpleLogger.showThreadName=true
# ???
org.slf4j.simpleLogger.showLogName=true
# ?????
#org.slf4j.simpleLogger.showShortLogName=false

View File

@ -0,0 +1,38 @@
package com.xiaoliu;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
/**
* Unit test for simple App.
*/
public class AppTest
extends TestCase
{
/**
* Create the test case
*
* @param testName name of the test case
*/
public AppTest( String testName )
{
super( testName );
}
/**
* @return the suite of tests being tested
*/
public static Test suite()
{
return new TestSuite( AppTest.class );
}
/**
* Rigourous Test :-)
*/
public void testApp()
{
assertTrue( true );
}
}

View File

@ -0,0 +1,36 @@
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.xiaoliu</groupId>
<artifactId>FileTransfer</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>FileTransferCommon</artifactId>
<packaging>jar</packaging>
<name>FileTransferCommon</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.java.version>21</project.java.version>
</properties>
<dependencies>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>${project.java.version}</source>
<target>${project.java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,58 @@
package com.xiaoliu.codec;
import com.xiaoliu.protocol.FilePacket;
import com.xiaoliu.protocol.Packet;
import com.xiaoliu.protocol.request.LoginPacket;
import com.xiaoliu.protocol.response.LoginResponsePacket;
import com.xiaoliu.protocol.serilizer.Serilizer;
import io.netty.buffer.ByteBuf;
import java.util.HashMap;
import java.util.Map;
import static com.xiaoliu.protocol.command.Command.*;
public class Codec {
public static final int TYPE = 0x12345678;
private final Map<Byte, Class<? extends Packet>> packetTypeMap;
public static Codec INSTANCE = new Codec();
private Codec() {
packetTypeMap = new HashMap<>();
packetTypeMap.put(FILE_PACKET, FilePacket.class);
packetTypeMap.put(LOGIN_PACKET_REQUEST, LoginPacket.class);
packetTypeMap.put(LOGIN_PACKET_RESPONSE, LoginResponsePacket.class);
}
public void encode(ByteBuf byteBuf, Packet packet) {
byte[] bytes = Serilizer.DEFAULT.serilize(packet);
byteBuf.writeInt(TYPE);
byteBuf.writeByte(packet.getCommand());
byteBuf.writeInt(bytes.length);
byteBuf.writeBytes(bytes);
// return byteBuf;
}
public Packet decode(ByteBuf byteBuf) {
byteBuf.readInt();
Byte command = byteBuf.readByte();
int len = byteBuf.readInt();
byte[] bytes = new byte[len];
byteBuf.readBytes(bytes);
byteBuf.release();
Class clazz = packetTypeMap.get(command);
if (clazz == null) {
throw new NullPointerException("解析失败,没有该类型的数据包");
}
return (Packet) Serilizer.DEFAULT.deSerilize(bytes, clazz);
}
}

View File

@ -0,0 +1,39 @@
package com.xiaoliu.codec;
import com.xiaoliu.protocol.Packet;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageCodec;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
@Slf4j
@ChannelHandler.Sharable
public class CodecHandler extends MessageToMessageCodec<ByteBuf, Object> {
@Override
protected void encode(ChannelHandlerContext ctx, Object o, List<Object> list) throws Exception {
if (o instanceof Packet) {
ByteBuf byteBuf = ctx.channel().alloc().ioBuffer();
Codec.INSTANCE.encode(byteBuf, (Packet) o);
list.add(byteBuf);
} else {
log.info("File ByteBuf need encode");
// ctx.writeAndFlush(o);
}
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List<Object> list) throws Exception {
if (byteBuf.getInt(0) == Codec.TYPE) {
System.out.println("decode FilePacket");
list.add(Codec.INSTANCE.decode(byteBuf));
} else {
log.info("File ByteBuf need decode");
// list.add(byteBuf);
ctx.fireChannelRead(byteBuf);
}
}
}

View File

@ -0,0 +1,15 @@
package com.xiaoliu.codec;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.util.List;
public class DecodeHandler extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List<Object> list) throws Exception {
list.add(Codec.INSTANCE.decode(byteBuf));
}
}

View File

@ -0,0 +1,15 @@
package com.xiaoliu.codec;
import com.xiaoliu.protocol.Packet;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
@ChannelHandler.Sharable
public class EncodeHandler extends MessageToByteEncoder {
@Override
protected void encode(ChannelHandlerContext ctx, Object o, ByteBuf byteBuf) throws Exception {
Codec.INSTANCE.encode(byteBuf, (Packet) o);
}
}

View File

@ -0,0 +1,47 @@
package com.xiaoliu.protocol;
import java.io.File;
import static com.xiaoliu.protocol.command.Command.FILE_PACKET;
public class FilePacket extends Packet {
File file;
int ACK;
@Override
public Byte getCommand() {
return FILE_PACKET;
}
public FilePacket() {
}
public FilePacket(File file) {
this.file = file;
}
public FilePacket(File file, int ACK) {
this.file = file;
this.ACK = ACK;
}
public File getFile() {
return file;
}
public void setFile(File file) {
this.file = file;
}
public int getACK() {
return ACK;
}
public void setACK(int ACK) {
this.ACK = ACK;
}
}

View File

@ -0,0 +1,7 @@
package com.xiaoliu.protocol;
public abstract class Packet {
public abstract Byte getCommand();
}

View File

@ -0,0 +1,13 @@
package com.xiaoliu.protocol.attribute;
import com.xiaoliu.protocol.request.LoginPacket;
import com.xiaoliu.protocol.session.Session;
import io.netty.util.AttributeKey;
public interface Attributes {
AttributeKey<Session> SESSION = AttributeKey.newInstance("session");
AttributeKey<LoginPacket> userAttr = AttributeKey.newInstance("user");
}

View File

@ -0,0 +1,11 @@
package com.xiaoliu.protocol.command;
public interface Command {
Byte FILE_PACKET = 1;
Byte LOGIN_PACKET_REQUEST = 2;
Byte LOGIN_PACKET_RESPONSE = 3;
}

View File

@ -0,0 +1,46 @@
package com.xiaoliu.protocol.request;
import com.xiaoliu.protocol.Packet;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import java.io.FileOutputStream;
import static com.xiaoliu.protocol.command.Command.LOGIN_PACKET_REQUEST;
@EqualsAndHashCode(callSuper = true)
@Data
public class LoginPacket extends Packet {
String name;
String id;
String fileName;
long fileLength;
long readLength;
FileOutputStream fileOutputStream;
boolean exec = false;
@Override
public Byte getCommand() {
return LOGIN_PACKET_REQUEST;
}
public LoginPacket() {
}
public LoginPacket(String name) {
this.name = name;
}
public LoginPacket(String name, String id) {
this.name = name;
this.id = id;
}
}

View File

@ -0,0 +1,39 @@
package com.xiaoliu.protocol.response;
import com.xiaoliu.protocol.Packet;
import static com.xiaoliu.protocol.command.Command.LOGIN_PACKET_RESPONSE;
public class LoginResponsePacket extends Packet {
String id;
String name;
public LoginResponsePacket() {
}
public LoginResponsePacket(String id, String name) {
this.id = id;
this.name = name;
}
@Override
public Byte getCommand() {
return LOGIN_PACKET_RESPONSE;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,14 @@
package com.xiaoliu.protocol.serilizer;
import com.xiaoliu.protocol.serilizer.impl.JSONSerilizer;
public interface Serilizer {
Serilizer DEFAULT = new JSONSerilizer();
byte[] serilize(Object object);
<T> T deSerilize(byte[] bytes, Class<T> clazz);
}

View File

@ -0,0 +1,18 @@
package com.xiaoliu.protocol.serilizer.impl;
import com.alibaba.fastjson.JSON;
import com.xiaoliu.protocol.serilizer.Serilizer;
public class JSONSerilizer implements Serilizer {
@Override
public byte[] serilize(Object object){
return JSON.toJSONBytes(object);
}
@Override
public <T> T deSerilize(byte[] bytes, Class<T> clazz){
return JSON.parseObject(bytes, clazz);
}
}

View File

@ -0,0 +1,38 @@
package com.xiaoliu.protocol.session;
public class Session {
String nodeId;
String nodeName;
public Session() {
}
public Session(String nodeId, String nodeName) {
this.nodeId = nodeId;
this.nodeName = nodeName;
}
public String getNodeId() {
return nodeId;
}
public void setNodeId(String nodeId) {
this.nodeId = nodeId;
}
public String getNodeName() {
return nodeName;
}
public void setNodeName(String nodeName) {
this.nodeName = nodeName;
}
@Override
public String toString() {
return "Session{" +
"nodeName='" + nodeName + "-" + nodeId + '\'' +
'}';
}
}

View File

@ -0,0 +1,14 @@
# ????????
org.slf4j.simpleLogger.defaultLogLevel=INFO
# ?????? class ????????????,?????? INFO
# org.slf4j.simpleLogger.log.com.baomidou.mybatisplus.generator=DEBUG
# ????
org.slf4j.simpleLogger.showDateTime=true
# ????
org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss
# ???
# org.slf4j.simpleLogger.showThreadName=true
# ???
org.slf4j.simpleLogger.showLogName=true
# ?????
#org.slf4j.simpleLogger.showShortLogName=false

View File

@ -0,0 +1,38 @@
package com.xiaoliu;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
/**
* Unit test for simple App.
*/
public class AppTest
extends TestCase
{
/**
* Create the test case
*
* @param testName name of the test case
*/
public AppTest( String testName )
{
super( testName );
}
/**
* @return the suite of tests being tested
*/
public static Test suite()
{
return new TestSuite( AppTest.class );
}
/**
* Rigourous Test :-)
*/
public void testApp()
{
assertTrue( true );
}
}

102
FileTransferService/pom.xml Normal file
View File

@ -0,0 +1,102 @@
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.xiaoliu</groupId>
<artifactId>FileTransfer</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>FileTransferService</artifactId>
<packaging>jar</packaging>
<name>FileTransferService</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.java.version>21</project.java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.xiaoliu</groupId>
<artifactId>FileTransferCommon</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<finalName>sane-service-v${project.version}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
</plugin>
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<finalName>${project.build.finalName}</finalName>
<archive>
<manifest>
<mainClass>com.xiaoliu.Server</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<appendAssemblyId>false</appendAssemblyId>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>io.github.fvarrui</groupId>
<artifactId>javapackager</artifactId>
<version>1.7.5</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>package</goal>
</goals>
<configuration>
<!-- mandatory -->
<mainClass>com.xiaoliu.Server</mainClass>
<!-- optional -->
<bundleJre>true</bundleJre>
<generateInstaller>true</generateInstaller>
<administratorRequired>false</administratorRequired>
<platform>windows</platform>
<displayName>netty</displayName>
<description>服务端软件</description>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,74 @@
package com.xiaoliu;
import com.xiaoliu.codec.DecodeHandler;
import com.xiaoliu.codec.EncodeHandler;
import com.xiaoliu.console.ConsoleManager;
import com.xiaoliu.console.impl.SendFileConsole;
import com.xiaoliu.handler.FilePacketServerHandler;
import com.xiaoliu.handler.FileReceiveServerHandler;
import com.xiaoliu.handler.FileSendServerHandler;
import com.xiaoliu.handler.JoinClusterRequestHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;
import java.util.Scanner;
@Slf4j
public class Server {
private static final int PORT = Integer.parseInt(System.getProperty("port", "8080"));
public static void main(String[] args) throws InterruptedException {
init();
}
public static void init() throws InterruptedException {
init(PORT);
}
public static void init(int port) throws InterruptedException {
if(port == 0){
port = 8080;
}
log.info("服务启动开始...");
ServerBootstrap bootstrap = new ServerBootstrap();
EventLoopGroup boss = new NioEventLoopGroup(2);
EventLoopGroup worker = new NioEventLoopGroup(10);
bootstrap.group(boss, worker)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.option(ChannelOption.TCP_NODELAY, true)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel channel) {
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(new FileReceiveServerHandler());
pipeline.addLast(new FileSendServerHandler());
pipeline.addLast(new DecodeHandler());
pipeline.addLast(new EncodeHandler());
pipeline.addLast(new JoinClusterRequestHandler());
pipeline.addLast(new FilePacketServerHandler());
// pipeline.addLast("handler", new MyServerHandler());
}
});
ChannelFuture future = bootstrap.bind(port).sync();
if (future.isSuccess()) {
log.info("端口绑定成功");
} else {
log.info("端口绑定失败");
}
future.channel().closeFuture().sync();
}
}

View File

@ -0,0 +1,11 @@
package com.xiaoliu.console;
import io.netty.channel.Channel;
import java.util.Scanner;
public interface Console {
void exec(Channel channel, Scanner scanner);
}

View File

@ -0,0 +1,33 @@
package com.xiaoliu.console;
import com.xiaoliu.console.impl.SendFileConsole;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
@Slf4j
public class ConsoleManager implements Console {
private Map<String, Console> consoleMap;
public ConsoleManager() {
consoleMap = new HashMap<>();
consoleMap.put("sendFile", new SendFileConsole());
}
@Override
public void exec(Channel channel, Scanner scanner) {
String consoleType = scanner.nextLine();
Console console = consoleMap.get(consoleType);
if (console != null) {
console.exec(channel, scanner);
} else {
log.info("无法识别指令:{}", consoleType);
}
}
}

View File

@ -0,0 +1,28 @@
package com.xiaoliu.console.impl;
import com.xiaoliu.console.Console;
import com.xiaoliu.protocol.FilePacket;
import com.xiaoliu.util.SessionUtil;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.util.Map;
import java.util.Scanner;
@Slf4j
public class SendFileConsole implements Console {
@Override
public void exec(Channel channel, Scanner scanner) {
log.info("请输入文件路径:");
String path = scanner.nextLine();
File file = new File(path);
FilePacket filePacket = new FilePacket(file);
Map<String, Channel> channelMap = SessionUtil.getNodeIdChannelMap();
for (Map.Entry<String, Channel> entry : channelMap.entrySet()) {
entry.getValue().writeAndFlush(filePacket);
}
}
}

View File

@ -0,0 +1,37 @@
package com.xiaoliu.handler;
import com.xiaoliu.protocol.FilePacket;
import com.xiaoliu.protocol.request.LoginPacket;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.io.FileOutputStream;
import static com.xiaoliu.protocol.attribute.Attributes.userAttr;
@Slf4j
@ChannelHandler.Sharable
public class FilePacketServerHandler extends SimpleChannelInboundHandler<FilePacket> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, FilePacket packet) throws Exception {
File file = packet.getFile();
log.info("receive file from client: {}", file.getName());
LoginPacket loginPacket = ctx.channel().attr(userAttr).get();
if(loginPacket.isExec()){
return;
}
loginPacket.setExec(true);
loginPacket.setFileName(file.getName());
loginPacket.setFileLength(file.length());
loginPacket.setFileOutputStream(new FileOutputStream(new File("./server-receive-" + file.getName())));
// FileReceiveServerHandler.fileLength = file.length();
// FileReceiveServerHandler.outputStream = new FileOutputStream(
// new File("./server-receive-" + file.getName())
// );
packet.setACK(packet.getACK() + 1);
ctx.writeAndFlush(packet);
}
}

View File

@ -0,0 +1,62 @@
package com.xiaoliu.handler;
import com.xiaoliu.codec.Codec;
import com.xiaoliu.protocol.attribute.Attributes;
import com.xiaoliu.protocol.request.LoginPacket;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;
import java.io.FileOutputStream;
import java.io.IOException;
@Slf4j
public class FileReceiveServerHandler extends ChannelInboundHandlerAdapter {
static FileOutputStream outputStream;
static long fileLength;
private static long readLength;
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
LoginPacket loginPacket = ctx.channel().attr(Attributes.userAttr).get();
ByteBuf byteBuf = (ByteBuf) msg;
int type = byteBuf.getInt(0);
if (type != Codec.TYPE) {
loginPacket.setReadLength(loginPacket.getReadLength()+byteBuf.readableBytes());
//readLength += byteBuf.readableBytes();
writeToFile(byteBuf, loginPacket.getFileOutputStream());
sendComplete(loginPacket);
} else {
super.channelRead(ctx, msg);
}
}
private void writeToFile(ByteBuf byteBuf, FileOutputStream outputStream) throws IOException {
byte[] bytes = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(bytes);
outputStream.write(bytes);
byteBuf.release();
}
private void sendComplete(long readLength) throws IOException {
if (readLength >= fileLength) {
log.info("文件接收完成.....");
outputStream.close();
}
}
private void sendComplete(LoginPacket loginPacket) throws IOException {
if (loginPacket.getReadLength() >= loginPacket.getFileLength()) {
log.info("文件接收完成...");
loginPacket.setExec(false);
loginPacket.setReadLength(0);
loginPacket.getFileOutputStream().close();
}
}
}

View File

@ -0,0 +1,45 @@
package com.xiaoliu.handler;
import com.xiaoliu.codec.Codec;
import com.xiaoliu.protocol.FilePacket;
import com.xiaoliu.protocol.Packet;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.DefaultFileRegion;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
@Slf4j
public class FileSendServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf = (ByteBuf) msg;
int type = byteBuf.getInt(0);
if (type == Codec.TYPE) {
Packet packet = Codec.INSTANCE.decode(byteBuf);
if (packet instanceof FilePacket) {
FilePacket filePacket = (FilePacket) packet;
if (filePacket.getACK() != 0) {
writeAndFlushFileRegion(ctx, filePacket);
} else {
super.channelRead(ctx, packet);
}
} else {
super.channelRead(ctx, packet);
}
}
}
private void writeAndFlushFileRegion(ChannelHandlerContext ctx, FilePacket packet) {
File file = packet.getFile();
DefaultFileRegion fileRegion = new DefaultFileRegion(file, 0, file.length());
ctx.writeAndFlush(fileRegion).addListener(future -> {
if (future.isSuccess()) {
log.info("{} 发送完成...", file.getName());
}
});
}
}

View File

@ -0,0 +1,32 @@
package com.xiaoliu.handler;
import com.xiaoliu.protocol.request.LoginPacket;
import com.xiaoliu.protocol.response.LoginResponsePacket;
import com.xiaoliu.protocol.session.Session;
import com.xiaoliu.util.IDUtil;
import com.xiaoliu.util.LoginUtil;
import com.xiaoliu.util.SessionUtil;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.extern.slf4j.Slf4j;
import java.util.Date;
@Slf4j
@ChannelHandler.Sharable
public class JoinClusterRequestHandler extends SimpleChannelInboundHandler<LoginPacket> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, LoginPacket loginPacket) {
String id = IDUtil.randomId();
loginPacket.setId(id);
log.info("{} [{}-{}]加入集群", new Date(), loginPacket.getName(), id);
LoginUtil.bindUser(loginPacket, ctx.channel());
ctx.writeAndFlush(new LoginResponsePacket(id, loginPacket.getName()));
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
LoginUtil.unBind(ctx.channel());
}
}

View File

@ -0,0 +1,48 @@
package com.xiaoliu.handler;
import com.xiaoliu.codec.Codec;
import com.xiaoliu.protocol.FilePacket;
import com.xiaoliu.protocol.Packet;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
@Slf4j
public class MyServerHandler extends ChannelInboundHandlerAdapter {
public static File file;
public static FileOutputStream outputStream;
public MyServerHandler() throws FileNotFoundException {
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println(msg);
ByteBuf byteBuf = (ByteBuf) msg;
int type = byteBuf.getInt(0);
if (type != Codec.TYPE) {
byte[] bytes = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(bytes);
outputStream.write(bytes);
byteBuf.release();
} else {
Packet packet = Codec.INSTANCE.decode(byteBuf);
if (packet instanceof FilePacket) {
FilePacket filePacket = (FilePacket) packet;
outputStream = new FileOutputStream("./receive-" + filePacket.getFile().getName());
ByteBuf byteBuf1 = ctx.channel().alloc().ioBuffer();
Codec.INSTANCE.encode(byteBuf1, filePacket);
ctx.channel().writeAndFlush(byteBuf1);
}
}
}
}

View File

@ -0,0 +1,11 @@
package com.xiaoliu.util;
import java.util.UUID;
public class IDUtil {
public static String randomId() {
return UUID.randomUUID().toString().split("-")[0];
}
}

View File

@ -0,0 +1,43 @@
package com.xiaoliu.util;
import com.xiaoliu.protocol.attribute.Attributes;
import com.xiaoliu.protocol.request.LoginPacket;
import com.xiaoliu.protocol.session.Session;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Slf4j
public class LoginUtil {
private static final Map<String, Channel> LOGIN_MAP = new HashMap<>();
public static void bindUser(LoginPacket login, Channel channel) {
LOGIN_MAP.put(login.getId(), channel);
channel.attr(Attributes.userAttr).set(login);
}
public static void unBind(Channel channel) {
if (hasLogin(channel)) {
LoginPacket login = getLogin(channel);
LOGIN_MAP.remove(login.getId());
channel.attr(Attributes.userAttr).set(null);
log.info("{} {}退出集群", new Date(), login.getId());
}
}
private static boolean hasLogin(Channel channel) {
return channel.hasAttr(Attributes.userAttr);
}
private static LoginPacket getLogin(Channel channel) {
return channel.attr(Attributes.userAttr).get();
}
public static Map<String, Channel> getLoginMap() {
return LOGIN_MAP;
}
}

View File

@ -0,0 +1,42 @@
package com.xiaoliu.util;
import com.xiaoliu.protocol.attribute.Attributes;
import com.xiaoliu.protocol.session.Session;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Slf4j
public class SessionUtil {
private static final Map<String, Channel> NODE_ID_CHANNEL_MAP = new HashMap<>();
public static void bindSession(Session session, Channel channel) {
NODE_ID_CHANNEL_MAP.put(session.getNodeId(), channel);
channel.attr(Attributes.SESSION).set(session);
}
public static void unBindSession(Channel channel) {
if (hasLogin(channel)) {
Session session = getSession(channel);
NODE_ID_CHANNEL_MAP.remove(session.getNodeId());
channel.attr(Attributes.SESSION).set(null);
log.info("{} {}退出集群", new Date(), session);
}
}
private static boolean hasLogin(Channel channel) {
return channel.hasAttr(Attributes.SESSION);
}
private static Session getSession(Channel channel) {
return channel.attr(Attributes.SESSION).get();
}
public static Map getNodeIdChannelMap() {
return NODE_ID_CHANNEL_MAP;
}
}

View File

@ -0,0 +1,14 @@
# ????????
org.slf4j.simpleLogger.defaultLogLevel=INFO
# ?????? class ????????????,?????? INFO
# org.slf4j.simpleLogger.log.com.baomidou.mybatisplus.generator=DEBUG
# ????
org.slf4j.simpleLogger.showDateTime=true
# ????
org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss
# ???
# org.slf4j.simpleLogger.showThreadName=true
# ???
org.slf4j.simpleLogger.showLogName=true
# ?????
#org.slf4j.simpleLogger.showShortLogName=false

View File

@ -0,0 +1,38 @@
package com.xiaoliu;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
/**
* Unit test for simple App.
*/
public class AppTest
extends TestCase
{
/**
* Create the test case
*
* @param testName name of the test case
*/
public AppTest( String testName )
{
super( testName );
}
/**
* @return the suite of tests being tested
*/
public static Test suite()
{
return new TestSuite( AppTest.class );
}
/**
* Rigourous Test :-)
*/
public void testApp()
{
assertTrue( true );
}
}

56
pom.xml Normal file
View File

@ -0,0 +1,56 @@
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.xiaoliu</groupId>
<artifactId>FileTransfer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>FileTransfer</name>
<url>http://maven.apache.org</url>
<modules>
<module>FileTransferClient</module>
<module>FileTransferService</module>
<module>FileTransferCommon</module>
</modules>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.110.Final</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.29</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.13</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.13</version>
</dependency>
</dependencies>
</project>