Winform文件下载之WinINet

在C#中,除了webclient我们还可以使用一组WindowsAPI来完成下载任务。这就是Windows Internet,简称 WinINet。本文通过一个demo来介绍WinINet的基本用法和一些实用技巧。 

  • 系列文章

Winform文件下载之WebClient

  • 接口介绍

相比WebClient的用法,Win32API在使用时可能会烦琐一些。所以先把用到的API简单介绍一下。

资源的初始化和释放

InternetOpen

这是需要调用的第一个方法,它会初始化内部数据结构,为后面的调用做准备。

InternetCloseHandle

这个方法用来关闭使用中打开的Internet句柄,释放资源。

建立到服务器的连接

InternetOpenUrl

这是一个通用的函数,应用程序可以用它来请求数据(只要是WinINet支持的协议就可以)。尤其是当我们仅仅想要通过一个URL获取数据,而不关心通信协议相关的内容时,这个接口就特别合适。该方法会解析参数中的URL字符串,然后建立到服务器的连接,并准备下载由RUL标识的数据。

检查响应信息

HttpQueryInfo

检索与HTTP请求相关的报头信息。主要是查看请求是否成功。

读取响应内容

InternetReadFile

从 InternetOpenUrl打开的句柄中读取数据。

  • 下载过程

这里我们只介绍下载过程中的关键环节,完整的过程请参考本文的demo。

InternetOpenUrl

当请求与服务器建立连接时,我们重点考虑三个问题:请求的url,是否使用 RELOAD 标识, 客户端是否支持gzip压缩。

请求的url不用多说,这里直接请求一个http url.

我们不希望拿到客户端缓存中的数据,所以希望每次请求都能够从服务器重新下载。此时需要为InternetOpenUrl方法传入INTERNET_FLAG_RELOAD 标识。

当前绝大多数的web服务器都是支持gzip压缩的,我们的客户端当然也要能够解压缩服务器传回来的gzip格式的数据。所以我们要在请求中告诉服务器,客户端是能够处理gzip数据的。只有这样,服务器才会主动的返回gzip格式的数据。

代码如下:

string referer = "Referer: xxxxxx\nAccept-Encoding: gzip";

// INTERNET_FLAG_RELOAD -> 0x80000000

// 跳过缓存,强制从原始的服务器下载数据

hInetFile = NativeMethods.InternetOpenUrl(this._hInet, uri.AbsoluteUri, referer, referer.Length, 0x80000000, IntPtr.Zero);

HttpQueryInfo

接下来我们开始检查前面发送的请求返回的header中的信息。主要是:请求的资源是否存在,返回的数据有多长,返回的文件的原始名称是什么,返回的数据是以什么格式被压缩的。

我们先要通过检查返回的状态码来确定请求是否成功,也就是返回的是不是200。

byte[] content = new byte[BufferSize];

int count = BufferSize;

int temp = 0;

NativeMethods.HttpQueryInfo(hInetFile, 19, content, out count, out temp)

statuscode = Encoding.Unicode.GetString(content, 0, count);

正确返回时,statuscode应该是 “200”。

不要对HttpQueryInfo的第二个参数感到奇怪,为了获得请求的返回状态我们就得传入19。你可以参考Query Onfo Flags 。

用类似的方法可以得到返回数据的长度,原始的文件名称,返回数据的格式。

InternetReadFile

前面一切顺利的话就可以读取数据了。这个方法本身没什么可说的,但出于简化操作的目的,笔者对InternetReadFile进行了简单的封装。创建了一个继承自Stream的类MyInternetReadStream。在重写的 Read方法中调用InternetReadFile,并且添加了一个回调方法用来计算下载进度等信息。下面是代码概要,完整代码请参考demo。

public override int Read(byte[] buffer, int offset, int count)

{

    int dwNumberOfBytesToRead = Math.Min(BufferSize, count);

    int length = 0;

    NativeMethods.InternetReadFile(this._hInetFile, this._localBuffer, dwNumberOfBytesToRead, out length)

    Array.Copy(this._localBuffer, 0, buffer, offset, length);

    this._bytesReadCallback(length, this._contentLength);

    return length;

}

