Springboot3整合Netty进行消息传递

1.服务端

1.1 字符消息

  • NettyServer
/**
 * @author liu.wenxuan1
 * @Description: netty服务端处理字符消息 解码器问题 不能同时处理文件和字符
 */
public class NettyServer {

    private static final int PORT = 8080;

    public static void main(String[] args) throws Exception {
        // 服务启动器
        new ServerBootstrap()
                // bossGroup用于处理连接请求 workerGroup 用于处理I/O操作
                .group(new NioEventLoopGroup(), new NioEventLoopGroup())
                // TCP SOCKET通道为NioServerSocketChannel
                // UDP DatagramChannel
                .channel(NioServerSocketChannel.class)
                // 当客户端注册读写事件时 初始化Handler
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new StringDecoder());
                        ch.pipeline().addLast(new StringEncoder());
                        ch.pipeline().addLast(new ServerHandler());
                    }
                })
                // 设置队列大小
                .option(ChannelOption.SO_BACKLOG, 128)
                // 保持连接
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                .bind(PORT);
    }
}
  • ServerHandler
public class ServerHandler extends SimpleChannelInboundHandler<Object> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) {
        try {
            if (msg instanceof String) {
                System.out.println("服务端接受信息:");
                System.out.println(msg);
                ctx.writeAndFlush("Server response: " + msg);
            }
        } catch (Exception e) {
            e.printStackTrace();
            ctx.close();
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws IOException {
        cause.printStackTrace();
        ctx.close();
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Client disconnected");
        super.channelInactive(ctx);
        ctx.close();
    }
}

1.2 文件

public class NettyServerFile {

    private static final int PORT = 8081;

    public static void main(String[] args) throws Exception {
        // 服务启动器
        new ServerBootstrap()
                // bossGroup用于处理连接请求 workerGroup 用于处理I/O操作
                .group(new NioEventLoopGroup(), new NioEventLoopGroup())
                // TCP SOCKET通道为NioServerSocketChannel
                // UDP DatagramChannel
                .channel(NioServerSocketChannel.class)
                // 当客户端注册读写事件时 初始化Handler
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.weakCachingConcurrentResolver(null))); // 最大长度
                        ch.pipeline().addLast(new ObjectEncoder());
                        ch.pipeline().addLast(new ServerHandlerFile());
                    }
                })
                // 设置队列大小
                .option(ChannelOption.SO_BACKLOG, 128)
                // 保持连接
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                .bind(PORT);
    }
}
  • ServerHandlerFile
public class ServerHandlerFile extends ChannelInboundHandlerAdapter {

    private int byteRead;
    private volatile int start = 0;
    private String file_dir = "C:\\Users\\liu.wenxuan1\\Desktop";

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        try {
            System.out.println("接收到客户端信息");
            if (msg instanceof FileUploadFile) {
                System.out.println("\"文件上传中...");
                FileUploadFile ef = (FileUploadFile) msg;
                byte[] bytes = ef.getBytes();
                System.out.println("byteRead:" + bytes.length);
                byteRead = ef.getEndPos();
                String md5 = ef.getFile_md5();//文件名
                String path = file_dir + File.separator + md5;
                File file = new File(path);
                RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
                randomAccessFile.seek(start);
                randomAccessFile.write(bytes);
                start = start + byteRead;
                if (byteRead > 0) {
                    ctx.writeAndFlush(start);
                }
                System.out.println("文件已经读完--------" + byteRead);
                randomAccessFile.close();

            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            ctx.close();
        }

    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}
  • FileUploadFile
public class FileUploadFile implements Serializable {


    private static final long serialVersionUID = 1L;
    private File file;// 文件
    private String file_md5;// 文件名
    private int starPos;// 开始位置
    private byte[] bytes;// 文件字节数组
    private int endPos;// 结尾位置

    public int getStarPos() {
        return starPos;
    }

    public void setStarPos(int starPos) {
        this.starPos = starPos;
    }

    public int getEndPos() {
        return endPos;
    }

    public void setEndPos(int endPos) {
        this.endPos = endPos;
    }

    public byte[] getBytes() {
        return bytes;
    }

    public void setBytes(byte[] bytes) {
        this.bytes = bytes;
    }

    public File getFile() {
        return file;
    }

    public void setFile(File file) {
        this.file = file;
    }

    public String getFile_md5() {
        return file_md5;
    }

    public void setFile_md5(String file_md5) {
        this.file_md5 = file_md5;
    }
}

2.客户端

  • NettyController
@RestController
@RequestMapping("/netty")
public class NettyController {

