IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> WinHttp c++ 介绍及应用 -> 正文阅读

[网络协议]WinHttp c++ 介绍及应用

一、HTTP协议介绍

http协议的底层协议是TCP协议。TCP协议是基于数据流的传输方式。其又叫做“超文本传输协议”,为什么呢,因为它是将超文本标记语言(HTML)文档从Web服务器传送到客户端的浏览器,通过因特网传送万维网文档的数据传送协议。

1. url 统一资源定位器

理论介绍

URL,全称是UniformResourceLocator,中文叫统一资源定位符。它主要是用来表示互联网上的某一个资源的位置的。例如:https://space.bilibili.com/438671228?spm_id_from=333.1007.0.0

便是一个url,其中:

?”https://“ 表示的是通信协议,有的是http协议,这个就是https协议(简单理解就是http传输的数据又进行了ssl加密,数据传输更安全)

?”space.bilibili.com“ 表示的是域名,域名和ip地址是一一对应的,因为ip地址是类似于这种”140.82.114.3“ 的一串数字,人们很难记忆(例如访问百度主页,你是用xxx.xxx.xxx.xxx简单呢,还是用baidu.com简单呢,肯定是后者啦),所以人们通过DNS解析来将原来的ip地址和域名绑定再一起,这样子访问就简单多啦。(http://127.0.0.1:8080/ 但是例如这种网址,可能会在域名后面添加 ":" 然后后面跟上一个数字,这个表示的是服务器的端口号,默认http协议是80端口,其他的还有好多,自己有兴趣可以查一查)

”/438671228“ 这一部分内容叫做虚拟目录(也叫做虚拟文件吧),就像是你电脑访问文件夹的目录一样,通常域名后面开始到 “?” 或者 “#” 为止中间的部分(上面这个链接就是到 “?” 结束)。

“?spm_id_from=333.1007.0.0” 后面这部分叫做参数部分。不同的参数之间用 ”&“ 符号分开,上面这个只有一个参数,所以没有?”&“ 符号。每一个参数对应的值用 “=” 连接。

有的后面还会有 “#” ,其代表的是锚部分,这个我也不太懂啊哈哈哈哈,不常用基本。

例子介绍

为了大家更好的理解url,下面我按照格式瞎编一个链接大家看一下:https://GaoZhong.com/HighSchoolSophomore/ClassEight?student=DounkeyBall&year=2017

这个链接的意思就是:采用https协议连接 GaoZhong.com 这个网站的HighSchoolSophomore目录下的ClassEight文件,访问的参数student是DounkeyBall,参数year是2017。(当然有的url可能会出现%,类似这种的:https://GaoZhong.com/%E9%AB%98%E4%BA%8C/%E5%85%AB%E7%8F%AD?%E5%AD%A6%E7%94%9F=%E9%A9%B4%E7%90%83&%E5%B9%B4%E4%BB%BD=2017

这是因为url不支持直接输入中文汉字,所有的中文汉字都被url编码成 "%??" 的形式了,有兴趣的可以解码看看内容,当然这个网址是我瞎编的,肯定访问不了哈哈哈哈哈)

2. http的请求(Request)

所有的用户通过http协议来和服务器交互都是采用Request的方式来进行的。

GET请求

客户端向服务器发起的一个HTTP请求主要包括以下几个内容:请求行(request line)、请求头部(header)、空行请求数据四个部分组成。

例如下面这个是访问一个天气接口(http://www.weather.com.cn/data/sk/101010100.html)的请求头内容:

GET /data/sk/101010100.html HTTP/1.1
Host: www.weather.com.cn
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.74 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7

(大家一定要注意其中的空格和换行,都是非常重要的)

第一行内容为请求行,内容包括:请求方式(GET)——请求有很多种,不同的请求对应着不同的功能,例如还有POST,PUT,OPTION等,自己可以具体查阅以下“http请求类型”,访问资源(/data/sk/101010100.html),http协议的版本(HTTP/1.1)——其他还有1.0版本,不过现在大多都1.1版本了。

下一行叫做请求头,请求头的格式为:“字段: 值”

第一个字段(首次出现这个名词哦,认真记)是 “Host”,后面的值是域名(有时候也叫做主机地址,主机ip地址等名字)。后面的几行都是http的头部字段以及对应的字段值,例如:Connection字段表示连接方式,User-Agent表示请求发起者的类型(这个请求是电脑发出的,里面就包含windows,如果是手机可能是android等内容),具体其他各个字段什么意思可以自己查阅以下。

所有的请求头结束后需要有一行空行表示请求头的内容发送结束了!(这个很重要哦)

POST请求

这个请求大部分和上面的一样,一点点小差异哦。

POST / HTTP1.1
Host:www.wrox.com
User-Agent:Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)
Content-Type:application/x-www-form-urlencoded
Content-Length:40
Connection: Keep-Alive

name=Professional%20Ajax&publisher=Wiley

POST请求主要用于向服务器上传消息。和上面的GET请求相比可以发现,请求头里面多了一个叫做Content-Length字段,表示请求头后面数据的长度,单位是字节(必须和后面发送的数据量长度保持一致,如果不一致的话,额,我也不知道有什么问题,自己看看吧)。

消息主体(也叫请求数据)和http请求头(就是上面的一团内容)之间必须要有一行空行来分隔开。消息主体即:“name=Professional%20Ajax&publisher=Wiley”。

响应消息Response

响应消息主要包含四部分:状态行、消息报头、空行和响应正文。这是那个天气网站的响应消息,我把它贴过来。

HTTP/1.1 200 OK
Date: Thu, 17 Mar 2022 10:54:48 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: keep-alive
Server: openresty
Age: 3867717
X-CCDN-CacheTTL: 2592000
X-Xss-Protection: 1
nginx-hit: 1
via: CHN-GDguangzhou-CMCC7-CACHE11[2],CHN-GDguangzhou-CMCC7-CACHE59[0,TCP_HIT,0],CHN-SH-GLOBAL1-CACHE37[23],CHN-SH-GLOBAL1-CACHE76[0,TCP_HIT,22]
x-hcs-proxy-type: 1
Content-Encoding: gzip

{"weatherinfo":{"city":"北京","cityid":"101010100","temp":"27.9","WD":"南风","WS":"小于3级","SD":"28%","AP":"1002hPa","njd":"暂无实况","WSE":"<3","time":"17:55","sm":"2.1","isRadar":"1","Radar":"JC_RADAR_AZ9010_JB"}}

其中状态行包括:http协议版本号(HTTP/1.1),状态码(200),状态消息(OK)。其中状态码有很多,常用的有:101(主要用于升级协议,后面会讲到),200(正确处理请求),301/302(重定向),404(找不到访问的url)。

下面的空行之间的内容是消息报头,和请求头的格式一样,但是字段内容有差异,具体含义自己可以查阅资料深入了解。隔一个空行后面是服务器返回的真正内容(本例子返回的是北京城市的天气信息)。

二、WinHttp函数的官方介绍

官方的document地址:Using WinHTTP - Win32 apps | Microsoft Docs

为了防止由于时间等不可控因素导致的在线文档不可以使用,此处放一个百度网盘的地址:

链接:https://pan.baidu.com/s/1wkhV4rGj_AGrNygVsNy2Sg?
提取码:w7ux

注意事项:

1. 使用工程需要包含的头文件以及静态库如下:

控制台程序的头文件包含(注意先后顺序也别弄错):

#include <iostream>
#include <windows.h>
#include <winhttp.h>
#include <websocket.h>

#pragma comment(lib, "Websocket.lib")
#pragma comment(lib, "winhttp.lib")
using namespace std;

2. 注意如果出现无法找到相关头文件,需要重定向一下目标方案,我用的是vs2019。

3. 如何能够看到我们的发送请求的内容呢,需要安装一个叫做fidder的程序,是个免费的程序,挺好使的。

Fiddler | Web Debugging Proxy and Troubleshooting Solutions

WinHttp SessionWinHTTP Sessions Overview - Win32 apps | Microsoft Docs

?我们用来测试的网站是:http://www.weather.com.cn/data/sk/101010100.html

其中需要修改的地方有几处:

1. 增加宏定义作为发起http请求头的USER-AGENT字段内容(不修改服务器可能不会相应你的请求):

#define CHROME_USER_AGENT TEXT("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36")

?2. 修改代码中部分内容

?效果:

?

URL的拆分和构建Uniform Resource Locators (URLs) in WinHTTP - Win32 apps | Microsoft Docs

?注意方框的内容是我修改的内容,运行结果:

现在我们就完成了url的拆分了,接下来看看怎么用这个拆分结果:

,再次运行是不是发现也可以跑呢,现在可以把那个url换成随意的url都可以自动拆分了,是不是很方便呢,ok,我们接着往下走!

http相应的编码转换

改变数据接收的逻辑,自己处理接收数据的内存空间分配

部分重点函数说明:

WinHttpReceiveResponseWinHttpReceiveResponse function (winhttp.h) - Win32 apps | Microsoft Docs

WinHttpQueryDataAvailableWinHttpQueryDataAvailable function (winhttp.h) - Win32 apps | Microsoft Docs

修改接收消息的部分细节逻辑:

主要利用vector来进行内存管理,关于vector的具体用法及函数介绍,参见:vector 类 | Microsoft Docs

修改的代码内容为:

#include <iostream>
#include <windows.h>
#include <winhttp.h>
#include <websocket.h>
#include <vector>

#pragma comment(lib, "Websocket.lib")
#pragma comment(lib, "winhttp.lib")
using namespace std;

#define CHROME_USER_AGENT TEXT("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36")


int main()
{
	//http://www.weather.com.cn/data/sk/101010100.html

	DWORD dwSize = 0;
	DWORD dwDownloaded = 0;
	LPSTR pszOutBuffer;
	BOOL  bResults = FALSE;
	HINTERNET  hSession = NULL,
		hConnect = NULL,
		hRequest = NULL;

	// Use WinHttpOpen to obtain a session handle.
	hSession = WinHttpOpen(CHROME_USER_AGENT,
		WINHTTP_ACCESS_TYPE_AUTOMATIC_PROXY /* <==WINHTTP_ACCESS_TYPE_DEFAULT_PROXY */,
		WINHTTP_NO_PROXY_NAME,
		WINHTTP_NO_PROXY_BYPASS, 0);

	// Specify an HTTP server.
	if (hSession)
		hConnect = WinHttpConnect(hSession, L"www.weather.com.cn"/*www.weather.com.cn*/,
			INTERNET_DEFAULT_HTTP_PORT/* <==INTERNET_DEFAULT_HTTPS_PORT */, 0);
	printf("connect successful...\n");

	// Create an HTTP request handle.
	if (hConnect)
		hRequest = WinHttpOpenRequest(hConnect, L"GET", L"/weather/101030100.shtml"/*/data/sk/101010100.html*/,
			NULL, WINHTTP_NO_REFERER,
			WINHTTP_DEFAULT_ACCEPT_TYPES,
			WINHTTP_FLAG_REFRESH | 0 /* <==WINHTTP_FLAG_SECURE */);
	printf("open request successful...\n");

	// Send a request.
	if (hRequest)
		bResults = WinHttpSendRequest(hRequest,
			WINHTTP_NO_ADDITIONAL_HEADERS, 0,
			WINHTTP_NO_REQUEST_DATA, 0,
			0, 0);
	printf("send request successful...\n");

	// End the request.
	if (bResults)
		bResults = WinHttpReceiveResponse(hRequest, NULL);
	printf("response received...\n");

	//create the buffer to store the data received
	vector<BYTE> bufferReceive;		
	int iDataReceive;
	//作为区域的终止字符
	bufferReceive.resize(1);
	iDataReceive = 0;

	// Keep checking for data until there is nothing left.
	if (bResults)
	{
		do
		{
			// Check for available data.
			dwSize = 0;
			if (!WinHttpQueryDataAvailable(hRequest, &dwSize))
				printf("Error %u in WinHttpQueryDataAvailable.\n",
					GetLastError());

			//=========================== new code here ===============================
			

			// Allocate space for the buffer.
			pszOutBuffer = new char[dwSize + 1];
			if (!pszOutBuffer)
			{
				printf("Out of memory\n");
				dwSize = 0;
			}
			else
			{
				// Read the data.
				ZeroMemory(pszOutBuffer, dwSize + 1);

				if (!WinHttpReadData(hRequest, (LPVOID)pszOutBuffer,
					dwSize, &dwDownloaded))
					printf("Error %u in WinHttpReadData.\n", GetLastError());
				else
				{
					//Here we can record how many times this is called
 					printf("\nreceiving the data...\n");
// 					printf("%s", pszOutBuffer);
					bufferReceive.resize(bufferReceive.size() + dwDownloaded);
					CopyMemory(&bufferReceive[iDataReceive] , pszOutBuffer, dwDownloaded);
					iDataReceive += dwDownloaded;
				}	

				// Free the memory allocated to the buffer.
				delete[] pszOutBuffer;
			}

			//===========================================================================

			//========================== original code here ==========================
			
// 			// Allocate space for the buffer.
// 			pszOutBuffer = new char[dwSize + 1];
// 			if (!pszOutBuffer)
// 			{
// 				printf("Out of memory\n");
// 				dwSize = 0;
// 			}
// 			else
// 			{
// 				// Read the data.
// 				ZeroMemory(pszOutBuffer, dwSize + 1);
// 
// 				if (!WinHttpReadData(hRequest, (LPVOID)pszOutBuffer,
// 					dwSize, &dwDownloaded))
// 					printf("Error %u in WinHttpReadData.\n", GetLastError());
// 				else
// 					printf("%s", pszOutBuffer);
// 
// 				// Free the memory allocated to the buffer.
// 				delete[] pszOutBuffer;
// 			}
//			//========================================================================

		} while (dwSize > 0);
	}
	
	//print all the data received
	printf("\nall data received are as follows: \n");
	printf("%s", &bufferReceive.front());
	printf("\nrequest completed!\n");

	// Report any errors.
	if (!bResults)
		printf("Error %d has occurred.\n", GetLastError());

	// Close any open handles.
	if (hRequest) WinHttpCloseHandle(hRequest);
	if (hConnect) WinHttpCloseHandle(hConnect);
	if (hSession) WinHttpCloseHandle(hSession);
}



修改后的效果:

ok,现在我们换一个网站再来测试一下你就可以更加清晰的看出来效果了,这个网站由于数据量比较少,所以客户端一次便接收完成了。修改代码中的如下位置内容(访问链接为:http://www.weather.com.cn/weather/101030100.shtml):

?效果为:

这里我们可以很明显的看出来,多次打印了receiving the data...内容,表明所有的内容并非一次读取完成,如果我们不能够很好的处理内存的话我们是无法很好的处理整个内容的编码解码操作的。(因为可能会由于部分文本接收不完整导致编码解码出现乱码)?

关于内存操作的其他方法简介:

new 和 delete 的搭配使用(利用BYTE申请空间)

操作内存块的相关函数

ZeroMemory:ZeroMemory macro (Windows) | Microsoft Docs

CopyMemory:CopyMemory function (Windows) | Microsoft Docs

memset:memset | Microsoft Docs,

memcopy:memcpy, wmemcpy | Microsoft Docs

前面已经用到,此处不具体讲解。

数据的编码转换

下面的内容,我不太想告诉你细节问题,因为这个东西听起来会很无聊,如果你是在特别特别想知道细节的话,那么你可以自己去查一查相关的资料哈哈哈哈哈,我不要讲这种东西,因为网络上好多这种编码解码都是错误的,当初摸索了好久真的是。

UNICODE ==> UTF8 or ANSI

string UnicodeToUTF8(wstring src)
{
	string sUTF8;
	int iLenBytes = 0;

	// 获得结果字符串所需字符个数,参数 -1 使函数自动计算 szUnicode 的长度
	iLenBytes = WideCharToMultiByte(CP_UTF8, 0, src.c_str(), -1, NULL, 0, NULL, NULL);
	// 分配结果字符串的空间
	sUTF8.resize(iLenBytes);
	// 转换:用 aLenBytes 因函数需要的是字节数非字符数
	WideCharToMultiByte(CP_UTF8, 0, src.c_str(), -1, (LPSTR)sUTF8.c_str(), iLenBytes, NULL, NULL);

	return sUTF8;
}
string UnicodeToANSI(wstring src)
{
	string sUTF8;
	int iLenBytes = 0;

	// 获得结果字符串所需字符个数,参数 -1 使函数自动计算 szUnicode 的长度
	iLenBytes = WideCharToMultiByte(CP_ACP, 0, src.c_str(), -1, NULL, 0, NULL, NULL);
	// 分配结果字符串的空间
	sUTF8.resize(iLenBytes);
	// 转换:用 aLenBytes 因函数需要的是字节数非字符数
	WideCharToMultiByte(CP_ACP, 0, src.c_str(), -1, (LPSTR)sUTF8.c_str(), iLenBytes, NULL, NULL);

	return sUTF8;
}

UTF8 or ANSI ==>??UNICODE

wstring UTF8ToUnicode(string src)
{
	wstring sUnicode;
	int iLen = 0;

	// 获得结果字符串所需字符个数,参数 -1 使函数自动计算 szAnsi 的长度
	iLen = MultiByteToWideChar(CP_UTF8, 0, src.c_str(), -1, NULL, 0);
	// 分配结果字符串的空间
	sUnicode.resize(iLen + 1);
	// 转换
	MultiByteToWideChar(CP_UTF8, 0, src.c_str(), -1, (LPWSTR)sUnicode.c_str(), iLen);
	sUnicode[iLen] = '\0';

	return sUnicode;
}
wstring ANSIToUnicode(string src)
{
	wstring sUnicode;
	int iLen = 0;

	// 获得结果字符串所需字符个数,参数 -1 使函数自动计算 szAnsi 的长度
	iLen = MultiByteToWideChar(CP_ACP, 0, src.c_str(), -1, NULL, 0);
	// 分配结果字符串的空间
	sUnicode.resize(iLen + 1);
	// 转换
	MultiByteToWideChar(CP_ACP, 0, src.c_str(), -1, (LPWSTR)sUnicode.c_str(), iLen);
	sUnicode[iLen] = '\0';

	return sUnicode;
}

string <==> wstring

wstring stringTowstring(string src)
{
	return ANSIToUnicode(src);
}
string wstringTostring(wstring src)
{
	return UnicodeToANSI(src);
}

url编码解码

unsigned char CharToHex(unsigned char x) {
	return (unsigned char)(x > 9 ? x + 55 : x + 48);
}
string UrlEncode(const string src)
{
	string str_encode;

	unsigned char* p = (unsigned char*)src.c_str();
	unsigned char ch;
	while (*p) {
		ch = (unsigned char)*p;
		if (*p == ' ')
		{
			str_encode += '+';
		}
		else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9')/*IsAlphaNumber(ch)*/ 
			|| strchr("-_.~!*'();:@&=+$,?#", ch))		//其中 /[] 字符被去掉
		{
			str_encode += *p;
		}
		else
		{
			str_encode += '%';
			str_encode += CharToHex((unsigned char)(ch >> 4));
			str_encode += CharToHex((unsigned char)(ch % 16));
		}
		++p;
	}

	return str_encode;
}
string UrlDecode(const string src)
{
	string str_decode;

	int i;
	char* cd = (char*)src.c_str();
	char p[2];
	for (i = 0; i < (int)strlen(cd); i++) {
		memset(p, '\0', 2);
		if (cd[i] != '%') {
			str_decode += cd[i];
			continue;
		}
		p[0] = cd[++i];
		p[1] = cd[++i];
		p[0] = p[0] - 48 - ((p[0] >= 'A') ? 7 : 0) - ((p[0] >= 'a') ? 32 : 0);
		p[1] = p[1] - 48 - ((p[1] >= 'A') ? 7 : 0) - ((p[1] >= 'a') ? 32 : 0);
		str_decode += (unsigned char)(p[0] * 16 + p[1]);
	}

	return str_decode;
}

解码测试

ok,我们把访问链接改为最原始的那个:http://www.weather.com.cn/data/sk/101010100.html,然后加入如下的代码:

?效果:

获取http请求头的信息

WinHttpQueryHeaders:WinHttpQueryHeaders function (winhttp.h) - Win32 apps | Microsoft Docs

三、实现自己的WinHttp class

Winhttp的更多用法扩展

如何使用Https

如何设置请求的代理服务器

如何设置winHttp函数的更多参数

异步实现WinHttp Session

HTTP协议升级为Websocket协议

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2022-03-30 19:06:09  更:2022-03-30 19:08:56 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 5:31:27-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码