服务端说:Hello
二、开发: 1.头文件: 本教程所使用的头文件只有三个:(它们都位于 include/openssl 子目录中) ssl.h ssl 的主要数据结构定义在 ssl.h 中。 bio.h 不管连接是安全的还是不安全的, OpenSSL 都使用了一个名为 BIO 的抽象库来处理包括文件和套接字在内的各种类型的通信。 err.h 2.初始化: 要初始化 OpenSSL 库,只需要三行代码行即可: // 初始化 SSL 算法库函数,调用 SSL 系列函数之前必须调用此函数! SSL_library_init(); // 加载 SSL 错误消息 SSL_load_error_strings(); // 加载 BIO 抽象库的错误信息 ERR_load_BIO_strings(); // 加载所有 加密 和 散列 函数 OpenSSL_add_all_algorithms(); 3.1.建立非安全连接: // 首先,我将向您展示如何建立一个标准的套接字连接。 // 相对于使用 BSD 套接字库,该操作需要的代码行更少一些。 // 在建立连接(无论安全与否)之前,要创建一个指向 BIO 对象的指针。 // 这类似于在标准 C 中为文件流创建 FILE 指针。 BIO* bio = NULL; // 创建新的连接需要调用 BIO_new_connect 。 // 您可以在同一个调用中同时指定主机名和端口号。 // 也可以将其拆分为两个单独的调用: // 一个是创建连接并设置主机名的 BIO_new_connect 调用, // 一个是设置端口号的 BIO_set_conn_port (或者 BIO_set_conn_int_port )调用。 // 一旦 BIO 的主机名和端口号都已指定,该指针会尝试打开连接。 // 如果创建 BIO 对象时遇到问题,指针将会是 NULL。 // 下面这行代码使用指定的主机名和端口创建了一个新的 BIO 对象。 // 参数字符串可以是 www.ibm.com:80 bio = BIO_new_connect( "hostname:port" ); if ( NULL == bio ) { // 处理故障 } // 为了确保连接成功,必须执行 BIO_do_connect 调用。 // 如果出错,则返回 0 或 -1。 if ( 0 >= BIO_do_connect( bio ) ) { // 处理连接失败 } 3.2.1.为安全连接进行设置: // 为安全连接进行设置要多几行代码。 // 同时需要有另一个类型为 SSL_CTX 的指针。该结构保存了一些 SSL 信息。 // 您也可以利用它通过 BIO 库建立 SSL 连接。 // 可以通过使用 SSL 方法函数调用 SSL_CTX_new 来创建这个结构, // 该方法函数的参数通常是 TLSv1_client_method() (以前是 SSLv23_client_method) 。 SSL_CTX* ctx = SSL_CTX_new( TLSv1_client_method() ); // 还需要另一个 SSL 类型的指针来保持 SSL 连接结构(这是短时间就能完成的一些连接所必需的)。 // 以后还可以用该 SSL 指针来检查连接信息或设置其他 SSL 参数。 SSL* ssl = NULL; 3.2.2.加载可信任证书: // 在创建上下文结构之后,必须加载一个可信任证书库。这是成功验证每个证书所必需的。 // 如果不能确认证书是可信任的,那么 OpenSSL 会将证书标记为无效(但连接仍可以继续)。 // OpenSSL 附带了一组可信任证书。它们位于源文件目录树的 certs/demo 目录中。 // 不过,每个证书都是一个独立的文件,也就是说,需要单独加载每一个证书。 // 在 certs 目录下,还有一个 expired 存放过期证书的子目录,试图加载这些证书将会出错。 // 在数字证书进行信任验证之前, // 必须为在为安全连接设置时创建的 OpenSSL SSL_CTX 对象提供一个默认的信任证书, // 这可以使用几种方法来提供, // 但是最简单的方法是将这个证书保存为一个 PEM 文件, // 并使用 // SSL_CTX_load_verify_locations( ctx, file, path ); // 将其加载到 OpenSSL 中。 // 该函数有三个参数: // ctx - 上下文指针( SSL_CTX_new 函数返回 ); // file - 包含一个或多个 PEM 格式的证书的文件的路径(必需); // path - 到一个或多个 PEM 格式文件的路径,不过文件名必须使用特定的格式(可为 NULL); // 如果指定成功,则返回 1 ,如果遇到问题,则返回 0 。 // 尽管当信任证书在一个目录中有多个单独的文件时更容易添加或更新, // 但是您不太可能会如此频繁地更新信任证书,因此不必担心这个问题。 if ( !SSL_CTX_load_verify_locations( ctx, "/path/to/trusted.pem", // CA 的信任证书 NULL ) ) { // 在这里处理失败的加载 } // 如果打算使用目录存储可信任库,那么必须要以特定的方式命名文件。 // OpenSSL 附带了一个名为 c_rehash 的工具(在 tools 目录中的一个脚本文件), // 它可以将文件夹配置为可用于 SSL_CTX_load_verify_locations 的路径参数。 if ( !SSL_CTX_load_verify_locations( ctx, NULL, "/path/to/certfolder" ) ) { // 在这里处理错误 } 注:3.2.3.*. 是建立安全连接双向认证的,如果不需要验证客户端,可以不用添加这些函数调用! 3.2.3.1.加载客户端证书 // 加载客户端证书 - 用法与 加载可信任证书 一样 if ( !SSL_CTX_use_certificate_file( ctx, "/path/to/clientcert.pem", SSL_FILETYPE_PEM ) ) { // 在这里处理错误 } 3.2.3.2.加载客户端私钥文件: // 加载客户端私钥文件 - 用法与 加载可信任证书 一样 if ( !SSL_CTX_use_PrivateKey_file( ctx, "private.key", SSL_FILETYPE_PEM ) ) { // 在这里处理错误 } 3.2.3.3.检查私钥是否和证书匹配: // 验证私钥是否与证书一致 if ( !SSL_CTX_check_private_key( ctx ) ) { // 在这里处理错误 } 3.2.4.创建连接: // 将指向 SSL 上下文的指针作为惟一参数,使用 BIO_new_ssl_connect 创建 BIO 对象。 bio = BIO_new_ssl_connect( ctx ); // 还需要获得指向 SSL 结构的指针,在本文中,只将该指针用于 SSL_set_mode 函数。 BIO_get_ssl( bio, &ssl ); // 而这个函数是用来设置 SSL_MODE_AUTO_RETRY 标记的。 // 使用这个选项进行设置,如果服务器突然希望进行一次新的握手,那么 OpenSSL 可以在后台处理它。 // 如果没有这个选项,当服务器希望进行一次新的握手时,进行读或写操作都将返回一个错误, // 同时还会在该过程中设置 retry 标记。 SSL_set_mode( ssl, SSL_MODE_AUTO_RETRY ); 3.2.5.打开安全连接: // 设置 SSL 上下文结构之后,就可以创建连接了。 // 主机名是使用 BIO_set_conn_hostname 函数设置的。 // 主机名和端口的指定格式与前面的相同,也可以是 192.168.12.39:443。 // 该函数还可以建立与服务端的连接。 BIO_set_conn_hostname( bio, "hostname:port" ); // 为了确认已经成功打开连接,必须执行对 BIO_do_connect 的调用。 // 该调用还将执行握手来建立安全连接。 if ( 0 >= BIO_do_connect( bio ) ) { // 处理失败的连接 } 3.2.6.检查证书是否有效: // 连接建立后,必须检查证书,以确定它是否有效。 // 实际上,OpenSSL 为我们完成了这项任务。 // 如果证书有致命的问题(例如,哈希值无效),那么将无法建立连接。 // 但是,如果证书的问题并不是致命的(当它已经过期或者尚不合法时),那么仍可以继续使用连接。 // 可以将 SSL 结构作为惟一参数, // 调用 SSL_get_verify_result 来查明证书是否通过了 OpenSSL 的检验。 // 如果证书通过了包括信任检查在内的 OpenSSL 的内部检查,则返回 X509_V_OK。 // 如果有地方出了问题,则返回一个错误代码,在 OpenSSL 文档的 verify 部分中都进行了介绍。 // 注:该错误代码被记录在命令行工具的 verify 选项下。 // 应该注意的是,验证失败并不意味着连接不能使用。 // 是否应该使用连接取决于验证结果和安全方面的考虑。 // 例如,失败的信任验证可能只是意味着没有可信任的证书。 // 连接仍然可用,只是需要从思想上提高安全意识。 // OpenSSL 在对证书进行验证时,有一些安全性检查并没有执行, // 包括证书的失效检查和对证书中通用名的有效性验证。 if ( X509_V_OK != SSL_get_verify_result( ssl ) ) { //处理失败验证 } 3.2.7.检索证书: // 如果您希望向用户显示证书的内容,或者要根据主机名或证书权威对证书进行验证, // 那么就需要检索证书的内容。 // 要在验证测试结果之后再检索证书,请调用 SSL_get_peer_certificate() 。 // 它返回一个指向该证书的 X509 指针,如果证书不存在,就返回 NULL 。 X509* peerCertificate = NULL; if ( X509_V_OK == SSL_get_verify_result(ssl) ) { peerCertificate = SSL_get_peer_certificate( ssl ); } else { // 在这里处理检验错误 } 3.2.8.验证证书: // 在握手时所提供的服务器的证书应该有一个名字与该服务器的主机名匹配。 // 如果没有,那么这个证书就应该标记为值得怀疑的。 // 内部验证过程已经对证书进行信任和有效期的验证; // 如果这个证书已经超期,或者包含一个不可信的签名,那么这个证书就会被标记为无效的。 // 由于这不是 SSL 标准的一部分,因此 OpenSSL 并不需要根据主机名对该证书的名字进行检查。 // 证书的“名字”实际上是证书中的 Common Name 字段。 // 这个字段应该从证书中进行检索,并根据主机名进行验证。 // 如果二者不能匹配,就只有怀疑这个证书无效了。 // 有些公司(例如 Yahoo)就在不同的主机上使用相同的证书, // 即使证书中的 Common Name 只是用于一个主机的。 // 为了确保这个证书是来自于相同的公司,可以进行更深入的检查,但是这完全取决于项目的安全性需要。 // 从证书中检索通用名需要两个步骤: // 1)从证书结构检索 X509_NAME 对象。 // 使用 X509_get_subject_name() 从证书中检索 X509_NAME 结构。 // 这会返回一个指向 X509_NAME 的对象。 // 2)然后从 X509_NAME 对象检索名字。 // 请使用 X509_NAME_get_text_by_NID() 来检索通用名,并保存到一个字符串中。 char commonName [512]; X509_NAME* name = X509_get_subject_name( peerCertificate ); X509_NAME_get_text_by_NID( name, NID_commonName, // openssl-0.9.8o 的宏值为 13 commonName, 512 ); // 使用标准的 C 字符串函数或您习惯使用的字符串库对通用名和主机名进行比较。 // 对不匹配的处理,完全取决于项目的需要或用户的决策。 // 如果要更深入地进行检查,我建议使用一个单独的字符串库来降低复杂性。 // 以下只是简单比较,如果必要的话,可以更彻底地检查证书 if ( 0 != stricmp( commonName, hostname ) ) { // 在这里处理一个可疑的证书 } 3.2.9.清除 SSL 上下文: // 必须在结束应用程序之前的某个时刻释放 SSL 上下文结构。 // 可以调用 SSL_CTX_free 来释放该结构。 SSL_CTX_free( ctx ); 4.与服务器进行通信: 不管 BIO 对象是套接字还是文件,对其进行的读和写操作都是通过以下两个函数来完成的: BIO_read 和 BIO_write 。 // BIO_read 将尝试从服务器读取一定数目的字节。 // 它返回读取的字节数、 0 或者 -1。 // 在受阻塞的连接中,该函数返回 0,表示连接已经关闭,而 -1 则表示连接出现错误。 // 在非阻塞连接的情况下,返回 0 表示没有可以获得的数据,返回 -1 表示连接出错。 // 可以调用 BIO_should_retry 来确定是否可能重复出现该错误。 int x = BIO_read( bio, buf, len ); if ( 0 == x ) { // 处理已关闭的连接 } else if ( 0 > x ) { if ( !BIO_should_retry( bio ) ) { // 在这里处理读取失败 } // 做某事以尝试重试 } // BIO_write 会试着将字节写入套接字。 // 它将返回实际写入的字节数、0 或者 -1。 // 同 BIO_read ,0 或 -1 不一定表示错误。 // BIO_should_retry 是找出问题的途径。 // 如果需要重试写操作,它必须使用和前一次完全相同的参数。 if ( 0 >= BIO_write( bio, buf, len ) ) { if ( !BIO_should_retry( bio ) ) { // 在这里处理写入失败 } // 做某事以尝试重试 } 5.关闭连接: 关闭连接也很简单。 您可以使用以下两种方式之一来关闭连接: BIO_reset 或 BIO_free_all 。 如果您还需要重新使用对象,那么请使用第一种方式。 如果您不再重新使用它,则可以使用第二种方式。 // BIO_reset 关闭连接并重新设置 BIO 对象的内部状态,以便可以重新使用连接。 // 如果要在整个应用程序中使用同一对象,比如使用一台安全的聊天客户机,那么这样做是有益的。 // 该函数没有返回值。 BIO_reset( bio ); // BIO_free_all 所做正如其所言: // 它释放内部结构体,并释放所有相关联的内存,其中包括关闭相关联的套接字。 // 注:如果将 BIO 嵌入于一个类中,那么应该在类的析构函数中使用这个调用。 BIO_free_all(bio); 6.错误检测: 首先,您需要得到错误代码本身; ERR_get_error 可以完成这项任务; 然后,需要将错误代码转换为错误字符串, 它是一个指向由 SSL_load_error_strings 或 ERR_load_BIO_strings 加载到内存中的永久字符串的指针。 可以在一个嵌套调用中完成这项操作。 ERR_reason_error_string 返回一个静态字符串的指针, 然后可以将字符串显示在屏幕上、写入文件, 或者以任何您希望的方式进行处理。 ERR_lib_error_string 指出错误发生在哪个库中。 ERR_func_error_string 返回导致错误的 OpenSSL 函数。 // 例如:打印出最后一个错误 printf( "Error: %s\n", ERR_reason_error_string( ERR_get_error() ) ); 您还可以让库给出预先格式化了的错误字符串。 可以调用 ERR_error_string 来得到该字符串。 该函数将错误代码和一个预分配的缓冲区作为参数。 而这个缓冲区必须是 256 字节长。 如果参数为 NULL, 则 OpenSSL 会将字符串写入到一个长度为 256 字节的静态缓冲区中,并返回指向该缓冲区的指针。 否则,它将返回您给出的指针。 如果您选择的是静态缓冲区选项,那么在下一次调用 ERR_error_string 时,该缓冲区会被覆盖。 printf( "%s\n", ERR_error_string( ERR_get_error(), NULL ) ); 您还可以将整个错误队列转储到文件或 BIO 中。 可以通过 ERR_print_errors 或 ERR_print_errors_fp 来实现这项操作。 队列是以可读格式被转储的。 第一个函数将队列发送到 BIO , ERR_print_errors( BIO* ); 第二个函数将队列发送到 FILE 。 ERR_print_errors_fp( FILE* ); 字符串格式如下(引自 OpenSSL 文档): [pid]:error:[error code]:[library name]:[function name]:[reason string]:[file name]:[line]:[optional text message] 其中, [pid] 是进程 ID, [error code] 是一个 8 位十六进制代码, [file name] 是 OpenSSL 库中的源代码文件, [line] 是源文件中的行号。 以下为 访问 HTTPS 的双向认证示例代码: #include "stdio.h" #include "string.h" // 使用 OpenSSL API 进行安全编程所需的三个头文件 // debug openssl 可以在 openssl-0.9.8o/Makefile 中的 CFLAG 变量里添加 -g -O0 // 本程序需要的 openssl 库是 libcrypto.a 和 libssl.a ,还需要 -l dl // ssl 的主要数据结构定义在 ssl.h 中。 #include <openssl/ssl.h> // BIO 抽像库来处理包括文件和套接字在内的各种类型的通信。 #include <openssl/bio.h> // #include <openssl/err.h> int main() { int iResult = 0; // 零为成功,负数为错误码 BIO* bio = NULL; SSL* ssl = NULL; SSL_CTX* ctx = NULL; X509* pX509 = NULL; SSL_METHOD* sslMethod = NULL; char commonName[512] = { 0 }; X509_NAME* pX509_NAME = NULL; // http://192.168.12.39/testssl 对应的 HTTP 请求协议 // 其中的 /testssl/ 最后的 / 是必须的 char szHttpReq[] = "GET /testssl/ HTTP/1.1\r\n" "Host:192.168.12.39\r\n" "Connection: keep-alive\r\n\r\n"; // 一次收到的字节数 unsigned int uiRecBytes = 0; // 接收服务端信息的字节缓冲区 unsigned char baRecBuffer[1024]; // 最终收完后的服务端信息缓冲区 unsigned char* pbRecFinish = NULL; // 临时备份缓冲区 unsigned char* pbRecBak = NULL; // 临时备份缓冲区字节大小 unsigned int uiRecBakLen = 0; // 按行打印收到的信息 char* pMsgline = NULL; // 初始化 OpenSSL 库,只需以下四行代码 // 初始化 SSL 算法库函数,调用 SSL 系列函数之前必须调用此函数! SSL_library_init(); // 加载 BIO 抽像库错误信息 ERR_load_BIO_strings(); // 加载 SSL 抽像库错误信息 SSL_load_error_strings(); // 加载所有加密和散列函数 OpenSSL_add_all_algorithms(); do // 非循环,只是为了减少分支缩进 { // 设置客户端使用的 SSL 协议算法 sslMethod = TLSv1_client_method(); if ( NULL == sslMethod ) { printf( "TLSv1_client_method err: %s\n", ERR_error_string( ERR_get_error(), NULL ) ); iResult = -1; break; } // 创建 SSL 上下文 ctx = SSL_CTX_new( sslMethod ); if ( NULL == ctx ) { printf( "SSL_CTX_new err: %s\n", ERR_error_string( ERR_get_error(), NULL ) ); iResult = -2; break; } // 加载可信任的 CA 证书 if( 0 == SSL_CTX_load_verify_locations( ctx, "certnew.pem", NULL ) ) { printf( "SSL_CTX_load_verify_locations err: %s\n", ERR_error_string( ERR_get_error(), NULL ) ); iResult = -3; break; } // 加载客户端证书 if ( 0 == SSL_CTX_use_certificate_file( ctx, "test1.pem", SSL_FILETYPE_PEM ) ) { printf( "SSL_CTX_use_certificate_file err: %s\n", ERR_error_string( ERR_get_error(), NULL ) ); iResult = -4; break; } // 加载客户端私钥文件 if ( 0 == SSL_CTX_use_PrivateKey_file( ctx, "private.key", SSL_FILETYPE_PEM ) ) { printf( "SSL_CTX_use_PrivateKey_file err: %s\n", ERR_error_string( ERR_get_error(), NULL ) ); iResult = -5; break; } // 验证私钥是否与证书一致 if ( 0 == SSL_CTX_check_private_key( ctx ) ) { printf( "SSL_CTX_check_private_key err: %s\n", ERR_error_string( ERR_get_error(), NULL ) ); iResult = -6; break; } // 创建 BIO 对象 bio = BIO_new_ssl_connect( ctx ); if ( NULL == bio ) { printf( "BIO_new_ssl_connect err: %s\n", ERR_error_string( ERR_get_error(), NULL ) ); iResult = -7; break; } // 获得指向 SSL 结构的指针 BIO_get_ssl( bio, &ssl ); // SSL_MODE_AUTO_RETRY - 如果服务端希望进行一次新的握手,OpenSSL 后台处理它。 // 没有 SSL_MODE_AUTO_RETRY 此选项,则新握手返回错误,且设置 retry 标记。 SSL_set_mode( ssl, SSL_MODE_AUTO_RETRY ); // 建立与服务端的连接 BIO_set_conn_hostname( bio, "192.168.12.39:443" ); // 为了确认成功打开连接,需执行 BIO_do_connect 函数 // 该调用还将执行握手来建立安全连接 if ( 0 >= BIO_do_connect( bio ) ) { printf( "BIO_do_connect err: %s\n", ERR_error_string( ERR_get_error(), NULL ) ); iResult = -8; break; } // 连接建立后,必须检查证书,以确定它是否有效。 // 实际上,OpenSSL 为我们完成了这项任务。 // 如果证书有致命的问题(如:哈希值无效),那么将无法建立连接。 // 如果证书无致命的问题(如:已过期或尚不合法时),那么可以继续使用连接。 // 调用 SSL_get_verify_result 来查明证书是否通了 OpenSSL 的检验。 // 如果证书通过了包括信任检查在内的 OpenSSL 的内部检查,则返回 X509_V_OK 。 // 如果有地方出了问题,则返回一个错误码,该代码被记录在命令行工具的 verify 选项下。 if ( X509_V_OK != SSL_get_verify_result( ssl ) ) { printf( "SSL_get_verify_result err: %s\n", ERR_error_string( ERR_get_error(), NULL ) ); iResult = -9; break; } // 如果您希望向用户显示证书的内容,或者要根据主机名或证书权威对证书进行验证, // 那么就需要检索证书的内容。 // 要在验证测试结果之后再检索证书,请调用 SSL_get_peer_certificate()。 // 它返回一个指向该证书的 X509 指针,如果证书不存在,就返回 NULL 。 pX509 = SSL_get_peer_certificate( ssl ); if ( NULL == pX509 ) { printf( "SSL_get_peer_certificate err: %s\n", ERR_error_string( ERR_get_error(), NULL ) ); iResult = -10; break; } // 使用 X509_get_subject_name() 从证书中检索 X509_NAME 结构。 // 这会返回一个指向 X509_NAME 的对象。 pX509_NAME = X509_get_subject_name( pX509 ); if ( NULL == pX509_NAME ) { printf( "X509_get_subject_name err: %s\n", ERR_error_string( ERR_get_error(), NULL ) ); iResult = -10; break; } X509_NAME_get_text_by_NID( pX509_NAME, NID_commonName, commonName, 512 ); if ( 0 != strcasecmp( commonName, "192.168.12.39" ) ) { printf( "Certificate's name 192.168.12.39 != %s\n", commonName ); iResult = -11; break; } // 通过 SSL 发送 HTTP 请求 // BIO_write 会试着将字节写入套接字。 // 它将返回实际写入的字节数、0 或者 -1。 // 同 BIO_read ,0 或 -1 不一定表示错误。 // BIO_should_retry 是找出问题的途径。 // 如果需要重试写操作,它必须使用和前一次完全相同的参数。 if ( 0 >= BIO_write( bio, szHttpReq, strlen( szHttpReq ) ) ) { printf( "Sent HTTP request failed by BIO_write\n" ); iResult = -12; break; } // 开始接收服务端信息数据 // BIO_read 将尝试从服务器读取一定数目的字节。 // 它返回读取的字节数、 0 或者 -1。 // 在受阻塞的连接中,该函数返回 0,表示连接已经关闭,而 -1 则表示连接出现错误。 // 在非阻塞连接的情况下,返回 0 表示没有可以获得的数据,返回 -1 表示连接出错。 // 可以调用 BIO_should_retry 来确定是否可能重复出现该错误。 while ( 0 < ( uiRecBytes = BIO_read( bio, (void*)baRecBuffer, sizeof( baRecBuffer ) ) ) ) { // 最终收完后的服务端信息缓冲区指针不为空说明要先备份后扩展 uiRecBakLen = 0; // 每次都清空以防计算错误 if ( NULL != pbRecFinish ) { uiRecBakLen = strlen( (char*)pbRecFinish ); pbRecBak = (unsigned char*)malloc( uiRecBakLen ); // 分配失败 if ( NULL == pbRecBak ) { printf( "pbRecBak call malloc to allocate mem fail\n" ); free( pbRecFinish ); pbRecFinish = NULL; uiRecBakLen = 0; // 每次都清空以防计算错误 break; } // 结尾的零没有一起拷贝!!! memcpy( pbRecBak, pbRecFinish, uiRecBakLen ); free( pbRecFinish ); pbRecFinish = NULL; } // 备份缓冲区和接收缓冲区都没有结尾的零,所以要多分一个字节 pbRecFinish = (unsigned char*)malloc( uiRecBakLen + uiRecBytes + 1 ); // 分配失败 if ( ( NULL == pbRecFinish ) && ( NULL != pbRecBak ) ) { printf( "pbRecFinish call malloc to allocate mem fail\n" ); free( pbRecBak ); pbRecBak = NULL; uiRecBakLen = 0; // 每次都清空以防计算错误 break; } // 全清零,此操作比数组快,且为字符串最后补零了 memset( pbRecFinish, 0, uiRecBakLen + uiRecBytes + 1 ); // 先恢复备份的 if ( NULL != pbRecBak ) { memcpy( pbRecFinish, pbRecBak, uiRecBakLen ); free( pbRecBak ); pbRecBak = NULL; } // 再拷贝入新收的 memcpy( pbRecFinish + uiRecBakLen, baRecBuffer, uiRecBytes ); // 未收满,说明没有数据可收了 if ( sizeof( baRecBuffer ) > uiRecBytes ) { break; } } if ( NULL != pbRecFinish ) { printf( "%s\n", strtok( (char*)pbRecFinish, "\r\n" ) ); while( NULL != ( pMsgline = strtok( NULL, "\r\n" ) ) ) { printf( "%s\n", pMsgline ); } free( pbRecFinish ); pbRecFinish = NULL; } break; // 流程到此结束 } while ( 0 ); if ( NULL != pbRecFinish ) { // 释放申请的内存 free( pbRecFinish ); pbRecFinish = NULL; } if ( NULL == bio ) { // 释放内部结构体相关的内存 BIO_free_all( bio ); } if ( NULL != ctx ) { // 清除 SSL 上下文 SSL_CTX_free( ctx ); } return iResult; } 分享: