Netty实现文件服务器

1.文件上传下载的常用方法

文件上传下载是一种非常常见的功能,特别是在web服务网站。

常用的文件上传下载协议有以下几种:

  • FTP(File Transfer Protocol):是一种用于在计算机间传输文件的标准网络协议。它使用客户端-服务器架构,通过对服务器的连接进行认证和授权,允许用户将文件上传到服务器或从服务器下载文件。
  • HTTP(s):超文本传输协议(HTTP)是用于在互联网上发送和接收超文本的协议。HTTP允许通过HTTP请求和响应进行文件的上传和下载。通过HTTPS可以提供更安全的传输。
  • SCP(Secure Copy):SCP是一种安全的文件传输协议,基于SSH(Secure Shell)协议。它可以在本地计算机和远程计算机之间进行文件传输,提供了加密和身份验证的功能。
  • SFTP(SSH File Transfer Protocol):SFTP是一个基于SSH的安全文件传输协议。它使用SSH进行身份验证和加密,可以在本地计算机和远程计算机之间进行安全的文件传输。
  • 除了以上几种应用层上的标准协议,我们甚至可以使用原生的socket构建文件服务器。使用Tcp搭建文件服务,采用的协议为私有协议。

2.服务端代码实现

本文演示如何使用netty搭建基于http协议的文件下载服务。(例子来自于netty官方example,这里做了简化,去掉ssl,去掉浏览器文件缓存)

2.1、构建ServerBootstrap

文件下载基于http协议实现,所以跟创建http服务很类似

public final class HttpStaticFileServer {private EventLoopGroup bossGroup = new NioEventLoopGroup(1);private EventLoopGroup workerGroup = new NioEventLoopGroup();private HttpFileConfig ftpConfig;public HttpStaticFileServer(HttpFileConfig ftpConfig) {this.ftpConfig = ftpConfig;}public void start() throws Exception {try {ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {@Overridepublic void initChannel(SocketChannel ch) {ch.pipeline().addLast(new HttpServerCodec());ch.pipeline().addLast(new HttpObjectAggregator(65536));ch.pipeline().addLast(new HttpStaticFileServerHandler(ftpConfig));}});Channel ch = b.bind(ftpConfig.getPort()).sync().channel();System.err.println("http file server listens at " +"http://127.0.0.1:" + ftpConfig.getPort() + '/');ch.closeFuture().sync();} catch (Exception e) {shutdown();throw e;}}public void shutdown() {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}

总共添加3个handler,作用如下

HttpServerCodec:将HTTP请求消息从字节流解码为HttpRequest对象,将HTTP响应消息从HttpResponse对象编码为字节流。它还负责处理HTTP的编解码细节,如HTTP头部的解析和生成、HTTP消息的分块处理等。

HttpObjectAggregator:将多个HTTP请求或响应消息进行聚合,形成一个完整的HttpObject对象。在HTTP请求中,它可以将HTTP请求头部和请求体合并为一个FullHttpRequest对象;在HTTP响应中,它可以将HTTP响应头部和响应体合并为一个FullHttpResponse对象。HttpStaticFileServerHandler:自定义类,用于处理上一个节点解析的FullHttpRequest。

2.2、自定义类处理客户端的http请求

public class HttpStaticFileServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {private HttpFileConfig httpConfig;HttpStaticFileServerHandler(HttpFileConfig config) {this.httpConfig = config;}@Overridepublic void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {if (!request.decoderResult().isSuccess()) {sendError(ctx, BAD_REQUEST);return;}if (request.method() != GET) {sendError(ctx, METHOD_NOT_ALLOWED);return;}final String uri = request.uri();final String path = sanitizeUri(uri);if (path == null) {sendError(ctx, FORBIDDEN);return;}File file = new File(path);if (file.isHidden() || !file.exists()) {sendError(ctx, NOT_FOUND);return;}if (file.isDirectory()) {if (uri.endsWith("/")) {sendFileListing(ctx, file, uri);} else {sendRedirect(ctx, uri + '/');}return;}if (!file.isFile()) {sendError(ctx, FORBIDDEN);return;}RandomAccessFile raf;try {raf = new RandomAccessFile(file, "r");} catch (FileNotFoundException ignore) {sendError(ctx, NOT_FOUND);return;}long fileLength = raf.length();HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK);HttpUtil.setContentLength(response, fileLength);setContentTypeHeader(response, file);response.headers().set(HttpHeaderNames.CONTENT_DISPOSITION, String.format("filename='%s'", URLEncoder.encode(file.getName(), "UTF-8")));if (HttpUtil.isKeepAlive(request)) {response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);}// Write the initial line and the header.ctx.write(response);// Write the content.ChannelFuture sendFileFuture;ChannelFuture lastContentFuture;sendFileFuture =ctx.write(new DefaultFileRegion(raf.getChannel(), 0, fileLength), ctx.newProgressivePromise());lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);sendFileFuture.addListener(new ChannelProgressiveFutureListener() {@Overridepublic void operationProgressed(ChannelProgressiveFuture future, long progress, long total) {if (total < 0) {System.err.println(future.channel() + " Transfer progress: " + progress);} else {System.err.println(future.channel() + " Transfer progress: " + progress + " / " + total);}}@Overridepublic void operationComplete(ChannelProgressiveFuture future) {System.err.println(future.channel() + " Transfer complete.");}});if (!HttpUtil.isKeepAlive(request)) {lastContentFuture.addListener(ChannelFutureListener.CLOSE);}}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {cause.printStackTrace();if (ctx.channel().isActive()) {sendError(ctx, INTERNAL_SERVER_ERROR);}}private String sanitizeUri(String uri) {try {uri = URLDecoder.decode(uri, "UTF-8");} catch (UnsupportedEncodingException e) {throw new Error(e);}if (uri.isEmpty() || uri.charAt(0) != '/') {return null;}uri = uri.replace('/', File.separatorChar);// 安全验证if (!httpConfig.getSafeRule().test(uri)) {return null;}// Convert to absolute path.return httpConfig.getFileDirectory() + File.separator + uri;}private void sendFileListing(ChannelHandlerContext ctx, File dir, String dirPath) {FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK);response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTF-8");StringBuilder buf = new StringBuilder().append("<!DOCTYPE html>\r\n").append("<html><head><meta charset='utf-8' /><title>").append("Listing of: ").append(dirPath).append("</title></head><body>\r\n").append("<h3>Listing of: ").append(dirPath).append("</h3>\r\n").append("<ul>").append("<li><a href=\"../\">..</a></li>\r\n");for (File f : dir.listFiles()) {if (f.isHidden() || !f.canRead()) {continue;}String name = f.getName();if (!httpConfig.getSafeRule().test(name)) {continue;}buf.append("<li><a href=\"").append(name).append("\">").append(name).append("</a></li>\r\n");}buf.append("</ul></body></html>\r\n");ByteBuf buffer = Unpooled.copiedBuffer(buf, CharsetUtil.UTF_8);response.content().writeBytes(buffer);buffer.release();ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);}private void sendRedirect(ChannelHandlerContext ctx, String newUri) {FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, FOUND);response.headers().set(HttpHeaderNames.LOCATION, newUri);// Close the connection as soon as the error message is sent.ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);}private void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) {FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, status, Unpooled.copiedBuffer("Failure: " + status + "\r\n", CharsetUtil.UTF_8));response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);}private static void setContentTypeHeader(HttpResponse response, File file) {MimetypesFileTypeMap mimeTypesMap = new MimetypesFileTypeMap();response.headers().set(HttpHeaderNames.CONTENT_TYPE, mimeTypesMap.getContentType(file.getPath()));}
}

对于客户端的请求,如果是目录则枚举目录文件,封装成html的<a>超链接标签;如果是文件,则提供下载。

其中下载用到了DefaultFileRegion工具。

2.3、DefaultFileRegion解析

DefaultFileRegion可以利用零拷贝(zero-copy)技术,将文件内容直接从文件系统读取,并将数据传输到网络中,而无需经过中间缓冲区的复制。这种方式可以大大提高文件传输的效率,并减少CPU和内存的消耗。非常适用于需要传输大文件或者高并发的文件传输场景。

使用DefaultFileRegion写入文件之后,需要再写入特殊的httpconent表示接受,单例内容LastHttpContent.EMPTY_LAST_CONTENT。

需要注意的是,DefaultFileRegion只适用于文件服务器直接推送给客户端情况下使用。如果文件服务器需要将文件加载到内存,则不适合使用。改用下面的HttpChunkedInput。

2.4、使用HttpChunkedInput传输大文件

使用HttpChunkedInput,可以将需要传输的数据分割成多个HttpContent块,并通过Netty的HTTP编解码器进行传输。这样,发送方可以在需要的时候生成和发送每个块,接收方可以在接收到每个块时进行处理。

