之前我实现了访问 http 网页,以及解析大小字段实现网页的完整读取。但现在很多网站都是 https 了,而 https 在 TCP 和 IP 层中间加了一个 SSL 或者 TLS 协议,相当地麻烦,还涉及到公钥加密等,徒手搓相当不现实。
C++也是这点缺点,调库很麻烦。不像 python,直接 import urllib,urlopen就可以直接开 https 的网页。
本文使用 openssl库 来实现 https 网页的访问。因为我在 openssl库的使用中踩了很多坑,所以我会尽量把我踩的坑都说清楚,以免读者再踩。
openssl托管在github的,不过README上也写明了,它不用github的release功能来发布,只在官网发布。
所以,第一步,打开https://www.openssl.org/source/
,下载我红圈圈住的这个链接。3.0.0那个虽然更新,但是是预发布版,我们暂时不管他,用1.1.1g这个版本。
openssl没有用cmake,它提供的编译方式是,使用perl和nasm生成makefile,然后通过makefile编译。perl和nasm必下,不能避开。
打开http://downloads.activestate.com/ActivePerl/releases/
,往下翻,有Downloads的框,选最新版,点进去,然后选名字类似于 ActivePerl-5.28.1.2801-MSWin32-x64-24563874.exe
格式的,下载,一路next安装。
打开https://www.nasm.us/pub/nasm/releasebuilds/
,选择版本下载。很奇怪的是,我下载最新的2.15rc12
版本,Windows Defender提示有病毒。我最后下载的2.14rc15
版本,就没有提示病毒了:https://www.nasm.us/pub/nasm/releasebuilds/2.14rc15/win64/nasm-2.14rc15-installer-x64.exe
。也是一路next安装。
perl安装时会自动设置环境变量,nasm不会,需要手动添加。确保PATH环境变量下有以下三条。
我这里默认你已经安装了VS 2019 Community,在开始菜单找到x86本地命令行工具,右键 更多 - 以管理员身份运行。
输入D:
(你放openssl的盘符),然后cd
后跟openssl解压后的目录。
运行perl configure VC-WIN32
,这里的configure是一个文件的名字,如果它说找不到configure的话,就把configura换成绝对路径,类似D:\REFER\Program\lib\openssl\openssl-1.1.1g\configure
这种的。
现在会出现上图的结果,白底红字的意思是找不到nmake。不过不管它。
现在需要运行一个叫vcvarsall.bat
的东西。在我电脑上它放在这里:C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat
,所以我运行:
"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat" x86
注意要带上双引号。得到如下结果:
出现最后这句就说明现在的环境变量设置成功了。
然后运行nmake
。
这里会持续运行几分钟,把很多.c文件编译成obj。如果在末尾出现了fetal error:
字样,可能是你运行的VS 命令行工具、vcvarsall.bat中指定的平台指令集不一致(命令行工具运行的x64版,vcvarsall.bat的参数x86_amd64、或者perl命令中的参数是VC-WIN64A)。
然后运行nmake test
。
成功的话会在openssl目录下生成一堆.lib和.dll文件。
到这里openssl的编译就完成了。如果需要编译64位,可以把前面的参数都改成64对应的参数。如果需要其他编译参数,可以打开makefile查看,需要一点点makefile的知识。
下面通过一个实例实现https网页的访问,代码来自另一位博主,链接提供在代码最后。我做了小幅改动。
打开VS 2019,新建命令行项目,填入代码:
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include
#include
#include
#include
#include
#pragma comment(lib,"libssl.lib")
#pragma comment(lib,"libcrypto.lib")
#pragma comment(lib,"ws2_32.lib")
using namespace std;
int main()
{
//加载SSL错误信息
SSL_load_error_strings();
//添加SSL的加密/HASH算法
SSLeay_add_ssl_algorithms();
//客户端,服务端选择SSLv23_server_method()
const SSL_METHOD* meth = SSLv23_client_method();
//建立新的SSL上下文
SSL_CTX* ctx = SSL_CTX_new(meth);
if (ctx == NULL)
{
ERR_print_errors_fp(stderr);
cout << "SSL_CTX_new error !";
return -1;
}
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
return -1;
}
SOCKET client = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (client == INVALID_SOCKET)
{
cout << "socket error !";
return -1;
}
string host = "www.baidu.com";
unsigned short port = 443;
hostent* ip = gethostbyname(host.c_str());
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
sin.sin_addr = *(in_addr*)ip->h_addr_list[0];
if (connect(client, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR)
{
cout << "connect error 1";
return -1;
}
//建立SSL
int ret;
SSL* ssl = SSL_new(ctx);
if (ssl == NULL)
{
cout << "SSL NEW error";
return -1;
}
//将SSL与TCP SOCKET 连接
SSL_set_fd(ssl, client);
//SSL连接
ret = SSL_connect(ssl);
if (ret == -1)
{
cout << "SSL ACCEPT error ";
return -1;
}
stringstream stream;
stream << "GET https://" << host << " HTTP/1.0\r\n";
stream << "Accept: */*\r\n";
//stream << "Accept-Encoding: gzip, deflate, br\r\n";//不要编码,否则还得多一个解码的步骤
stream << "Accept-Language: zh-Hans-CN, zh-Hans; q=0.8, en-US; q=0.5, en; q=0.3\r\n";
stream << "Connection: Keep-Alive\r\n";
stream << "Host: " << host << "\r\n";
stream << "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36 Edge/17.17134\r\n";
stream << "\r\n";
string s = stream.str();
const char* sendData = s.c_str();
ret = SSL_write(ssl, sendData, strlen(sendData));
if (ret == -1)
{
cout << "SSL write error !";
return -1;
}
char* rec = new char[1024 * 1024];
int start = 0;
while ((ret = SSL_read(ssl, rec + start, 1024)) > 0)
{
start += ret;
}
rec[start] = 0;
cout << rec;
//关闭SSL套接字
SSL_shutdown(ssl);
//释放SSL套接字
SSL_free(ssl);
//释放SSL会话环境
SSL_CTX_free(ctx);
closesocket(client);
WSACleanup();
}
//————————————————
//版权声明:本文为CSDN博主「ZRXSLYG」的原创文章,遵循CC 4.0 BY - SA版权协议,转载请附上原文出处链接及本声明。
//原文链接:https://blog.csdn.net/ZRXSLYG/article/details/81395640
然后打开 调试 - XX属性,在VC++目录中加入包含目录和库目录,如图:
红线部分要换成你自己的目录。
把刚才编译生成的libssl-1_1.dll
和libcrypto-1_1.dll
放进工程目录。在VS中调试的话就放这里,如果要单独运行exe的话,就复制一份和exe呆在一个目录下。不做这步的话,运行程序会弹出“不能链接到xxx.dll”
。
运行结果就是这样:
这篇是我的主要参考来源,不过有些坑没有指出来,后半部分的ms/do_nasm在现在的版本已经没有了。
实例中的代码来自这里:
这是官方给出的编译步骤: