C# HTTP 文件上传、下载服务器

程序需要管理员权限,vs需要管理员打开

首次运行需要执行以下命令注册URL(管理员命令行)

bash 复制代码
netsh advfirewall firewall add rule name="FileShare" dir=in action=allow protocol=TCP localport=8000
ipconfig | findstr "IPv4"
csharp 复制代码
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main()
    {
        string path = @"D:\8000"; // 共享目录
        int port = 8000;
        HttpListener listener = new HttpListener();
        listener.Prefixes.Add($"http://+:{port}/");
        listener.Start();
        Console.WriteLine($"服务器已启动: http://{GetLocalIP()}:{port}");

        while (true)
        {
            var context = listener.GetContext();
            if (context.Request.HttpMethod == "POST")
            {
                ProcessUploadRequest(context, path);
            }
            else
            {
                ProcessRequest(context, path);
            }
        }
    }

    static void ProcessUploadRequest(HttpListenerContext context, string rootPath)
    {
        try
        {
            // 获取上传文件名
            string filename = context.Request.Headers["X-FileName"] ?? Path.GetRandomFileName();
            string filePath = Path.Combine(rootPath, filename);

            using (FileStream fs = new FileStream(filePath, FileMode.Create))
            {
                context.Request.InputStream.CopyTo(fs);
            }

            SendResponse(context, HttpStatusCode.Created, "文件上传成功");
        }
        catch (Exception ex)
        {
            SendResponse(context, HttpStatusCode.InternalServerError, $"上传失败: {ex.Message}");
        }
    }

    static void ProcessRequest(HttpListenerContext context, string rootPath)
    {
        try
        {
            string requestPath = context.Request.Url.LocalPath.TrimStart('/');
            string fullPath = Path.Combine(rootPath, requestPath);

            // 处理文件下载
            if (File.Exists(fullPath))
            {
                using (FileStream fs = File.OpenRead(fullPath))
                {
                    context.Response.ContentType = GetMimeType(Path.GetExtension(fullPath));
                    context.Response.AddHeader("Content-Disposition", $"attachment; filename=\"{Path.GetFileName(fullPath)}\"");
                    fs.CopyTo(context.Response.OutputStream);
                }
            }
            // 处理目录浏览
            else if (Directory.Exists(fullPath))
            {
                string directoryList = GenerateDirectoryListing(fullPath, context.Request.Url.AbsoluteUri);
                byte[] buffer = Encoding.UTF8.GetBytes(directoryList);
                context.Response.ContentType = "text/html; charset=utf-8";
                context.Response.OutputStream.Write(buffer, 0, buffer.Length);
            }
            else
            {
                SendResponse(context, HttpStatusCode.NotFound, "资源不存在");
            }
        }
        catch (Exception ex)
        {
            SendResponse(context, HttpStatusCode.InternalServerError, $"处理请求失败: {ex.Message}");
        }
        finally
        {
            context.Response.Close();
        }
    }

    static string GenerateDirectoryListing(string path, string baseUrl)
    {
        var sb = new StringBuilder();
        sb.Append("<html><head><title>文件列表</title></head><body>");
        sb.Append($"<h1>文件列表 - {path}</h1><ul>");

        // 添加返回上级目录链接
        if (Directory.GetParent(path) != null)
        {
            sb.Append($"<li><a href='{baseUrl}../'>[上级目录]</a></li>");
        }

        // 遍历目录
        foreach (var dir in Directory.GetDirectories(path))
        {
            string dirName = Path.GetFileName(dir);
            sb.Append($"<li><a href='{baseUrl}{dirName}/'>[目录] {dirName}/</a></li>");
        }

        // 遍历文件
        foreach (var file in Directory.GetFiles(path))
        {
            string fileName = Path.GetFileName(file);
            sb.Append($"<li><a href='{baseUrl}{fileName}'>{fileName}</a></li>");
        }

        sb.Append("</ul></body></html>");
        return sb.ToString();
    }

    static void SendResponse(HttpListenerContext context, HttpStatusCode statusCode, string message)
    {
        byte[] buffer = Encoding.UTF8.GetBytes(message);
        context.Response.StatusCode = (int)statusCode;
        context.Response.ContentType = "text/plain; charset=utf-8";
        context.Response.OutputStream.Write(buffer, 0, buffer.Length);
    }

    /***   
    * MIME类型映射类
    * MIME(Multipurpose Internet Mail Extensions)‌ 
    * 类型是一种标准化的方式,用于描述互联网上传输的内容类型(例如文本、图像、视频等)。
    * 它的核心作用是告诉浏览器或客户端‌如何正确处理文件‌(例如直接显示、下载、调用外部程序打开等)
    */
    static string GetMimeType(string extension)
    {
        var mimeTypes = new Dictionary<string, string>
        {
            { ".txt", "text/plain" },
            { ".pdf", "application/pdf" },
            { ".doc", "application/msword" },
            { ".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document" },
            { ".xls", "application/vnd.ms-excel" },
            { ".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" },
            { ".png", "image/png" },
            { ".jpg", "image/jpeg" },
            { ".jpeg", "image/jpeg" },
            { ".gif", "image/gif" },
            { ".zip", "application/zip" }
        };
        return mimeTypes.TryGetValue(extension.ToLower(), out string mime) ? mime : "application/octet-stream";
    }

    static string GetLocalIP()
    {
        return Dns.GetHostEntry(Dns.GetHostName())
            .AddressList.First(ip => ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
            .ToString();
    }
}

文件上传

curl.exe -X POST -H "X-FileName: Git-2.46.2-64-bit.exe" --data-binary "@C:\Users\Ins\Downloads\Git-2.46.2-64-bit.exe" http://192.168.1.242:8000/![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/5cee6643652b4d94ad4c8534673b97b1.png)

相关推荐
牛奶18 小时前
从一行字到改变世界:HTTP这三十年都经历了什么?
前端·http·http3
程序设计实验室18 小时前
C# 扩展方法只会写 this 吗?C# 14 新语法直接把扩展方法玩出了花
c#
牛奶20 小时前
浏览器到底在偷偷帮你做什么?——HTTP缓存与刷新机制
前端·http·浏览器
唐青枫21 小时前
C#.NET SignalR 深入解析:实时通信、Hub 与连接管理实战
c#·.net
唐宋元明清21881 天前
.NET Win32磁盘动态卷/跨区卷触发“函数不正确”问题排查
windows·c#·存储
hez20101 天前
Satori GC:同时做到高吞吐、低延时和低内存占用
c#·.net·.net core·gc·clr
唐青枫2 天前
C#.NET Channel 深入解析:高性能异步生产者消费者模型实战
c#·.net
韭菜炒大葱2 天前
前端经典面试题:从 URL 输入到页面展示,中间经历了什么?
前端·http·面试
小峥降临2 天前
Rokid UXR 的手势追踪虚拟中更真实的手实战开发【含 工程源码 和 最终完成APK】
c#
茶杯梦轩6 天前
从零起步学习RabbitMQ || 第二章:RabbitMQ 深入理解概念 Producer、Consumer、Exchange、Queue 与企业实战案例
服务器·后端·消息队列