HttpChunkedInput可以与Netty的HTTP编解码器无缝集成,使得在进行HTTP分块传输时非常方便。对于需要处理大量数据的应用场景,使用HttpChunkedInput可以提高性能和降低内存消耗,从而更好地处理大规模数据传输。

使用HttpChunkedInput,则不需要手动添加LastHttpContent.EMPTY_LAST_CONTENT。内部代码自行添加

    public HttpContent readChunk(ByteBufAllocator allocator) throws Exception {if (this.input.isEndOfInput()) {if (this.sentLastChunk) {return null;} else {this.sentLastChunk = true;return this.lastHttpContent;}} else {ByteBuf buf = (ByteBuf)this.input.readChunk(allocator);return buf == null ? null : new DefaultHttpContent(buf);}}

ServerBootstrap添加ChunkedWriteHandler()配合处理大文件分块问题

  ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {@Overridepublic void initChannel(SocketChannel ch) {ch.pipeline().addLast(new HttpServerCodec());ch.pipeline().addLast(new HttpObjectAggregator(65536));ch.pipeline().addLast(new ChunkedWriteHandler());ch.pipeline().addLast(new HttpStaticFileServerHandler(ftpConfig));}});

2.5文件服务配置类

配置类复制端口设定,文件目录设定,文件安全检测策略设定等。

public class HttpFileConfig {/*** 需要提供对外服务的文件目录*/private String fileDirectory;/*** 对外服务端口,默认为8080*/private int port  = 8080;/*** url安全规则判定,默认运行访问制定目录下所有文件*/private Predicate<String> safeRule = new Predicate<String>() {@Overridepublic boolean test(String s) {return true;}};//省略setter/getter}

2.6启动入口

public class Main {public static void main(String[] args) throws Exception {HttpFileConfig cfg = new HttpFileConfig();cfg.setFileDirectory("F:\\projects");HttpStaticFileServer fileServer = new HttpStaticFileServer(cfg);fileServer.start();}
}

3.客户端演示

3.1netty客户端

使用netty,也可以实现文件下载功能。

public class HttpDownloadClient {private static final String SERVER_HOST = "localhost";private static final int SERVER_PORT = 8080;private static EventLoopGroup group = new NioEventLoopGroup();public static void main(String[] args) throws Exception {try {Bootstrap b = new Bootstrap();b.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true).handler(new ChannelInitializer<SocketChannel>() {@Overridepublic void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new HttpClientCodec());ch.pipeline().addLast(new ChunkedWriteHandler());ch.pipeline().addLast(new HttpObjectAggregator(65536));ch.pipeline().addLast(new HttpFileDownloadClientHandler());}});ChannelFuture future = b.connect(SERVER_HOST, SERVER_PORT).sync();// 构建HTTP GET请求DefaultHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/pom.xml");// 设置请求头信息,指定下载文件的名称和保存路径requestSetting(request);// 发送HTTP GET请求future.channel().writeAndFlush(request);future.channel().closeFuture().sync();} finally {group.shutdownGracefully();}}private static void requestSetting(DefaultHttpRequest request) {request.headers().set(HttpHeaderNames.HOST, SERVER_HOST);request.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE);request.headers().set(HttpHeaderNames.ACCEPT_ENCODING, HttpHeaderValues.GZIP);request.headers().set(HttpHeaderNames.ACCEPT_CHARSET, "ISO-8859-1,utf-8;q=0.7,*;q=0.7");request.headers().set(HttpHeaderNames.USER_AGENT, "Netty File Download Client");request.headers().set(HttpHeaderNames.ACCEPT_LANGUAGE, "en-us,en;q=0.5");request.headers().set(HttpHeaderNames.ACCEPT, "*/*");request.headers().set(HttpHeaderNames.RANGE, "bytes=0-");request.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/octet-stream");request.headers().set(HttpHeaderNames.CONTENT_LENGTH, HttpHeaderValues.ZERO);}static class HttpFileDownloadClientHandler extends SimpleChannelInboundHandler<FullHttpResponse> {@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {}@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {}private static final Pattern FILENAME_PATTERN = Pattern.compile("filename\\*?=\"?(?:UTF-8'')?([^\";]*)\"?;?");@Overrideprotected void channelRead0(ChannelHandlerContext ctx, FullHttpResponse response) throws Exception {System.out.println(response);String contentType = response.headers().get(HttpHeaderNames.CONTENT_TYPE);if (contentType.contains("html")) {if (response.getDecoderResult().isSuccess() && response.content().isReadable()) {String html = response.content().toString(io.netty.util.CharsetUtil.UTF_8);System.out.println(html);}} else if (contentType.contains("application/octet-stream")) {Matcher matcher = FILENAME_PATTERN.matcher(response.headers().get(HttpHeaderNames.CONTENT_DISPOSITION));String fileName = "f@" + System.currentTimeMillis();if (matcher.find()) {fileName = matcher.group(1).replaceAll("'", "").replaceAll("\"", "");System.out.println("下载文件:" + fileName);}RandomAccessFile file = new RandomAccessFile("download/"+fileName, "rw");FileChannel fileChannel = file.getChannel();fileChannel.write(response.content().nioBuffer()); // 将数据从ByteBuffer写入到RandomAccessFile}}}
}

