当前位置:   article > 正文

c++中如何接收和发送http请求_c++ http

c++ http

一、http的使用和背景

HTTP 的全称是 HyperText Transfer Protocol (超文本传输协议)的缩写,是一种建立在 TCP 上的无状态连接。HTTP 是互联网的基础协议,用于客户端与服务器之间的通信,它规定了客户端和服务器之间的通信格式,包括请求与响应的格式。但现在主流的使用还是在java后端开发中,c++中的开发较少的使用,此篇主要讲述http在c++中是如何接收和发送请求的。

在本文章中需要用到的第三方库为winhttp,如下:

http第三方库

二、导入winhttp第三方库

从上面的http第三方库下载对应的winhttp.lib和winhttp.dll,放入到项目同级文件夹,尽量使用一个文件夹管理所有的第三方dll和lib。操作完后,在vs的属性设置中,加入对应的lib和dll,此时,我是将文件夹命名为lib,如下图所示:
在这里插入图片描述
在这里插入图片描述
配置完对应的配置文件后,可以简单的尝试导入第三方库,如下代码:

#include<iostream>
#include"winhttp.h"
#pragma comment(lib,"winhttp.lib")
  • 1
  • 2
  • 3

经过上面操作即成功导入第三方库

三、winhttp访问流程

大致流程是:

1.通过WinHttpOpen来获得一个Session句柄;

2.使用该句柄通过WinHttpConnect连接服务器获得一个Connect句柄;

3.用得到的Connect句柄通过WinHttpOpenRequest打开Http请求,获得Request句柄;

4.使用这个Request句柄就可以和服务器发送接收数据了;

5.以上句柄需要依次关闭。

1.初始化winhttp

在使用WinHttp连接服务器之前,需要使用WinHttpOpen来进行初始化。WinHttpOpen将创建一个维护Http会话细节的会话环境(Session Context),并返回一个session handle。使用该句柄,WinHttpConnect可以连接到目标Http(Https)服务器。

注意 在请求特定的资源之前,WinHttpConnect并不会建立到目标服务器的实际连接。

