使用WinInet向 ASP.Net Web服务器 多线程上传文件

本文介绍了一种高效的大文件分块上传方案,通过客户端将文件分割成小块并发上传,减轻服务器内存压力。该方案利用唯一标识码跟踪文件块,确保数据完整性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

众所周知,如果需要向WEB服务器上传文件,一般选用下列2种方式。

1. 使用HTTP PUT指令

2. 模拟页面的form提交

 

第一种需要配置服务器,略过。

第二种需要使用WinInet根据HTTP协议,拼除POST BODY后提交。

 

对于第二种,在ASP.Net里面特麻烦。

1. 需要模拟页面的VIEWSTATE,模拟不成功就不行

2. ASP.Net对每个请求有最大长度限制,这个值默认为4MB,但可以在web.config中修改

3. 文件在上传过程中并没有直接写入磁盘,而是先放入了内存,等到全部上传结束再写入磁盘。所以如果传输超大的文件对服务器性能影响很大

 

 

本文的做法是:

客户端不需要模拟form,将大文件分成等大的小块(如64K),使用多线程将这些小块上传到服务器后,服务器再拼合起来。

 

---------------------------------------------------------

流程:

1. 客户端:需要向服务器上传一个文件,首先调用服务器的某一个页面(如BeginUpload.aspx),通知此文件的大小(bytes)

 

2.服务器:服务器收到此请求,首先验证客户端权限,然后在自定义的文件夹中按照请求中提供的大小创建一个空文件,并返回一个唯一标示码到客户端。

 

3.客户端:收到服务器返回成功后,记录下此次上传的唯一标识码。

 

4.客户端:将需要上传的这个文件分成大小相等的文件块(如64K)。(这个过程只是一个逻辑上的过程,实际的做法并不需要分块,可以直接使用内存映射文件或者将文件直接读入到虚拟内存以加快速度)

 

5.客户端:开启一个领导者-跟随者线程池。领导者线程负责要上传文件块的调度,而跟随者线程负责自己分配到的文件块上传。

 

6.客户端,跟随者线程:读取自己分配到的文件块,向服务器的特定路径或者页面POST文件内容。

这个POST的HEADER或者QueryString里面起码要包含这几个参数:唯一标识码、当前文件块的区间。

 

7.服务端:收到跟随者线程的请求,以共享方式打开临时文件夹中的文件,写入当前文件块。

 

8.客户端,领导者线程:检测到文件块全部上传完毕,则向服务器某一个页面报告(如EndUpload.aspx),此文件上传结束,清理资源。

 

9.服务器:收到文件上传结束的通知,将文件从临时文件夹移动到需要的位置。

 

10. 服务器周期性地清理临时文件夹中的过期文件。

 

--------------------------------------------------------------------

上面的流程是多线程分块并行上传的基本流程。也可以在此基础上进一步加入CRC32验证文件完整性的功能。如果要简化流程,不需要分块上传,只需要直接进行第6步操作就可以了。

 

 

对于第6部,在服务端,可以使用一个*.aspx页面或者一个IHttpHandler来处理请求。

参考下列代码,参数以及其它部分都已经略掉。

 

1.         protected void Page_Load(object sender, EventArgs e)   

2.         {   

3.             Stream stream = Page.Request.InputStream;   

4.             byte[] buffer = new byte[stream.Length];   

5.             stream.Read(buffer, 0, buffer.Length);   

6.             // TO DO: Something else   

7.         }  

 

对于其它B/S平台,也是类似的方式。

 