通过解析 FullHttpResponse的Header,如果是html文本则显示html内容;如果是字节流则保存到本地目录。

本地测试了以下,一个3.5G的电影,大概需要60秒。

进一步拓展

1.增加用户界面;

2.增加文件断点续传;

3.多线程下载;

3.2浏览器客户端

直接在浏览器显示,当然最方便。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/293425.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

嵌入式|蓝桥杯STM32G431(HAL库开发)——CT117E学习笔记12:DAC数模转换

系列文章目录 嵌入式|蓝桥杯STM32G431&#xff08;HAL库开发&#xff09;——CT117E学习笔记01&#xff1a;赛事介绍与硬件平台 嵌入式|蓝桥杯STM32G431&#xff08;HAL库开发&#xff09;——CT117E学习笔记02&#xff1a;开发环境安装 嵌入式|蓝桥杯STM32G431&#xff08;…

2024年天津体育学院退役大学生士兵专升本专业考试报名安排

天津体育学院2024年退役大学生士兵免试专升本招生专业考试报名安排 一、报名安排 1.报名对象&#xff1a;免于参加天津市文化考试的退役大学生士兵&#xff08;已参加天津市统一报名且资格审核通过&#xff09; 2.报名时间&#xff1a;2024年4月4日9&#xff1a;00-4月5日17…

Stream流 --java学习笔记

什么是Stream? 也叫Stream流&#xff0c;是|dk8开始新增的一套APl(java.util.stream.*)&#xff0c;可以用于操作集合或者数组的数据。优势:Stream流大量的结合了Lambda的语法风格来编程&#xff0c;提供了一种更加强大&#xff0c;更加简单的方式操作集合或者数组中的数据&a…

1.10 类、方法、封装、继承、多态、装饰器

一、介绍类 类(class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例 实例化&#xff1a;创建一个类的实例&#xff0c;类的具体对象。 对象&#xff1a;通过类定义的数据结构实例。对象包括两个数据成员&#x…

windows安全中心设置@WindowsDefender@windows安全中心常用开关

文章目录 abstractwindows defender相关服务&#x1f47a; 停用windows Defender临时关闭实时防护使用软件工具关闭defender control(慎用)dismdControl 其他方法使其他杀毒软件注册表修改 保护历史恢复被认为是有病毒的文件添加信任目录,文件,文件类型或进程 abstract window…

算法学习——LeetCode力扣动态规划篇4(377. 组合总和 Ⅳ、322. 零钱兑换、279. 完全平方数、139. 单词拆分)

算法学习——LeetCode力扣动态规划篇4 377. 组合总和 Ⅳ 377. 组合总和 Ⅳ - 力扣&#xff08;LeetCode&#xff09; 描述 给你一个由 不同 整数组成的数组 nums &#xff0c;和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。 题目数据保…

2、Cocos Creator 下载安装

Cocos Creator 从 v2.3.2 开始接入了全新的 Dashboard 系统&#xff0c;能够同时对多版本引擎和项目进行统一升级和管理&#xff01;Cocos Dashboard 将做为 Creator 各引擎统一的下载器和启动入口&#xff0c;方便升级和管理多个版本的 Creator。还集成了统一的项目管理及创建…

pytorch反向传播算法

目录 1. 链式法则复习2. 多输出感知机3. 多层感知机4. 多层感知机梯度推导5. 反向传播的总结 1. 链式法则复习 2. 多输出感知机 3. 多层感知机 如图&#xff1a; 4. 多层感知机梯度推导 简化式子把( O k O_k Ok​ - t k t_k tk​) O k O_k Ok​(1 - O k O_k Ok​)起个别名…

Python(django)之单一接口展示功能前端开发

1、代码 建立apis_manage.html 代码如下&#xff1a; <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><title>测试平台</title> </head> <body role"document"> <nav c…

谈谈 MySQL 的锁

前言 在MySQL中&#xff0c;锁这个定义其实还是蛮重要的。经过我这几天的学习&#xff0c;我感觉锁是一个可以说难又可以说不难的知识点。难就难在锁可以与事务、多线程、并发结合在一起&#xff0c;这就很难了。但是&#xff0c;假如锁没有结合这些知识点&#xff0c;就单单一…

webpack搭建开发环境

webpack搭建开发环境 一.webpack开发模式二.webpack打包模式三.webpack打包模式应用四.Webpack 前端注入环境变量五.Webpack 开发环境调错 source map六. Webpack 设置解析别名路径七.优化-CDN的使用八.多页面打包九.优化-分割公共代码一.webpack开发模式 作用:启动 Web 服务…

六、Django开发

六、Django开发 1.新建项目2.创建app2.1 第一种方法&#xff1a;2.2 利用pycharm中tools工具直接创建app 3.设计表结构&#xff08;django&#xff09;4.在MySQL中生成表5.静态文件管理6.部门管理6.1 部门列表 7.模板的继承8.用户管理8.1初识Form1.views.py2.user_add.html 8.2…

leetcode131分割回文串

递归树 下面这个代码是遍历处所有的子串 #include <bits/stdc.h> using namespace std; class Solution { public:vector<vector<string>> vvs;vector<string> vs;vector<vector<string>> partition(string s) {dfs(0,s);return vvs;}vo…

笔记本电脑上部署LLaMA-2中文模型

尝试在macbook上部署LLaMA-2的中文模型的详细过程。 &#xff08;1&#xff09;环境准备 MacBook Pro(M2 Max/32G); VMware Fusion Player 版本 13.5.1 (23298085); Ubuntu 22.04.2 LTS; 给linux虚拟机分配8*core CPU 16G RAM。 我这里用的是16bit的量化模型&#xff0c;…

是德科技KEYSIGHT N5234B网络分析仪

181/2461/8938产品概述&#xff1a; 描述 主要特性和功能 对无源元件和简单有源器件进行基本分析在成本敏感型应用中以高达43.5 GHz的高精度测量S参数获得全球最佳的微波制造性价比为信号完整性测量和材料表征配置经济的解决方案使用多点触控显示屏和直观的用户界面加快对组…

librdkafka的简单使用

文章目录 摘要kafka是什么安装环境librdkafka的简单使用生产者消费者 摘要 本文是Getting Started with Apache Kafka and C/C的中文版&#xff0c; kafka的hello world程序。 本文完整代码见仓库&#xff0c;这里只列出producer/consumer的代码 kafka是什么 本节来源&#…

踏入网页抓取的旅程:使用 grequests 构建 Go 视频下载器

引言 在当今数字化的世界中&#xff0c;网页抓取技术变得越来越重要。无论是获取数据、分析信息&#xff0c;还是构建自定义应用程序&#xff0c;我们都需要从互联网上抓取数据。本文将介绍如何使用 Go 编程语言和 grequests 库来构建一个简单的 Bilibili 视频下载器&#xff…

UE4_碰撞_碰撞蓝图节点——Line Trace For Objects(对象的线条检测)

一、Line Trace For Objects&#xff08;对象的线条检测&#xff09;&#xff1a;沿给定线条执行碰撞检测并返回遭遇的首个命中&#xff0c;这只会找到由Object types指定类型的对象。注意他与Line Trace By Channel(由通道检测线条&#xff09;的区别&#xff0c;一个通过Obje…

AI如何影响装饰器模式与组合模式的选择与应用

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》《MYSQL应用》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 &#x1f680; 转载自热榜文章&#xff1a;设计模式深度解析&#xff1a;AI如何影响…

腾讯云轻量2核2G3M云服务器优惠价格61元一年,限制200GB月流量

腾讯云轻量2核2G3M云服务器优惠价格61元一年&#xff0c;配置为轻量2核2G、3M带宽、200GB月流量、40GB SSD盘&#xff0c;腾讯云优惠活动 yunfuwuqiba.com/go/txy 活动链接打开如下图&#xff1a; 腾讯云轻量2核2G云服务器优惠价格 腾讯云&#xff1a;轻量应用服务器100%CPU性能…