20.8 OpenSSL 套接字SSL传输文件

有了上面的基础那么传输文件的实现就变得简单了,在传输时通常我们需要打开文件,并每次读入1024个字节的数据包,通过SSL加密传输即可,此处的文件传输功能在原生套接字章节中也进行过详细讲解,此处我们还是使用原来的密钥对,实现一个服务端等待客户端上传,当客户端连接到服务端后则开始传输文件,服务端接收文件的功能。

服务端代码部分,此处我们只需要实现一个DownloadFile函数,该函数接收一个SSL套接字,与保存文件路径即可,其他部分同上。

#include <WinSock2.h>
#include <iostream>
#include <string.h>
#include <errno.h>
#include <stdlib.h>

#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <openssl/pem.h>
#include <openssl/crypto.h>

extern "C"
{
#include <openssl/applink.c>
}

#pragma comment(lib, "WS2_32.lib")
#pragma comment(lib,"libssl.lib")
#pragma comment(lib,"libcrypto.lib")

#define MAXBUF 1024

// 从路径中获取当前文件名
char* GetFileName(char* Path)
{
if (strchr(Path, '\\'))
{
char ch = '\\';
char* ref = strrchr(Path, ch) + 1;
return ref;
}
else
{
char ch = '/';
char* ref = strrchr(Path, ch) + 1;
return ref;
}
}

// 下载文件到当前目录
// 传入套接字句柄,以及放置根目录
void DownloadFile(SSL* ptr, const char* RootPath)
{
int FileSize = 0;
char FilePath[1024] = { 0 };
char buffer[1024] = { 0 };

// 接收文件长度
SSL_read(ptr, &FileSize, 4);

// 接收文件路径
SSL_read(ptr, FilePath, 1024);

// 获取到文件名
char* FileName = GetFileName(FilePath);

// 拼接路径
char sz[1024] = { 0 };
strcpy(sz, RootPath);
strcat(sz, FileName);
std::cout << sz << std::endl;

// 保存文件到当前目录
FILE* pointer = fopen(sz, "wb");

if (pointer != NULL)
{
DWORD length = 0;
DWORD total_length = 0;

// 循环接收字节数据,每次接收1024字节
while ((length = SSL_read(ptr, buffer, 1024)) > 0)
{
// 写出文件并判断是否写出成功
if (fwrite(buffer, sizeof(char), length, pointer) < length)
{
break;
}

// 每次累加递增
total_length += length;
memset(buffer, 0, 1024);

// 判断文件长度是否全部接收完毕
if (total_length >= FileSize)
{
std::cout << "[传输完成] " << total_length << std::endl;
fclose(pointer);
return;
}
}
fclose(pointer);
}
}

int main(int argc, char** argv)
{
SOCKET sockfd, new_fd;
struct sockaddr_in socket_ptr, their_addr;
char buf[MAXBUF + 1];
SSL_CTX* ctx;

SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();

ctx = SSL_CTX_new(SSLv23_server_method());
if (ctx == NULL)
{
return 0;
}

if (SSL_CTX_use_certificate_file(ctx, "d://cacert.pem", SSL_FILETYPE_PEM) <= 0)
{
return 0;
}

if (SSL_CTX_use_PrivateKey_file(ctx, "d://privkey.pem", SSL_FILETYPE_PEM) <= 0)
{
return 0;
}

if (!SSL_CTX_check_private_key(ctx))
{
return 0;
}

WSADATA wsaData;

WSAStartup(MAKEWORD(2, 2), &wsaData);
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
WSACleanup();
return 0;
}

if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
return 0;
}

socket_ptr.sin_family = AF_INET;
socket_ptr.sin_addr.s_addr = inet_addr("127.0.0.1");
socket_ptr.sin_port = htons(9999);

if (bind(sockfd, (struct sockaddr*)&socket_ptr, sizeof(struct sockaddr)) == -1)
{
return 0;
}
if (listen(sockfd, 10) == -1)
{
return 0;
}

