網路上的文件傳輸功能也是很有必要實現一下的,網路傳輸文件的過程通常分為客戶端和伺服器端兩部分。客戶端可以選擇上傳或下載文件,將文件分塊並逐塊發送到伺服器,或者從伺服器分塊地接收文件。伺服器端接收來自客戶端的請求,根據請求類型執行對應的操作,並根據發送的文件名或其他標識來確定要傳輸的文件。在實現文件傳... ...
網路上的文件傳輸功能也是很有必要實現一下的,網路傳輸文件的過程通常分為客戶端和伺服器端兩部分。客戶端可以選擇上傳或下載文件,將文件分塊並逐塊發送到伺服器,或者從伺服器分塊地接收文件。伺服器端接收來自客戶端的請求,根據請求類型執行對應的操作,並根據發送的文件名或其他標識來確定要傳輸的文件。
在實現文件傳輸之前,需要先打開要傳輸的文件,並獲取文件的大小信息,也可以通過其他方式獲取文件的信息。在客戶端和伺服器端都準備就緒後,可以通過套接字來發送文件數據。在傳輸文件的過程中,可以將文件分解為若幹個數據包進行傳輸,以減少數據傳輸中的丟包或傳輸錯誤。每個數據包的長度可以根據實際情況進行選擇,通常選擇1024
位元組或更大,也可以設置成更小的值。傳輸文件的過程中,還需要實現一定的錯誤處理機制,例如檢測傳輸過程中的超時、丟包、不完整數據等情況,併在必要時進行錯誤重傳或協商其他解決方案。
首先無論時服務端還是客戶端都需要封裝兩個函數,其中GetFileName()
函數用於當用戶傳入文件的具體路徑信息時自動獲取到該文件的文件名,第二個函數GetFileSize()
則用於傳入文件路徑並自動獲取到該文件的位元組數。
// 傳入路徑得到文件名
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;
}
}
// 獲取文件大小
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;
}
接著我們來看一下RecvFile()
接收文件函數是如何實現的,首先第一個發送用於向服務端發出我需要下載具體的那個目錄下的文件,接著服務端會返回該目錄文件的長度,此時我們通過fopen()
創建一個新文件,並以此迴圈接收該文件的長度,每次接收成功後自動的fwrite
寫出到文件中,當文件被接收完畢後,則通過fclose(pointer)
保存並關閉文件。
// 接收文件
bool RecvFile(SOCKET ptr, char* LocalPath, char* RemoteFile)
{
// 發送需要下載的文件路徑
send(ptr, RemoteFile, strlen(RemoteFile), 0);
// 接收文件長度
long long file_size = 0;
recv(ptr, (char*)&file_size, sizeof(int), 0);
if (file_size <= 0)
{
return false;
}
// 保存文件到指定目錄下
char *file_name = GetFileName(RemoteFile);
char file_all_name[1024] = { 0 };
strcat(file_all_name, LocalPath);
strcat(file_all_name, file_name);
std::cout << "生成保存路徑: " << file_all_name << std::endl;
FILE* pointer = fopen(file_all_name, "wb");
char buffer[1024] = { 0 };
if (pointer != NULL)
{
long long length = 0;
long long total_length = 0;
// 迴圈接收位元組數據,每次接收1024位元組
while ((length = recv(ptr, buffer, 1024, 0)) > 0)
{
// 寫出文件並判斷是否寫出成功
if (fwrite(buffer, sizeof(char), length, pointer) < length)
{
break;
}
// 每次累加遞增
total_length += length;
memset(buffer, 0, 1024);
// 判斷文件長度是否全部接收完畢
if (total_length >= file_size)
{
std::cout << "文件接收完畢, 接收位元組數: " << total_length << std::endl;
fclose(pointer);
return true;
}
}
fclose(pointer);
}
return false;
}
對於SendFile()
發送文件而言首先我們接收到客戶端傳來的文件路徑,並通過該路徑得到該文件的具體長度,第一次調用發送函數將文件的長度傳遞給客戶端,此時打開我們所需要發送的文件,並通過迴圈的方式向客戶端傳輸,當數據包傳輸完畢後則自動關閉文件。
// 發送指定文件
bool SendFile(SOCKET ptr)
{
// 接收文件路徑
char file_path[1024] = { 0 };
recv(ptr, file_path, 1024, 0);
// 得到文件長度併發送給服務端
long long file_size = GetFileSize(file_path);
if (file_size <= 0)
{
return false;
}
send(ptr, (char*)&file_size, sizeof(int), 0);
std::cout << "發送文件長度: " << file_size << std::endl;
// 迴圈發送數據
char buffer[1024] = { 0 };
FILE* pointer = fopen(file_path, "rb");
if (pointer != NULL)
{
long long length = 0;
long long total_length = 0;
// 迴圈發送數據
while ((length = fread(buffer, sizeof(char), 1024, pointer)) > 0)
{
send(ptr, buffer, length, 0);
memset(buffer, 0, 1024);
total_length += length;
}
if (total_length == file_size)
{
return true;
}
}
return false;
}
14.9.1 服務端實現
如下代碼展示瞭如何使用Winsock
進行TCP協議的文件傳輸。首先使用WSAStartup
函數對Winsock
庫進行初始化。然後創建一個socket
,設置IP地址、埠號等信息,並將該socket
和本地服務端的地址綁定起來。接下來對該socket
進行監聽,等待客戶端的連接請求。
當有客戶端連接請求到來時,accept函數會接收請求,並創建一個新的socket
與客戶端進行通信。在與客戶端通信的過程中,可以通過send
和recv
函數進行數據的傳輸,實現文件的上傳和下載功能。此處的代碼調用RecvFile
函數,該函數為自定義實現的接收文件函數,負責接收數據並將接收到的文件存儲到指定的路徑下。
int main(int argc, char* argv[])
{
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
exit(1);
// 聲明並初始化一個服務端(本地)的地址結構
sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.S_un.S_addr = INADDR_ANY;
server_addr.sin_port = htons(8087);
// 創建socket
SOCKET m_Socket = socket(AF_INET, SOCK_STREAM, 0);
if (SOCKET_ERROR == m_Socket)
exit(1);
// 綁定socket和服務端(本地)地址
if (SOCKET_ERROR == bind(m_Socket, (LPSOCKADDR)&server_addr, sizeof(server_addr)))
exit(1);
// 監聽
if (SOCKET_ERROR == listen(m_Socket, 10))
exit(1);
sockaddr_in client_addr;
int client_addr_len = sizeof(client_addr);
SOCKET m_New_Socket = accept(m_Socket, (sockaddr*)&client_addr, &client_addr_len);
// 接收遠程d://lyshark.exe放到本地的d://11g/目錄下
bool ref = RecvFile(m_New_Socket, (char*)"d://11g/", (char*)"d://lyshark.exe");
std::cout << "接收狀態: " << ref << std::endl;
closesocket(m_New_Socket);
closesocket(m_Socket);
WSACleanup();
system("pause");
return 0;
}
14.9.2 客戶端實現
如下客戶端代碼實現了一個基於TCP協議的文件傳輸客戶端。首先使用WSAStartup
函數對Winsock
庫進行初始化。然後創建一個socket,並設置服務端的IP地址和埠號。之後通過connect
函數與服務端建立連接,連接成功後調用SendFile
函數進行文件傳輸,將指定的文件發送到服務端。文件傳輸完成後,關閉socket
連接,清除Winsock
資源。
int main(int argc, char* argv[])
{
while (true)
{
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
exit(1);
// 創建socket
SOCKET c_Socket = socket(AF_INET, SOCK_STREAM, 0);
//指定服務端的地址
sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
server_addr.sin_port = htons(8087);
if (SOCKET_ERROR != connect(c_Socket, (LPSOCKADDR)&server_addr, sizeof(server_addr)))
{
bool ref = SendFile(c_Socket);
std::cout << "文件發送狀態: " << ref << std::endl;
}
closesocket(c_Socket);
WSACleanup();
Sleep(1000);
}
return 0;
}
文件傳輸功能代碼就這些,其實理解起來並不難,讀者可自行編譯並運行上述代碼,運行後則可接收遠程d://lyshark.exe
文件,並放到本地的d://11g/
目錄下,輸出效果圖如下;
本文作者: 王瑞
本文鏈接: https://www.lyshark.com/post/323aa250.html
版權聲明: 本博客所有文章除特別聲明外,均採用 BY-NC-SA 許可協議。轉載請註明出處!
文章出處:https://www.cnblogs.com/LyShark/p/17768898.html
本博客所有文章除特別聲明外,均採用 BY-NC-SA 許可協議。轉載請註明出處!