示例如下:

	//初始化一个WinHTTP-session句柄,“IrregularPacking”为此句柄的名称
	hSession = WinHttpOpen(L"AmbitionTc Code", NULL, NULL, NULL, NULL);
	if (hSession == NULL) {
		cout << "Error:Open session failed: " << GetLastError() << endl;
		return -1;
	}

	//通过上述句柄连接到服务器,需要指定服务器IP和端口号。若连接成功,返回的hConnect句柄不为NULL。
	hConnect = WinHttpConnect(hSession, L"127.0.0.1", (INTERNET_PORT)80, 0);
	//注意:这里使用的是IP地址,也可以使用域名。
	//hConnect = WinHttpConnect(hSession, L"https://editor.csdn.net/", (INTERNET_PORT)80, 0);
	if (hConnect == NULL) {
		cout << "Error:Connect failed: " << GetLastError() << endl;
		return -1;
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

2.打开请求

WinHttpOpenRequest打开一个HTTP请求并且返回一个HINTERNET句柄以供其他HTTP函数使用。在调用WinHttpOpenRequest时并不会向服务器发送请求。实际上是WinHttpSendRequest建立了一个网络链接并发送请求。

示例如下:

//通过hConnect句柄创建一个hRequest句柄,用于发送数据与读取从服务器返回的数据。
//“Get”为请求方式,可以为"Post"
	hRequest = WinHttpOpenRequest(hConnect, L"Get", L"/GetSanyNestTask?token=123", NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0);
	if (hRequest == NULL) {
		cout << "Error:OpenRequest failed: " << GetLastError() << endl;
		return -1;
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

3.添加请求头部

WinHttpAddRequestHeaders方法能够向一个Http请求句柄添加一个或者多个请求头部。这一步我尝试过,也可以不要,但是在一些传输数据格式问题的情况下,必须要加入一个请求头部。这里给出一个常见的请求头,如下:

	LPCWSTR header = _T("Content-type: application/json; charset=utf-8/r/n");
	SIZE_T len = lstrlenW(header);

	bResults = WinHttpAddRequestHeaders(hRequest, header, DWORD(len), WINHTTP_ADDREQ_FLAG_ADD);
	if(!bResults)
	{
		cout << "Error:Headers failed: " << endl;
		return -1;
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

4.向服务器发送数据

要向服务器发送数据,在调用WinHttpOpenRequest时HTTP verb(HTTP method)应设为PUT或者POST。当调用WinHttpSendRequest时,dwTotalLength应设为发送数据的字节数。然后通过WinHttpWriteData来向服务器发送数据。

另外,可以通过设置WinHttpSendRequest的lpOptional参数为一个包含数据的缓冲区的地址来发布到服务器。使用这种方法时,必须设置WinHttpSendRequest的两个参数dwOptionalLength和dwTotalLength 的数据大小。以这种方式调用WinHttpSendRequest的话就不需要再调用WinHttpWriteData了。

下面给出使用WinHttpSendRequest发送数据的代码:

	string data = string(sendData);
	const void *ss = (const char *)data.c_str();
	//发送请求
	BOOL bResults;
	bResults = WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, const_cast<void*>(ss), data.length(), data.length(), 0);
	if (!bResults){
		cout << "Error:SendRequest failed: " << GetLastError() << endl;
		return -1;
	}
	else{
		//发送请求成功则准备接受服务器的response。注意:在使用 WinHttpQueryDataAvailable和WinHttpReadData前必须使用WinHttpReceiveResponse才能access服务器返回的数据
		bResults = WinHttpReceiveResponse(hRequest, NULL);
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

5.获取请求信息

WinHttpQueryHeaders函数允许应用程序检索HTTP请求的信息。

示例如下:

	// 获取服务器返回数据的header信息。这一步我用来获取返回数据的数据类型。
	LPVOID lpHeaderBuffer = NULL;
	DWORD dwSize = 0;
	if (bResults)
	{
		//(1) 获取header的长度
		WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_RAW_HEADERS_CRLF,
			WINHTTP_HEADER_NAME_BY_INDEX, NULL,
			&dwSize, WINHTTP_NO_HEADER_INDEX);

		//(2) 根据header的长度为buffer申请内存空间
		if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
		{
			lpHeaderBuffer = new WCHAR[dwSize / sizeof(WCHAR)];

			//(3) 使用WinHttpQueryHeaders获取header信息
			bResults = WinHttpQueryHeaders(hRequest,
				WINHTTP_QUERY_RAW_HEADERS_CRLF,
				WINHTTP_HEADER_NAME_BY_INDEX,
				lpHeaderBuffer, &dwSize,
				WINHTTP_NO_HEADER_INDEX);
		}
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

6.从Web读取到对应的资源

在获得请求后,可以使用WinHttpReadData从web端获得对应的数据,网上还有关于WinHttpQueryDataAvailable下载对应的资源的,这个可以自行尝试,这里主要使用第一个获取对应的字段资源。

实例如下:

	//解析上述header信息会发现服务器返回数据的charset为uft-8。这意味着后面需要对获取到的raw data进行宽字符转换。一开始由于没有意识到需要进行转换所以得到的数据都是乱码。
	//出现乱码的原因是:HTTP在传输过程中是二值的,它并没有text或者是unicode的概念。HTTP使用7bit的ASCII码作为HTTP headers,但是内容是任意的二值数据,需要根据header中指定的编码方式来描述它(通常是Content-Type header).
	//因此当你接收到原始的HTTP数据时,先将其保存到char[] buffer中,然后利用WinHttpQueryHearders()获取HTTP头,得到内容的Content-Type,这样你就知道数据到底是啥类型的了,是ASCII还是Unicode或者其他。
	//一旦你知道了具体的编码方式,你就可以通过MultiByteToWideChar()将其转换成合适编码的字符,存入wchar_t[]中。
	//获取服务器返回数据
	LPSTR pszOutBuffer = NULL;
	DWORD dwDownloaded = 0;         //实际收取的字符数
	wchar_t *pwText = NULL;
	vector<char*> pszOutBuffers;
	if (bResults)
	{
		do
		{
			//(1) 获取返回数据的大小(以字节为单位)
			dwSize = 0;
			if (!WinHttpQueryDataAvailable(hRequest, &dwSize)){
				cout << "Error:WinHttpQueryDataAvailable failed:" << GetLastError() << endl;
				break;
			}
			if (!dwSize)    break;  //数据大小为0                
//			cout << "dwSize" << dwSize << endl;
			//(2) 根据返回数据的长度为buffer申请内存空间
			pszOutBuffer = new char[dwSize + 1];
			pszOutBuffers.push_back(pszOutBuffer);
			if (!pszOutBuffer){
				cout << "Out of memory." << endl;
				break;
			}
			ZeroMemory(pszOutBuffer, dwSize + 1);       //将buffer置0

			//(3) 通过WinHttpReadData读取服务器的返回数据
			if (!WinHttpReadData(hRequest, pszOutBuffer, dwSize, &dwDownloaded)){
				cout << "Error:WinHttpQueryDataAvailable failed:" << GetLastError() << endl;
			}
			if (!dwDownloaded)
				break;
		} while (dwSize > 0);
		string InputData = "";
		DWORD index = 0;
		for (DWORD i = 0; i < pszOutBuffers.size(); i++)
		{
			InputData.append(pszOutBuffers[i]);
		}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

上述代码的读取中,用了一个数组去记录保存,主要原因是因为读取字符一次性只能读固定的位数,如果发送的数据过多,则不好保存,用数组记录每次读到的,然后拼接。

四、IP地址和域名的变量写法

在使用这套代码的过程中,会存在一个问题,上述的代码是写死的,比如域名为"127.0.0.1",这些都是写死的,如果是一个变量字符串放入到对应的位置,则会出现问题,所以,如果在运行的过程中无法明确的知道自己IP地址和域名的准确值,则需要想办法使用一个变量表示。

在这套http第三方库中,不直接使用string,而是使用wstring,具体的区别可以搜一些链接看一下,下面主要是给出一段string转wstring的代码。

//将string转换成wstring 
wstring string2wstring(string str)
{
	wstring result;
	//获取缓冲区大小,并申请空间,缓冲区大小按字符计算  
	int len = MultiByteToWideChar(CP_ACP, 0, str.c_str(), str.size(), NULL, 0);
	TCHAR* buffer = new TCHAR[len + 1];
	//多字节编码转换成宽字节编码  
	MultiByteToWideChar(CP_ACP, 0, str.c_str(), str.size(), buffer, len);
	buffer[len] = '\0';             //添加字符串结尾  
	//删除缓冲区并返回值  
	result.append(buffer);
	delete[] buffer;
	return result;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

有了对应的转化方法,那么上面在写IP地址和域名的使用就可以用相关的变量进行代替,可以灵活的操作,示例如下:

string myIP = "127.0.0.1";
wstring IP = string2wstring(myIP );
hConnect = WinHttpConnect(hSession, IP.c_str(), (INTERNET_PORT)80, 0);
  • 1
  • 2
  • 3

这样子就不用完全写死代码了,方便更好的维护。

五、代码的整体展示


int ReceiveMsg(const char* sendData)
{
	HINTERNET hSession = NULL;
	HINTERNET hConnect = NULL;
	HINTERNET hRequest = NULL;

	//1. 初始化一个WinHTTP-session句柄,参数1为此句柄的名称
	hSession = WinHttpOpen(L"AmbitionTc code", NULL, NULL, NULL, NULL);
	if (hSession == NULL) {
		cout << "Error:Open session failed: " << GetLastError() << endl;
		return -1;
	}
	//2. 通过上述句柄连接到服务器,需要指定服务器IP和端口号。若连接成功,返回的hConnect句柄不为NULL
	hConnect = WinHttpConnect(hSession, L"127.0.0.1", (INTERNET_PORT)80, 0);
	if (hConnect == NULL) {
		cout << "Error:Connect failed: " << GetLastError() << endl;
		return -1;
	}
	setlocale(LC_CTYPE, ".936");
	std::ifstream fin("http.txt");
	std::string str((std::istreambuf_iterator<char>(fin)),
		std::istreambuf_iterator<char>());
	setlocale(LC_CTYPE, 0);
	str = "&" + str;
	str = "api/token=123" + str;
	wstring strtest = string2wstring(str);
	//3. 通过hConnect句柄创建一个hRequest句柄,用于发送数据与读取从服务器返回的数据。
	hRequest = WinHttpOpenRequest(hConnect, L"Get", strtest.c_str(), NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0);
	//其中参数2表示请求方式,此处为Post;参数3:给定Post的具体地址,如这里的具体地址为http://192.168.50.112/getServiceInfo
	if (hRequest == NULL) {
		cout << "Error:OpenRequest failed: " << GetLastError() << endl;
		return -1;
	}
	//4-1. 向服务器发送post数据
	//(1) 指定发送的数据内容
	string data = string(sendData);
	const void *ss = (const char *)data.c_str();

	//(2) 发送请求
	BOOL bResults;
	bResults = WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, const_cast<void*>(ss), data.length(), data.length(), 0);
	if (!bResults){
		cout << "Error:SendRequest failed: " << GetLastError() << endl;
		return -1;
	}
	else{
		//(3) 发送请求成功则准备接受服务器的response。注意:在使用 WinHttpQueryDataAvailable和WinHttpReadData前必须使用WinHttpReceiveResponse才能access服务器返回的数据
		bResults = WinHttpReceiveResponse(hRequest, NULL);
	}
	//4-2. 获取服务器返回数据的header信息。这一步我用来获取返回数据的数据类型。
	LPVOID lpHeaderBuffer = NULL;
	DWORD dwSize = 0;
	if (bResults)
	{
		//(1) 获取header的长度
		WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_RAW_HEADERS_CRLF,
			WINHTTP_HEADER_NAME_BY_INDEX, NULL,
			&dwSize, WINHTTP_NO_HEADER_INDEX);

		//(2) 根据header的长度为buffer申请内存空间
		if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
		{
			lpHeaderBuffer = new WCHAR[dwSize / sizeof(WCHAR)];

			//(3) 使用WinHttpQueryHeaders获取header信息
			bResults = WinHttpQueryHeaders(hRequest,
				WINHTTP_QUERY_RAW_HEADERS_CRLF,
				WINHTTP_HEADER_NAME_BY_INDEX,
				lpHeaderBuffer, &dwSize,
				WINHTTP_NO_HEADER_INDEX);
		}
	}
	//解析上述header信息会发现服务器返回数据的charset为uft-8。这意味着后面需要对获取到的raw data进行宽字符转换。一开始由于没有意识到需要进行转换所以得到的数据都是乱码。
	//出现乱码的原因是:HTTP在传输过程中是二值的,它并没有text或者是unicode的概念。HTTP使用7bit的ASCII码作为HTTP headers,但是内容是任意的二值数据,需要根据header中指定的编码方式来描述它(通常是Content-Type header).
	//因此当你接收到原始的HTTP数据时,先将其保存到char[] buffer中,然后利用WinHttpQueryHearders()获取HTTP头,得到内容的Content-Type,这样你就知道数据到底是啥类型的了,是ASCII还是Unicode或者其他。
	//一旦你知道了具体的编码方式,你就可以通过MultiByteToWideChar()将其转换成合适编码的字符,存入wchar_t[]中。
	//关于乱码的解决方案请看4-4

	//4-3. 获取服务器返回数据
	LPSTR pszOutBuffer = NULL;
	DWORD dwDownloaded = 0;         //实际收取的字符数
	wchar_t *pwText = NULL;
	vector<char*> pszOutBuffers;
	if (bResults)
	{
		do
		{
			//(1) 获取返回数据的大小(以字节为单位)
			dwSize = 0;
			if (!WinHttpQueryDataAvailable(hRequest, &dwSize)){
				cout << "Error:WinHttpQueryDataAvailable failed:" << GetLastError() << endl;
				break;
			}
			if (!dwSize)    break;  //数据大小为0                
//			cout << "dwSize" << dwSize << endl;
			//(2) 根据返回数据的长度为buffer申请内存空间
			pszOutBuffer = new char[dwSize + 1];
			pszOutBuffers.push_back(pszOutBuffer);
			if (!pszOutBuffer){
				cout << "Out of memory." << endl;
				break;
			}
			ZeroMemory(pszOutBuffer, dwSize + 1);       //将buffer置0
			//(3) 通过WinHttpReadData读取服务器的返回数据
			if (!WinHttpReadData(hRequest, pszOutBuffer, dwSize, &dwDownloaded)){
				cout << "Error:WinHttpQueryDataAvailable failed:" << GetLastError() << endl;
			}
			if (!dwDownloaded)
				break;
		} while (dwSize > 0);
		string InputData = "";
		DWORD index = 0;
		for (DWORD i = 0; i < pszOutBuffers.size(); i++)
		{
			InputData.append(pszOutBuffers[i]);
		}
		InputString = InputData;
		for (auto it = pszOutBuffers.begin(); it != pszOutBuffers.end(); it++)			//释放空间
		{
			if (*it != NULL)
			{
				delete *it;
				*it = NULL;
			}
		}
	}
	//5. 依次关闭request,connect,session句柄
	if (hRequest) WinHttpCloseHandle(hRequest);
	if (hConnect) WinHttpCloseHandle(hConnect);
	if (hSession) WinHttpCloseHandle(hSession);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133

上述代码也是从一些博客中的学来的,与总结的,可能会出现大量相同的内容,如果侵权,请联系我。第一次写博客,还请大家多多包容。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/685378
推荐阅读
相关标签
  

闽ICP备14008679号