原创内容,转载请注明原文网址:http://homeqin.cn/a/wenzhangboke/jishutiandi/2019/0911/636.html
	C++模仿手机App外包Http/Https访问web站点
	一、概述
	1.Http与Https的区别与联络
	在OSI参考模型中Http与Https均属于应用层协议。Http即Hypertext Transfer Protocol,超文本传输协议;而Https为Secure Hypertext Transfer Protocol平安超文本传输协议,它是一个平安通讯通道,基于HTTP开发,用于在客户端与效劳器之间交流信息,它运用平安套接字层SSL停止信息交流,简单来说它就是HTTP的平安版。
	Http默许运用80端口,Https运用443端口。
	Http的数据在网络上是明文传输,而Https则是经过加密后的传输,因而它相比http会愈加平安,但是由于需求额外加解密操作,由于Https的效率没有那么高。在登录Https站点和Http站点时,能够明显觉得到性能差别。
	2.关于web访问的平安性
	当前许多的web站点登录时都是采用普通的http停止传输,这种方式有着极大的平安隐患。当前web开发登录系统常用的有以下四种方式:
	1) 账号和密码完整没有加密,明文传送。这种方式的平安级别是最低的,它无疑是将本人的账号和密码直接暴露给他人,经过抓包工具(例:WireShark)能够很容易的截获到账号和密码。
	2) 密码采用MD5或其它加密方式停止加密,宣称不可破解。其实,完整没有必要破解,只需截获加密后的密码串,就能够以你的身份访问效劳器,这样也是能够经过认证受权的。这种方式在加密水平上有了一定水平的进步,但仍是不平安的。
	3) 客户端在登录前去效劳端拿一次密钥,经过该密钥停止加密,而效劳器端的密钥是随机生成的,每次访问均会用不同的密钥。这种方式的平安性比拟高。
	4) 采用“平安性最高”的App开发培训HTTPS方式传输,客户端与效劳端会经过认证,且中间的传输数据全部停止加密。之所以在平安性最高上加引号,是由于它也不是绝对平安的,比方前段时间Openssl曝出平安破绽,大名鼎鼎的“心脏出血”,黑客应用它的一个memcpy的bug,能够从溢出的内存中拿到64K的用户数据,招致用户信息泄露。但是这个平安性级别相对前面三个是最高的,当前效劳端的证书一年收费大约3-5千,用这点钱换来相对平安,是很划算的事情了。
	二、SOCKET发送HTTP恳求
	1.根本流程
	无论是Http还是Https都是基于TCP停止传输的,因而运用SOCKET模仿HTTP访问web站点的方式,很简单,就是将头部数据拼接成数据包,发送给效劳端,然后接纳返回再解析就能够了。
	其根本流程和编写普通SOCKET通讯是一样的。Windows下的流程为:
	a. WSAStartup对Winsock效劳停止初始化
	b. 树立socket套接字
	c. connect衔接效劳端
	d. send发送数据
	e. recv接纳数据
	下面,以某站点的登录为例,应用Fiddler抓到的POST的头部信息如下:
	这样,我们就能够构建这样的数据包发送进来,然后接纳响应了,C++完成中心代码请见下局部。
	2.中心代码
	BOOL SocketClient::ConnectToServer(const CString strServerUrl, const int nPort)
	{
	cstrServerUrl = strServerUrl;
	nServerPort = nPort;
	BOOL bRet = FALSE;
	do 
	{
	if (!InitializeContext())
	{
	break;
	}
	if(!Connect())
	{
	break;
	}
	bRet = TRUE;
	} while (FALSE);
	return bRet;
	}
	BOOL SocketClient::LoginToServer(const CString strUsername, const CString strPasswd)
	{
	cstrUserName = strUsername;
	cstrPassWord = strPasswd;
	BOOL bRet = FALSE;
	do 
	{
	if (!SendPostData())
	{
	break;
	}
	bRet = TRUE;
	} while (FALSE);
	return bRet;
	}
	BOOL SocketClient::LogoutOfServer()
	{
	return FALSE;
	}
	BOOL SocketClient::InitializeContext()
	{
	BOOL bRet = FALSE;
	wsaData = new WSADATA;
	WORD wVersion = MAKEWORD(2, 2);
	do 
	{
	if(0 != WSAStartup(wVersion, wsaData))
	{
	break;
	}
	if(LOBYTE( wsaData->wVersion ) != 2 || HIBYTE( wsaData->wVersion ) != 2 )
	{
	WSACleanup();
	break;
	}
	LPHOSTENT lpHostTent;
	lpHostTent = gethostbyname(cstrServerUrl);
	if (NULL == lpHostTent)
	{
	break;
	}
	socketClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (socketClient == INVALID_SOCKET)
	{
	WSACleanup();
	break;
	}
	socketAddrClient = new SOCKADDR_IN;
	socketAddrClient->sin_family = AF_INET;
	socketAddrClient->sin_port = htons(nServerPort);
	socketAddrClient->sin_addr = *((LPIN_ADDR)*lpHostTent->h_addr_list);
	memset(socketAddrClient->sin_zero, 0, sizeof(socketAddrClient->sin_zero));
	bRet = TRUE;
	} while (FALSE);
	return bRet;
	}
	BOOL SocketClient::Connect()
	{
	BOOL bRet = FALSE;
	do 
	{
	if (SOCKET_ERROR == connect(socketClient, (LPSOCKADDR)socketAddrClient, sizeof(SOCKADDR_IN)))
	{
	int nErrorCode = WSAGetLastError();
	closesocket(socketClient);
	break;
	}
	bRet = TRUE;
	} while (FALSE);
	return bRet;
	}
	BOOL SocketClient::SendPostData()
	{
	CString cstrSendData;
	CString cstrSendParam = "redirect=&username="+cstrUserName+"&password="+cstrPassWord+"&auto_login=checked&submit=%E7%99%BB%E5%BD%95";
	BOOL bRet = FALSE;
	CString cstrSendParamLen;
	cstrSendParamLen.Format("%d", cstrSendParam.GetLength());
	cstrSendData = "POST http://account.vsochina.com/user/login HTTP/1.1\r\n";
	cstrSendData += "Host: account.vsochina.com\r\n";
	cstrSendData += "User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:35.0) Gecko/20100101 Firefox/35.0\r\n";
	cstrSendData += "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n";
	cstrSendData += "Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3\r\n";
	cstrSendData += "Accept-Encoding: gzip, deflate\r\n";
	cstrSendData += "DNT: 1\r\n";
	cstrSendData += "Referer: http://account.vsochina.com/user/login\r\n";
	cstrSendData += "Connection: keep-alive\r\n";
	cstrSendData += "Content-Type: application/x-www-form-urlencoded\r\n";
	cstrSendData += "Content-Length: " + cstrSendParamLen +"\r\n";
	cstrSendData += "\r\n";
	cstrSendData += cstrSendParam;
	CString cstrRecvData;
	do 
	{
	if (-1 == send(socketClient, cstrSendData.GetBuffer(), cstrSendData.GetLength(), 0))
	{
	break;
	}
	char recvData[1000] = {0};
	int nRecvLen;
	while((nRecvLen = recv(socketClient, recvData, sizeof(recvData), 0)) > 0)
	{
	cstrRecvData += recvData;
	}
	if (cstrRecvData.GetLength() == 0)
	{
	break;
	}
	ParseCookieFromRecvData(cstrRecvData);
	//!判别返回的COOKIE信息中,UID能否存在
	if (cstrCookieUid.IsEmpty())
	{
	break;
	}
	bRet = TRUE;
	} while (FALSE);
	return bRet;
	}
	void SocketClient::ParseCookieFromRecvData(const CString cstrRecvData)
	{
	list lstCookiesLine;        //!寄存Set-Cookie的一行,例:Set-Cookie: vso_uname=houqd_1111;
	CString cstrFind = "Set-Cookie:";    //!查找标志
	CString cstrSeperator = "\r\n";      //!以"\r\n"分割号来分割字符串
	int nPos = 0;
	int nStart = cstrRecvData.Find(cstrSeperator);
	while(nStart != -1)
	{
	CString cstrSessionLine = cstrRecvData.Mid(nPos, nStart - nPos + 1);
	if (cstrSessionLine.Find(cstrFind) != -1)
	{
	CString cstrRealRecord = cstrSessionLine.Right(cstrSessionLine.GetLength() - cstrFind.GetLength() - 3);
	list::iterator it = find(lstCookiesLine.begin(), lstCookiesLine.end(), cstrRealRecord);
	if (it == lstCookiesLine.end())
	{
	lstCookiesLine.push_back(cstrRealRecord);
	}
	}
	nPos = nStart;
	nStart = cstrRecvData.Find(cstrSeperator, nPos + 2);
	}
	//!依据每行获取的cookie值,解析为key-value的方式
	vector vecCookieSet;
	for (list::iterator it = lstCookiesLine.begin(); it != lstCookiesLine.end(); it++)
	{
	CString cstrCookies = *it;
	CString cstrSeperator = ";";
	StaticUtility::StringSplit(cstrCookies, cstrSeperator, vecCookieSet);
	}
	vector vecTemp;
	for (vector::iterator it = vecCookieSet.begin(); it != vecCookieSet.end(); it++)
	{
	vecTemp.clear();
	CString cstrOneCookies = *it;
	CString cstrSeperator = "=";
	StaticUtility::StringSplit(cstrOneCookies, cstrSeperator, vecTemp);
	CString cstrKey = vecTemp[0];
	CString cstrVal = vecTemp[1];
	if(cstrKey.Compare("vso_uid") == 0)
	{
	cstrCookieUid = cstrVal;
	break;
	}
	}
	}
	经过常州网站开发培训接纳来的头部信息中,将cookie信息解析出来,就能够判别能否登录胜利了。然后,假如有或许的操作,在恳求中挂上这些cookie信息,就能够获取想要的数据,完成想要的操作了。
	三、OpenSSL发送HTTPS恳求
	1.根本流程
	HTTPS=HTTP + SSL,因而应用OpenSSL发送恳求给HTTPS站点和第二章的SOCKET发送HTTP是十分类似的,只不过要在原生的套接字上套上SSL层,根本流程如下:
	a. WSAStartup对Winsock效劳停止初始化
	b. 树立socket套接字
	c. connect衔接效劳端
	d. 树立SSL上下文
	e. 树立SSL
	f. 将SSL与前面树立的socket套接字绑定
	g. SSL_write()发送数据
	h. SSL_read()接纳数据
	下面以小米官网站点的登录为例,来展现应用OpenSSL如何访问HTTPS站点,模仿登陆,中心代码,见下一章节。
	2.中心代码
	#pragma comment( lib, "libeay32.lib" )
	#pragma comment( lib, "ssleay32.lib" )
	HttpsClient::HttpsClient(void):
	wsaData(NULL),
	     socketAddrClient(NULL),
	ssl(NULL),
	sslCtx(NULL),
	sslMethod(NULL),
	serverCertification(NULL)
	{
	SSL_load_error_strings();
	SSLeay_add_ssl_algorithms();
	}
	HttpsClient::~HttpsClient(void)
	{
	//!清算翻开的句柄
	if (NULL != ssl)
	{
	SSL_shutdown(ssl);
	closesocket(socketClient);
	SSL_free(ssl);
	ssl = NULL;
	}
	if (NULL != sslCtx)
	{
	SSL_CTX_free(sslCtx);
	}
	WSACleanup();
	}
	BOOL HttpsClient::ConnectToServer(const CString strServerUrl, const int nPort)
	{
	cstrServerUrl = strServerUrl;
	nServerPort = nPort;
	BOOL bRet = FALSE;
	do 
	{
	if (!InitializeSocketContext())
	{
	break;
	}
	if (!SocketConnect())
	{
	break;
	}
	if (!InitializeSslContext())
	{
	break;
	}
	if (!SslConnect())
	{
	break;
	}
	bRet = TRUE;
	} while (FALSE);
	return bRet;
	}
	BOOL HttpsClient::LoginToServer(const CString strUsername, const CString strPasswd)
	{
	cstrUserName = strUsername;
	cstrPassWord = strPasswd;
	BOOL bRet = FALSE;
	do 
	{
	if (!SendLoginPostData())
	{
	break;
	}
	CString cstrRecvData;
	RecvLoginPostData(cstrRecvData);
	if (cstrRecvData.GetLength() == 0)
	{
	break;
	}
	ParseCookieFromRecvData(cstrRecvData);
	if (cstrCookieUid.IsEmpty() || cstrCookieUid.Compare("EXPIRED") == 0)
	{
	break;
	}
	bRet = TRUE;
	} while (FALSE);
	return bRet;
	}
	BOOL HttpsClient::LogoutOfServer()
	{
	return FALSE;
	}
	BOOL HttpsClient::InitializeSocketContext()
	{
	//!初始化winSocket环境
	BOOL bRet = FALSE;
	wsaData = new WSADATA;
	WORD wVersion = MAKEWORD(2, 2);
	do 
	{
	if(0 != WSAStartup(wVersion, wsaData))
	{
	break;
	}
	if(LOBYTE( wsaData->wVersion ) != 2 || HIBYTE( wsaData->wVersion ) != 2 )
	{
	WSACleanup();
	break;
	}
	LPHOSTENT lpHostTent;
	lpHostTent = gethostbyname(cstrServerUrl);
	if (NULL == lpHostTent)
	{
	break;
	}
	socketClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (socketClient == INVALID_SOCKET)
	{
	WSACleanup();
	break;
	}
	socketAddrClient = new SOCKADDR_IN;
	socketAddrClient->sin_family = AF_INET;
	socketAddrClient->sin_port = htons(nServerPort);
	socketAddrClient->sin_addr = *((LPIN_ADDR)*lpHostTent->h_addr_list);
	memset(socketAddrClient->sin_zero, 0, sizeof(socketAddrClient->sin_zero));
	bRet = TRUE;
	} while (FALSE);
	return bRet;
	}
	BOOL HttpsClient::SocketConnect()
	{
	//!原生socket衔接
	BOOL bRet = FALSE;
	do 
	{
	if (SOCKET_ERROR == connect(socketClient, (LPSOCKADDR)socketAddrClient, sizeof(SOCKADDR_IN)))
	{
	int nErrorCode = WSAGetLastError();
	closesocket(socketClient);
	break;
	}
	bRet = TRUE;
	} while (FALSE);
	return bRet;
	}
	BOOL HttpsClient::InitializeSslContext()
	{
	//!SSL通讯初始化
	BOOL bRet = FALSE;
	do 
	{
	sslMethod = SSLv23_client_method();
	if(NULL == sslMethod)
	{
	break;
	}
	sslCtx = SSL_CTX_new(sslMethod);
	if (NULL == sslCtx)
	{
	break;
	}
	ssl = SSL_new(sslCtx);
	if (NULL == ssl)
	{
	break;
	}
	bRet = TRUE;
	} while (FALSE);
	return bRet;
	}
	BOOL HttpsClient::SslConnect()
	{
	//!SSL绑定原生socket,并衔接效劳器
	BOOL bRet = FALSE;
	do 
	{
	SSL_set_fd(ssl, socketClient);
	int nRet = SSL_connect(ssl);
	if (-1 == nRet)
	{
	break;
	}
	bRet = TRUE;
	} while (FALSE);
	return bRet;
	}
	BOOL HttpsClient::SslGetCipherAndCertification()
	{
	BOOL bRet = FALSE;
	do 
	{
	cstrSslCipher = SSL_get_cipher(ssl);
	serverCertification = SSL_get_certificate(ssl);
	if (NULL == serverCertification)
	{
	break;
	}
	cstrSslSubject = X509_NAME_oneline(X509_get_subject_name(serverCertification), 0, 0);
	cstrSslIssuer = X509_NAME_oneline(X509_get_issuer_name(serverCertification), 0, 0);
	X509_free(serverCertification);
	bRet = TRUE;
	} while (FALSE);
	return bRet;
	}
	BOOL HttpsClient::SendLoginPostData()
	{
	CString cstrSendData;
	//CString cstrSendParam = "redirect=&username="+cstrUserName+"&password="+cstrPassWord+"&auto_login=checked&submit=%E7%99%BB%E5%BD%95";
	CString cstrSendParam = "user="+cstrUserName+"&_json=true&pwd="+cstrPassWord+"&callback=http%3A%2F%2Forder.mi.com%2Flogin%2Fcallback%3Ffollowup%3Dhttp%253A%252F%252Fwww.mi.com%252F%26sign%3DNWU4MzRmNjBhZmU4MDRmNmZkYzVjMTZhMGVlMGFmMTllMGY0ZTNhZQ%2C%2C&sid=mi_eshop&qs=%253Fcallback%253Dhttp%25253A%25252F%25252Forder.mi.com%25252Flogin%25252Fcallback%25253Ffollowup%25253Dhttp%2525253A%2525252F%2525252Fwww.mi.com%2525252F%252526sign%25253DNWU4MzRmNjBhZmU4MDRmNmZkYzVjMTZhMGVlMGFmMTllMGY0ZTNhZQ%25252C%25252C%2526sid%253Dmi_eshop&hidden=&_sign=%2Bw73Dr7cAfRlMfOR6fW%2BF0QG4jE%3D&serviceParam=%7B%22checkSafePhone%22%3Afalse%7D&captCode=";
	BOOL bRet = FALSE;
	CString cstrSendParamLen;
	cstrSendParamLen.Format("%d", cstrSendParam.GetLength());
	cstrSendData = "POST https://account.xiaomi.com/pass/serviceLoginAuth2 HTTP/1.1\r\n";
	cstrSendData += "Host: account.xiaomi.com\r\n";
	cstrSendData += "User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; rv:35.0) Gecko/20100101 Firefox/35.0\r\n";
	cstrSendData += "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n";
	cstrSendData += "Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3\r\n";
	cstrSendData += "Accept-Encoding: gzip, deflate\r\n";
	cstrSendData += "DNT: 1\r\n";
	cstrSendData += "Content-Type: application/x-www-form-urlencoded; charset=UTF-8\r\n";
	cstrSendData += "Referer: https://account.xiaomi.com/pass/serviceLogin?callback=http%3A%2F%2Forder.mi.com%2Flogin%2Fcallback%3Ffollowup%3Dhttp%253A%252F%252Fwww.mi.com%252Findex.html%26sign%3DNDRhYjQwYmNlZTg2ZGJhZjI0MTJjY2ZiMTNiZWExODMwYjkwNzg2ZQ%2C%2C&sid=mi_eshop\r\n";
	cstrSendData += "Content-Length: " + cstrSendParamLen +"\r\n";
	cstrSendData += "Connection: keep-alive\r\n";
	cstrSendData += "\r\n";
	cstrSendData += cstrSendParam;
	CString cstrRecvData;
	do 
	{
	int nRet = SSL_write(ssl, cstrSendData, cstrSendData.GetLength());
	if(-1 == nRet)
	{
	break;
	}
	bRet = TRUE;
	} while (FALSE);
	return bRet;
	}
	void HttpsClient::RecvLoginPostData(CString &cstrRecvData)
	{
	BOOL bRet = FALSE;
	do 
	{
	TIMEVAL tval;
	tval.tv_sec = 20;
	tval.tv_usec = 0;
	while(TRUE)
	{
	FD_SET fds;
	FD_ZERO(&fds);
	FD_SET(socketClient, &fds);
	char recvData[1000] = {0};
	int nRecvLen;
	//int nSelect = select(FD_SETSIZE, &fds, NULL, NULL, &tval);
	//if (1 != nSelect)
	//{
	// break;
	//}
	int nErr = SSL_read(ssl, recvData, sizeof(recvData));
	if (nErr <= 0)
	{
	break;
	}
	cstrRecvData += recvData;
	}
	if (cstrRecvData.GetLength() == 0)
	{
	break;
	}
	bRet = TRUE;
	} while (FALSE);
	}
	void HttpsClient::ParseCookieFromRecvData(const CString cstrRecvData)
	{
	list lstCookiesLine;        //!寄存Set-Cookie的一行,例:Set-Cookie: vso_uname=houqd_1111;
	CString cstrFind = "Set-Cookie:";    //!查找标志
	CString cstrSeperator = "\r\n";      //!以"\r\n"分割号来分割字符串
	int nPos = 0;
	int nStart = cstrRecvData.Find(cstrSeperator);
	while(nStart != -1)
	{
	CString cstrSessionLine = cstrRecvData.Mid(nPos, nStart - nPos + 1);
	if (cstrSessionLine.Find(cstrFind) != -1)
	{
	CString cstrRealRecord = cstrSessionLine.Right(cstrSessionLine.GetLength() - cstrFind.GetLength() - 3);
	list::iterator it = find(lstCookiesLine.begin(), lstCookiesLine.end(), cstrRealRecord);
	if (it == lstCookiesLine.end())
	{
	lstCookiesLine.push_back(cstrRealRecord);
	}
	}
	nPos = nStart;
	nStart = cstrRecvData.Find(cstrSeperator, nPos + 2);
	}
	//!依据每行获取的cookie值,解析为key-value的方式
	vector vecCookieSet;
	for (list::iterator it = lstCookiesLine.begin(); it != lstCookiesLine.end(); it++)
	{
	CString cstrCookies = *it;
	CString cstrSeperator = ";";
	StaticUtility::StringSplit(cstrCookies, cstrSeperator, vecCookieSet);
	}
	vector vecTemp;
	for (vector::iterator it = vecCookieSet.begin(); it != vecCookieSet.end(); it++)
	{
	vecTemp.clear();
	CString cstrOneCookies = *it;
	CString cstrSeperator = "=";
	StaticUtility::StringSplit(cstrOneCookies, cstrSeperator, vecTemp);
	CString cstrKey;
	CString cstrVal;
	if (vecTemp.size() == 2)
	{
	cstrKey = vecTemp[0];
	cstrVal = vecTemp[1];
	}
	if(cstrKey.Compare("userId") == 0)
	{
	cstrCookieUid = cstrVal;
	break;
	}
	}
	}
	同理,判别企业培训登录也是在返回的信息中拿cookie信息,再停止下一步操作。
上篇:上一篇:c++ 的string的格式化办法
下篇:下一篇:C++解析Json用JsonCpp读写Json数据