    @Autowired
    private NettyClient nettyClient;

    @PostMapping(value = "/sendMessage", consumes = "text/plain")
    @ResponseBody
    public void uploadFile(@RequestBody Stringmessage) {
        try {
            nettyClient.sendMessage(message);
            // 这里可以添加发送数据的逻辑,例如通过ChannelHandlerContext发送数据
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @PostMapping("/upload")
    public void uploadFile(@RequestBody FileUploadFile uploadFile) {
        try {
            nettyClient.uploadFile(uploadFile);
            // 这里可以添加发送数据的逻辑,例如通过ChannelHandlerContext发送数据
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • FileUploadFile
/**
 * package路径要和服务端一样才行
 */
@Data
public class FileUploadFile implements Serializable {

    private static final long serialVersionUID = 1L;

    private String filePath;

    private File file;// 文件

    private String fileName;// 文件名

    private int starPos;// 开始位置

    private byte[] bytes;// 文件字节数组

    private int endPos;// 结尾位置
}
  • ClientHandler
public class ClientHandler extends SimpleChannelInboundHandler<Object> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof String) {
            System.out.println("Client received: " + msg);
        }
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Client active");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws IOException {
        cause.printStackTrace();
        ctx.close();
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        super.channelInactive(ctx);
    }
}
  • ClientHandlerFile
public class ClientHandlerFile extends ChannelInboundHandlerAdapter {

    private int byteRead;
    private volatile int start = 0;
    private volatile int lastLength = 0;
    public RandomAccessFile randomAccessFile;
    private FileUploadFile fileUploadFile = new FileUploadFile();

    public ClientHandlerFile() {

    }

    public void send(FileUploadFile file, Channel ctx) {
        try {
            randomAccessFile = new RandomAccessFile(file.getFile(), "r");
            randomAccessFile.seek(file.getStarPos());
            lastLength = (int) randomAccessFile.length() ;
            System.out.println("文件长度:" + randomAccessFile.length());
            byte[] bytes = new byte[lastLength];
            if ((byteRead = randomAccessFile.read(bytes)) != -1) {
                file.setEndPos(byteRead);
                file.setBytes(bytes);
                ctx.writeAndFlush(file);
                System.out.println("已发送");
            } else {
                System.out.println("文件已经读完");
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException i) {
            i.printStackTrace();
        }
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        System.out.println("已连接");
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//        if (msg instanceof Integer) {
//            start = (Integer) msg;
//            if (start != -1) {
//                randomAccessFile = new RandomAccessFile(fileUploadFile.getFile(), "r");
//                randomAccessFile.seek(start);
//                System.out.println("块儿长度:" + (randomAccessFile.length() / 10));
//                System.out.println("长度:" + (randomAccessFile.length() - start));
//                int a = (int) (randomAccessFile.length() - start);
//                int b = (int) (randomAccessFile.length() / 10);
//                if (a < b) {
//                    lastLength = a;
//                }
//                byte[] bytes = new byte[lastLength];
//                System.out.println("-----------------------------" + bytes.length);
//                if ((byteRead = randomAccessFile.read(bytes)) != -1 && (randomAccessFile.length() - start) > 0) {
//                    System.out.println("byte 长度:" + bytes.length);
//                    fileUploadFile.setEndPos(byteRead);
//                    fileUploadFile.setBytes(bytes);
//                    try {
//                        ctx.writeAndFlush(fileUploadFile);
//                    } catch (Exception e) {
//                        e.printStackTrace();
//                    }
//                } else {
//                    randomAccessFile.close();
//                    ctx.close();
//                    System.out.println("文件已经读完--------" + byteRead);
//                }
//            }
//        }
    }

    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }

}
  • NettyClient
@Component
public class NettyClient {

    private static final int MESSAGE_PORT = 8080;

    private static final int FILE_PORT = 8081;

    private static final String HOST = "10.82.224.183";

    private static final String PREFIX = "C:\\Users\\p30019551244\\Desktop\\";

    public void sendMessage(String message) throws Exception {
        // 启动类
        new Bootstrap()
                // 添加EventLoop
                .group(new NioEventLoopGroup())
                // 客户端channel实现
                .channel(NioSocketChannel.class)
                // 处理器
                .handler(new ChannelInitializer<NioSocketChannel>() {
                    /**
                     * 连接后被调用
                     * @param ch
                     * @throws Exception
                     */
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new StringDecoder());
                        ch.pipeline().addLast(new StringEncoder());
                        ch.pipeline().addLast(new ClientHandler());
                    }
                })
                .connect(HOST, MESSAGE_PORT)
                .sync()
                .channel()
                .writeAndFlush(message);
    }

