1.Netty传输工具
This commit is contained in:
commit
00d1dea5be
|
@ -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
|
|
@ -0,0 +1,8 @@
|
|||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 基于编辑器的 HTTP 客户端请求
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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() + " 登陆成功");
|
||||
}
|
||||
}
|
||||
|
|
@ -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()));
|
||||
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
|
@ -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 );
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package com.xiaoliu.protocol;
|
||||
|
||||
public abstract class Packet {
|
||||
|
||||
public abstract Byte getCommand();
|
||||
|
||||
}
|
|
@ -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");
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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 + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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 );
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -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];
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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 );
|
||||
}
|
||||
}
|
|
@ -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>
|
Loading…
Reference in New Issue