while (1)
{
SSL* ssl;
int len = sizeof(struct sockaddr);
if ((new_fd = accept(sockfd, (struct sockaddr*)&their_addr, &len)) != -1)
{
printf("客户端地址: %s --> 端口: %d --> 套接字: %d \n", inet_ntoa(their_addr.sin_addr), ntohs(their_addr.sin_port), new_fd);
}

ssl = SSL_new(ctx);
SSL_set_fd(ssl, new_fd);
if (SSL_accept(ssl) == -1)
{
closesocket(new_fd);
break;
}

// 调用下载文件函数
DownloadFile(ssl, "d://lyshark/");

finish:
SSL_shutdown(ssl);
SSL_free(ssl);
closesocket(new_fd);
}

closesocket(sockfd);
WSACleanup();
SSL_CTX_free(ctx);

system("pause");
return 0;
}

客户端部分,同样代码中只需要实现一个UploadFile函数,该函数用于发送本地文件到远程,其他部分同上。

#include <WinSock2.h>
#include <iostream>
#include <string.h>
#include <errno.h>
#include <stdlib.h>

#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <openssl/pem.h>
#include <openssl/crypto.h>

extern "C"
{
#include <openssl/applink.c>
}

#pragma comment(lib, "WS2_32.lib")
#pragma comment(lib,"libssl.lib")
#pragma comment(lib,"libcrypto.lib")

#define MAXBUF 1024

// 获取文件大小
int GetFileSize(std::string FileName)
{
FILE* pointer = NULL;
pointer = fopen(FileName.c_str(), "rb");
if (pointer != NULL)
{
fseek(pointer, 0, SEEK_END);
int size = ftell(pointer);
fclose(pointer);
return size;
}
return 0;
}

// 上传文件,传入socket套接字句柄,需要发送的文件路径
void UploadFile(SSL* ptr, const char* FilePath)
{
int FileSize = GetFileSize(FilePath);
char buffer[1024] = { 0 };

// 发送文件长度
SSL_write(ptr, &FileSize, 4);

// 发送完整文件路径
SSL_write(ptr, FilePath, strlen(FilePath));
FILE* pointer = fopen(FilePath, "rb");
if (pointer != NULL)
{
int length = 0;
DWORD total_length = 0;

while ((length = fread(buffer, sizeof(char), 1024, pointer)) > 0)
{
SSL_write(ptr, buffer, length);
memset(buffer, 0, 1024);
}
}
}

int main(int argc, char** argv)
{
int sockfd, len;
struct sockaddr_in dest;
char buffer[MAXBUF + 1] = { 0 };
SSL_CTX* ctx;
SSL* ssl;

SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();

ctx = SSL_CTX_new(SSLv23_client_method());
if (ctx == NULL)
{
WSACleanup();
return 0;
}

WSADATA wsaData;

WSAStartup(MAKEWORD(2, 2), &wsaData);
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
WSACleanup();
return 0;
}

if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
WSACleanup();
return 0;
}

dest.sin_family = AF_INET;
dest.sin_addr.s_addr = inet_addr("127.0.0.1");
dest.sin_port = htons(9999);

if (connect(sockfd, (struct sockaddr*)&dest, sizeof(dest)) != 0)
{
WSACleanup();
return 0;
}

ssl = SSL_new(ctx);
SSL_set_fd(ssl, sockfd);

if (SSL_connect(ssl) != -1)
{
printf("SSL 连接类型: %s \n", SSL_get_cipher(ssl));
}

// 发送文件
UploadFile(ssl, "d://lyshark.exe");

finish:
SSL_shutdown(ssl);
SSL_free(ssl);
closesocket(sockfd);
SSL_CTX_free(ctx);

system("pause");
return 0;
}

读者可自行编译这段代码,并首先启动服务端等待传输,接着打开客户端,此时客户端中的d://lyshark.exe将被传输到服务端的特定目录下,如下图所示;