一、相关知识
HTTP 客户端用C语言实现就是通过SOCKET 链接通道,按照HTTP协议把数据包做装好,通过SOCKET连接通道,发送,接收。我们把收到的数据按协议,拆分开,再按我梦的意愿吧没部分数据展示或存储起来就可以了。HTTP协议:https://blog.csdn.net/weixin_38087538/article/details/82838762
HTTPS 应用百度百科的解释【HTTPS 主要由两部分组成:HTTP + SSL / TLS,也就是在 HTTP 上又加了一层处理加密信息的模块。服务端和客户端的信息传输都会通过 TLS 进行加密,所以传输的数据都是加密后的数据。】
简单实现HTTPS客户端也就是说我们建立一个SSL通道,然后按照HTTP协议处理数据就可以了,和HTTP客户端相比,就是把SOCKET链接通道换成SSL连接通道。那我们就要研究SSL链接通道的建立与断开。
网上搜到两幅图比较好,很容易理解SSL的建立过程。应用于:https://blog.csdn.net/Aquester/article/details/7635573
SSL客户端图
SSL服务端图
二、下载配置OpenSSL-Win64 库
然后我们需要下载 OpenSSL-Win64 库
OpenSSL-Win 主页为:https://slproweb.com/products/Win32OpenSSL.html
OpenSSL-Win64:
https://slproweb.com/download/Win64ARMOpenSSL-3_0_2.exe
https://slproweb.com/download/Win64OpenSSL-1_1_1n.exe
下载完后直接安装就可以了,一般默认配置,我的安装目录选在 D:\Program Files\OpenSSL-Win64
三、Visual Studio 配置 OpenSSL
点击项目--属性
打开属性界面,点击VC++ 目录,
"配置(C): " 选 “所有配置” ,
“平台(P):” 选 自己需要的 32 位或 64位
” 包含目录“ 添加 你 OpenSSL-Win64 安装的目录位置下的 头文件位置 D:\Program Files\OpenSSL-Win64\include
” 包含目录“ 添加 你 OpenSSL-Win64 安装的目录位置下的 lib位置 D:\Program Files\OpenSSL-Win64\lib
Visual Studio 配置 OpenSSL完成
四、撸代码
#include
#include
#include
#include
#include
#include
#include
#define BUFF_SIZE 1024
#pragma warning(disable : 4075)
#pragma warning(disable : 4996)
#pragma warning(disable : 6387)
#pragma comment(lib,"ws2_32.lib")
#pragma comment(lib,"libssl.lib")
#pragma comment(lib,"libcrypto.lib")
CONST INT RECV_SIZE = 8192;
char* UTF8ToANSI(const char* str);
/*多字符转换为宽字符 --- ANSI -to- Unicode*/
wchar_t* ANSIToUnicode(const char* str)
{
size_t textlen;
wchar_t* result;
textlen = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0);
result = (wchar_t*)malloc((textlen + 1) * sizeof(wchar_t));
if (nullptr != (result = (wchar_t*)malloc((textlen + 1) * sizeof(wchar_t))))
{
memset(result, 0, (textlen + 1) * sizeof(wchar_t));
MultiByteToWideChar(CP_ACP, 0, str, -1, (LPWSTR)result, textlen);
return result;
}
return 0;
}
/*宽字符转换为多字符 --- Unicode -to- ANSI*/
char* UnicodeToANSI(const wchar_t* str)
{
char* result;
size_t textlen;
textlen = WideCharToMultiByte(CP_ACP, 0, str, -1, NULL, 0, NULL, NULL);
result = (char*)malloc((textlen + 1) * sizeof(char));
if (nullptr != result)
{
memset(result, 0, sizeof(char) * (textlen + 1));
WideCharToMultiByte(CP_ACP, 0, str, -1, result, textlen, NULL, NULL);
return result;
}
return 0;
}
/*UTF8转换为宽字符 --- UTF8 -to- Unicode */
wchar_t* UTF8ToUnicode(const char* str)
{
size_t textlen;
wchar_t* result;
textlen = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
result = (wchar_t*)malloc((textlen + 1) * sizeof(wchar_t));
if (nullptr != result)
{
memset(result, 0, (textlen + 1) * sizeof(wchar_t));
MultiByteToWideChar(CP_UTF8, 0, str, -1, (LPWSTR)result, textlen);
return result;
}
return 0;
}
/*宽字符转换为UTF8 --- Unicode -to- UTF8 */
char* UnicodeToUTF8(const wchar_t* str)
{
char* result;
size_t textlen;
textlen = WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL);
result = (char*)malloc((textlen + 1) * sizeof(char));
if (nullptr != result)
{
memset(result, 0, sizeof(char) * (textlen + 1));
WideCharToMultiByte(CP_UTF8, 0, str, -1, result, textlen, NULL, NULL);
return result;
}
return 0;
}
/*多字符转换为UTF8 --- Unicode -to- UTF8 */
char* ANSIToUTF8(const char* str)
{
return UnicodeToUTF8(ANSIToUnicode(str));
}
/*UTF8转换为多字符 --- UTF8 -to- ANSI */
char* UTF8ToANSI(const char* str)
{
return UnicodeToANSI(UTF8ToUnicode(str));
}
INT _tmain(INT argc, LPTSTR argv[])
{
//启初始化wsa
WSADATA wsadData;
if (0 != WSAStartup(MAKEWORD(2, 2), &wsadData)) {
printf_s("初始化 WSAStartup 失败");
return 0;
}
//获取Host的IP地址等信息
ADDRINFOT aiHints;
ZeroMemory(&aiHints, sizeof(ADDRINFOT));
aiHints.ai_family = AF_INET;
aiHints.ai_flags = AI_PASSIVE;
aiHints.ai_protocol = 0;
aiHints.ai_socktype = SOCK_STREAM;
std::wstring wstrHost = TEXT("www.baidu.com");
PADDRINFOT paiResult;
GetAddrInfo(wstrHost.c_str(), NULL, &aiHints, &paiResult);
//创建套接字
SOCKET sSocket = socket(AF_INET, SOCK_STREAM, 0);
if (sSocket == SOCKET_ERROR)
{
std::wcout << "Error socket" << std::endl;
return -1;
}
//连接Host
SOCKADDR_IN sinHost{};
sinHost.sin_addr = ((LPSOCKADDR_IN)paiResult->ai_addr)->sin_addr;
sinHost.sin_family = AF_INET;
sinHost.sin_port = htons(443);
if (connect(sSocket, (LPSOCKADDR)&sinHost, sizeof(SOCKADDR_IN)) == SOCKET_ERROR)
{
std::wcout << "Error connect" << std::endl;
return -1;
}
//初始化OpenSSL库
//(虽然不知道为什么,但是不加这三行似乎并不会导致什么问题,在不加这3行的情况下测试了几个网站并没有发现任何问题喵)
SSL_library_init(); //SSL库初始化
SSLeay_add_ssl_algorithms(); //载入所有SSL算法
SSL_load_error_strings(); //载入所有SSL错误消息
//创建SSL会话环境等
SSL_CTX* pctxSSL = SSL_CTX_new(TLS_client_method()); //产生一个SSL_CTX 数据结构
if (pctxSSL == NULL)
{
std::wcout << "Error SSL_CTX_new" << std::endl;
return -1;
}
SSL* psslSSL = SSL_new(pctxSSL); //产生一个SLL 数据结构
if (psslSSL == NULL)
{
std::wcout << "Error SSL_new" << std::endl;
return -1;
}
SSL_set_fd(psslSSL, sSocket); //将 socket 载入到 SSL 中
INT iErrorConnect = SSL_connect(psslSSL); //建立 SSL 链接
if (iErrorConnect < 0)
{
std::wcout << "Error SSL_connect, iErrorConnect=" << iErrorConnect << std::endl;
return -1;
}
std::wcout << "SSL connection using " << SSL_get_cipher(psslSSL) << std::endl;
//发包
std::string strWrite =
"GET https://www.baidu.com/ HTTP/1.1\r\n"
"Host: www.baidu.com\r\n"
"Connection: close\r\n\r\n";
INT iErrorWrite = SSL_write(psslSSL, strWrite.c_str(), strWrite.length()); //通过SSL链接发送数据
if (iErrorWrite < 0)
{
std::wcout << "Error SSL_write" << std::endl;
return -1;
}
//收包并输出
//这里接受的是char形式的,所以中文会乱码
//如果要正常显示中文,需要再转换为wchar_t或std::wstring
LPSTR lpszRead = new CHAR[RECV_SIZE];
INT iLength = 1;
while (1 <= (iLength = SSL_read(psslSSL, lpszRead, RECV_SIZE))) //通过SSL链接接收数据
{
std::cout << UTF8ToANSI(lpszRead) << std::endl;
}
delete[] lpszRead;
SSL_shutdown(psslSSL); //关闭 SSL 链接
SSL_free(psslSSL); //释放 SSL 数据结构体
closesocket(sSocket); //释放 SOCKET
SSL_CTX_free(pctxSSL); //释放 SSL_CTX 数据结构体
return 0;
}