1.服务端
1.1 字符消息
/**
* @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);
}
}
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);
}
}
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();
}
}
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.客户端
@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();
}
}
}
/**
* 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;// 结尾位置
}
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);
}
}
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();
}
}
@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);
}
}