客户端代码,我自己封装了一下,这里只列出关键代码。

 

  1. void CHttpClient::OpenConnection( LPCTSTR lpszServer, UINT nPort)   
  2. {   
  3.     CloseConnection();   
  4.     m_hSession = ::InternetOpen(  USER_AGENT   
  5.         , INTERNET_OPEN_TYPE_PRECONFIG   
  6.         , NULL   
  7.         , NULL   
  8.         , 0   
  9.         );   
  10.     if( !m_hSession )   
  11.         throw CCustomException(_T("Error: Failed to connect to the server, InternetOpen failed."));   
  12.     m_hConnect = ::InternetConnect( m_hSession   
  13.         , lpszServer   
  14.         , nPort   
  15.         , NULL   
  16.         , NULL   
  17.         , INTERNET_SERVICE_HTTP   
  18.         , NULL   
  19.         , NULL   
  20.         );   
  21.     if( !m_hConnect )   
  22.         throw CCustomException(_T("Error: Failed to connect to the server, InternetConnect failed."));   
  23. }   
  24. void CHttpClient::CloseConnection(void)   
  25. {   
  26.     if( m_hSession )   
  27.     {   
  28.         ::InternetCloseHandle(m_hSession);   
  29.         m_hSession = NULL;   
  30.     }   
  31.     if( m_hConnect )   
  32.     {   
  33.         ::InternetCloseHandle(m_hConnect);   
  34.         m_hConnect = NULL;   
  35.     }   
  36. }   
  37. void CHttpClient::PostBuffer( LPCTSTR lpszPath, LPBYTE lpBuffer, DWORD dwSize)   
  38. {   
  39.     ASSERT( m_hSession && m_hConnect );   
  40.     HINTERNET hRequest = ::HttpOpenRequest( m_hConnect   
  41.         , _T("POST")   
  42.         , lpszPath   
  43.         , NULL   
  44.         , NULL   
  45.         , NULL   
  46.         , INTERNET_FLAG_NO_CACHE_WRITE   
  47.         , 0   
  48.         );   
  49.     if(!hRequest)   
  50.         throw CCustomException(_T("Error: Failed to POST data to server, HttpOpenRequest failed."));   
  51.     INTERNET_BUFFERS stBuffers = {0};   
  52.     stBuffers.dwStructSize = sizeof(stBuffers);    
  53.     stBuffers.Next = NULL;    
  54.     stBuffers.lpcszHeader = NULL;   
  55.     stBuffers.dwHeadersLength = 0;   
  56.     stBuffers.dwHeadersTotal = 0;   
  57.     stBuffers.lpvBuffer = NULL;                   
  58.     stBuffers.dwBufferLength = 0;   
  59.     stBuffers.dwBufferTotal = dwSize;   
  60.     stBuffers.dwOffsetLow = 0;   
  61.     stBuffers.dwOffsetHigh = 0;   
  62.     BOOL bRet = ::HttpSendRequestEx( hRequest, &stBuffers, NULL, 0, 0);   
  63.     if(!bRet)   
  64.     {   
  65.         ::InternetCloseHandle(hRequest);   
  66.         throw CCustomException(_T("Error: Failed to POST data to server, HttpSendRequestEx failed."));   
  67.     }   
  68.     DWORD dwSent = 0;   
  69.     DWORD dwBytesWritten = 0;   
  70.     while(dwSent < dwSize)   
  71.     {   
  72.         bRet = ::InternetWriteFile( hRequest   
  73.             , (LPBYTE)(lpBuffer + dwSent)   
  74.             , dwSize - dwSent   
  75.             , &dwBytesWritten   
  76.             );   
  77.         if( bRet )   
  78.             dwSent += dwBytesWritten;   
  79.     }   
  80.     bRet = ::HttpEndRequest(hRequest, NULL, 0, 0);   
  81.     ::InternetCloseHandle(hRequest);   
  82.     if( !bRet )   
  83.         throw CCustomException(_T("Error: Failed to POST data to server, HttpEndRequest failed."));   
  84. }  

  1. const int BUFFER_SIZE = 1024000;   
  2. CHttpClient oClient;   
  3. oClient.OpenConnection( _T("127.0.0.1"), 4638 );   
  4. BYTE * pBuffer = new BYTE[BUFFER_SIZE];   
  5. for (UINT i = 0; i < BUFFER_SIZE; i++)   
  6. {   
  7.     pBuffer[i] = i%0xFF;   
  8. }   
  9. oClient.PostBuffer( _T("/1/Default.aspx"), pBuffer, BUFFER_SIZE);   
  10. delete [] pBuffer;  

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值