    public void uploadFile(FileUploadFile fileUploadFile) throws Exception {
        // 启动类
        Channel channel = new Bootstrap()
                // 添加EventLoop
                .group(new NioEventLoopGroup())
                // 客户端channel实现
                .channel(NioSocketChannel.class)
                // 处理器
                .handler(new ChannelInitializer<NioSocketChannel>() {
                    /**
                     * 连接后被调用
                     * @param ch
                     * @throws Exception
                     */
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.weakCachingConcurrentResolver(null))); // 最大长度
                        ch.pipeline().addLast(new ObjectEncoder());
                        ch.pipeline().addLast(new ClientHandlerFile());
                    }
                })
                .connect(HOST, FILE_PORT)
                .sync()
                .channel();

        fileUploadFile.setFile(new File(PREFIX + fileUploadFile.getFileName()));
        fileUploadFile.setStarPos(0);
        int byteRead;
        int lastLength = 0;
        RandomAccessFile randomAccessFile;
        randomAccessFile = new RandomAccessFile(fileUploadFile.getFile(), "r");
        randomAccessFile.seek(fileUploadFile.getStarPos());
        lastLength = (int) randomAccessFile.length();
        System.out.println("文件长度:" + randomAccessFile.length());
        byte[] bytes = new byte[lastLength];
        if ((byteRead = randomAccessFile.read(bytes)) != -1) {
            fileUploadFile.setEndPos(byteRead);
            fileUploadFile.setBytes(bytes);
            channel.writeAndFlush(fileUploadFile);
            System.out.println("已发送");
        } else {
            System.out.println("文件已经读完");
        }
        randomAccessFile.close();

    }

    public static void main(String[] args) throws Exception {
        // 启动类
        Channel channel = new Bootstrap()
                // 添加EventLoop
                .group(new NioEventLoopGroup())
                // 客户端channel实现
                .channel(NioSocketChannel.class)
                // 处理器
                .handler(new ChannelInitializer<NioSocketChannel>() {
                    /**
                     * 连接后被调用
                     * @param ch
                     * @throws Exception
                     */
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new StringDecoder());
                        ch.pipeline().addLast(new StringEncoder());
                        ch.pipeline().addLast(new ClientHandler());
                    }
                })
                .connect(HOST, MESSAGE_PORT)
                .sync()
                .channel();

        // 创建Scanner对象,用于读取用户输入
        Scanner scanner = new Scanner(System.in);
        while (true) {
            System.out.print("输入消息");
            String userInput = scanner.nextLine();
            if ("exit".equalsIgnoreCase(userInput)) {
                break;
            }  else {
                channel.writeAndFlush(userInput);
            }
        }
    }

    public void testFile() throws Exception {
        // 启动类
        Channel channel = new Bootstrap()
                // 添加EventLoop
                .group(new NioEventLoopGroup())
                // 客户端channel实现
                .channel(NioSocketChannel.class)
                // 处理器
                .handler(new ChannelInitializer<NioSocketChannel>() {
                    /**
                     * 连接后被调用
                     * @param ch
                     * @throws Exception
                     */
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.weakCachingConcurrentResolver(null))); // 最大长度
                        ch.pipeline().addLast(new ObjectEncoder());
                        ch.pipeline().addLast(new ClientHandlerFile());
                    }
                })
                .connect(HOST, FILE_PORT)
                .sync()
                .channel();

        // 创建Scanner对象,用于读取用户输入
        Scanner scanner = new Scanner(System.in);
        while (true) {
            System.out.print("输入file来发送文件");
            String userInput = scanner.nextLine();
            if ("exit".equalsIgnoreCase(userInput)) {
                break;
            } else if ("file".equalsIgnoreCase(userInput)) {
                File file = new File("C:\\Users\\p30019551244\\Desktop\\demo.txt");
                FileUploadFile fileUploadFile = new FileUploadFile();
                fileUploadFile.setFile(file);
                fileUploadFile.setFileName("1234567.txt");
                fileUploadFile.setStarPos(0);
                int byteRead;
                int start = 0;
                int lastLength = 0;
                RandomAccessFile randomAccessFile;
                try {
                    randomAccessFile = new RandomAccessFile(fileUploadFile.getFile(), "r");
                    randomAccessFile.seek(fileUploadFile.getStarPos());
                    lastLength = (int) randomAccessFile.length() ;
                    System.out.println("文件长度:" + randomAccessFile.length());
                    byte[] bytes = new byte[lastLength];
                    if ((byteRead = randomAccessFile.read(bytes)) != -1) {
                        fileUploadFile.setEndPos(byteRead);
                        fileUploadFile.setBytes(bytes);
                        channel.writeAndFlush(fileUploadFile);
                        System.out.println("已发送");
                    } else {
                        System.out.println("文件已经读完");
                    }
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (IOException i) {
                    i.printStackTrace();
                }
            } else {
                System.out.println("发送信息");
                channel.writeAndFlush(userInput);
            }
        }
    }
}