Gzip stream

前面我们提到,服务器可能返回的是经过gzip压缩的数据,这就需要我们先检查数据的格式。如果是gzip格式的数据就需要把它解压缩。其实这在C#中是很简单的,我们只要把刚才创建的MyInternetReadStream的实例传给GZipStream的构造函数,创建一个新的GZipStream实例就可以了。

private Stream GetInternetStream(IntPtr hInetFile)

{

    //检查数据是不是gzip格式

    string contentEncoding = MyWinInet.GetContentEncoding(hInetFile);

    if (contentEncoding.IndexOf("gzip", StringComparison.OrdinalIgnoreCase) != -1)

    {

        return new GZipStream(this.ForGZipReadStream(hInetFile), CompressionMode.Decompress, false);

    }

    …

}

private Stream ForGZipReadStream(IntPtr hInetFile)

{
return new MyWinInet.MyInternetReadStream(hInetFile, new MyWinInet.MyInternetReadStream.BytesReadCallback(this.BytesReadCallback));

}

至于计算下载进度,实时的下载速度的实现和Winform文件下载之WebClient 中的实现基本相同,请参考上文,或者直接看本文的demo。

总结:相比WebClient,使用WinINet接口要烦琐不少。当然也有一定的优势,比如前文中提到的代理问题,WinINet的默认设置就能处理好Credentials。不过在笔者看来,更重要的是我们可以选用不同的方式去处理下载问题。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏idba

ZanDB基于Celery定时任务的二次开发

ZanDB早期的任务需求中,大部分都是针对servant(跑在主机上的agent)做任务调度。也就是说,一期的任务系统,满足的是在特定时刻调用特定主机执行特定的...

1392
来自专栏微服务生态

深入淘宝Diamond之客户端架构解析

diamond是淘宝内部使用的一个管理持久配置的系统,它的特点是简单、可靠、易用,目前淘宝内部绝大多数系统的配置,由diamond来进行统一管理。 diamo...

1003
来自专栏JetpropelledSnake

Python入门之logging日志模块以及多进程日志

本篇文章主要对 python logging 的介绍加深理解。更主要是 讨论在多进程环境下如何使用logging 来输出日志, 如何安全地切分日志文件。 1. ...

8697
来自专栏比原链

Derek解读Bytom源码-protobuf生成比原核心代码

Gitee地址:https://gitee.com/BytomBlockchain/bytom

1023
来自专栏随心DevOps

[实战篇] Python 运维中使用并发

今天从大哥手里接了一个需求: 验证一下新的 Docker 镜像仓库(Docker Registry)是否迁移成功了 简单粗暴的方法就是拿到老仓库中的镜像列表(I...

41812
来自专栏烂笔头

Python爬虫—破解JS加密的Cookie

目录[-] 前言 在GitHub上维护了一个代理池的项目,代理来源是抓取一些免费的代理发布网站。上午有个小哥告诉我说有个代理抓取接口不能用了,返回状态521...

6217
来自专栏aoho求索

基于可靠消息方案的分布式事务(四):接入Lottor服务

在上一篇文章中,通过Lottor Sample介绍了快速体验分布式事务Lottor。本文将会介绍如何将微服务中的生产方和消费方服务接入Lottor。

2441
来自专栏编程

小白爬虫之爬虫快跑,多进程和多线程

使用多线程时好像在目录切换的问题上存在问题,可以给线程加个锁试试 Hello 大家好!我又来了。 你是不是发现下载图片速度特别慢、难以忍受啊!对于这种问题 一般...

2027
来自专栏前端说吧

Gulp安装流程、使用方法及cmd常用命令导览

3916
来自专栏大内老A

[WCF安全系列]谈谈WCF的客户端认证[用户名/密码认证]

对于基于Internet的应用,基于用户名和密码的认证方式是最为常用的,而WCF为你提供了不同模式的用户名认证方式。首先还是从用户凭证的表示说起。 一、用户名/...

2239

扫码关注云+社区