3.大文件

public class NettyServerConfig {

    @Autowired
    private  FileUploadHandler fileUploadHandler;

    @Value("${netty.port:8080}")
    private int port;

    @Bean
    public ServerBootstrap serverBootstrap() {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        ServerBootstrap b = new ServerBootstrap();
        b.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) {
                        ChannelPipeline pipeline = ch.pipeline();
                        // HTTP编解码器
                        pipeline.addLast(new HttpServerCodec());
                        // 聚合HTTP报文
                        pipeline.addLast(new HttpObjectAggregator(Integer.MAX_VALUE));
                        // 大文件分块处理
                        pipeline.addLast(new ChunkedWriteHandler());
                        // 自定义业务处理器
                        pipeline.addLast(fileUploadHandler);
                    }
                })
                .option(ChannelOption.SO_BACKLOG, 128)
                .childOption(ChannelOption.SO_KEEPALIVE, true);

        return b;
    }

    @PreDestroy
    public void shutdown() throws InterruptedException {

    }
}
ChannelHandler.Sharable
@Component
public class FileUploadHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

    private static final String UPLOAD_PATH = "./uploads/";

    @Autowired
    MinIoUtil minIoUtil;

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) {
        // 处理文件上传请求
        if (request.method() == HttpMethod.POST && "/upload".equals(request.uri())) {
            try {
                ByteBuf content = request.content();
                String fileName = parseFileName(request);
                saveFile(content, fileName);
                sendResponse(ctx, "Upload success: " + fileName, HttpResponseStatus.OK);
            } catch (Exception e) {
                sendResponse(ctx, "Upload failed: " + e.getMessage(),
                        HttpResponseStatus.INTERNAL_SERVER_ERROR);
            }
        }
        // 处理文件下载请求
        else if (request.method() == HttpMethod.POST && request.uri().startsWith("/download/")) {
            String fileName = request.uri().substring("/download/".length());
            sendFile(ctx, fileName);
        }
    }

    private String parseFileName(HttpRequest request) {
        String contentType = request.headers().get(HttpHeaderNames.CONTENT_DISPOSITION);
        String[] tokens = contentType.split(";");
        for (String token : tokens) {
            if (token.trim().startsWith("filename=")) {
                return token.substring(token.indexOf('=')+1).trim().replace("\"", "");
            }
        }
        return "unknown_" + System.currentTimeMillis();
    }

    private void saveFile(ByteBuf content, String fileName) throws IOException {
        Path path = Paths.get(UPLOAD_PATH + fileName);
        // 保存到MinIO
        MinioUploadResp res = minIoUtil.upload(content, fileName, "lwx/test");
        System.out.println(res);
        // 保存到本地
//        Files.createDirectories(path.getParent());
//        try (FileOutputStream out = new FileOutputStream(path.toFile())) {
//            content.readBytes(out, content.readableBytes());
//        }
    }

    private void sendFile(ChannelHandlerContext ctx, String fileName) {
        File file = new File(UPLOAD_PATH + fileName);
        if (!file.exists()) {
            sendResponse(ctx, "File not found", HttpResponseStatus.NOT_FOUND);
            return;
        }

        try {
            RandomAccessFile raf = new RandomAccessFile(file, "r");
            long fileLength = raf.length();
            
            HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
            HttpUtil.setContentLength(response, fileLength);
            response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/octet-stream");
            response.headers().set(HttpHeaderNames.CONTENT_DISPOSITION, 
                                 "attachment; filename=" + fileName + "\"");

                ctx.write(response);
                ctx.write(new ChunkedFile(raf), ctx.newProgressivePromise());
                ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
            } catch (Exception e) {
                ctx.close();
        }
    }

        private void sendResponse(ChannelHandlerContext ctx, String message, HttpResponseStatus status) {
            FullHttpResponse response = new DefaultFullHttpResponse(
                    HttpVersion.HTTP_1_1, status, Unpooled.copiedBuffer(message, CharsetUtil.UTF_8));
            response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
            ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
        }
    }
posted @ 2025-01-22 16:58  lwx_R  阅读(169)  评论(0)    收藏  举报