2010-06-26 111 views
5

我試圖通過套接字發送文件。我創建了一個程序,它適用於文件類型,如.cpp,.txt和其他文本文件。但是二進制文件,圖像(.jpg,.png)和壓縮文件(如.zip和.rar)未正確發送。我知道這與文件大小無關,因爲我使用大型.txt文件進行了測試。我不知道這個問題,我收到了所有發送的字節,但文件無法打開。大多數時候文件已損壞,無法查看。我已經通過Google搜索了一個解決方案,並發現其他人遇到同樣的問題並且沒有解決方案。因此,通過幫助我,您也可以幫助其他需要解決方案的人。通過C++套接字發送圖像(Linux)

服務器代碼:

#include <stdlib.h> 
#include <stdio.h> 
#include <string.h> 
#include <sys/socket.h> 
#include <sys/types.h> 
#include <netinet/in.h> 
#include <arpa/inet.h> 
#include <unistd.h> 

int main (int agrc, char *argv[]) 
{ 
    /******** Program Variable Define & Initialize **********/ 
    int Main_Socket; // Main Socket For Server 
    int Communication_Socket; // Socket For Special Clients 
    int Status; // Status Of Function 
    struct sockaddr_in Server_Address; // Address Of Server 
    struct sockaddr_in Client_Address;// Address Of Client That Communicate with Server 
    int Port; 
    char Buff[100] = ""; 
    Port = atoi(argv[2]); 
    printf ("Server Communicating By Using Port %d\n", Port); 
    /******** Create A Socket To Communicate With Server **********/ 
    Main_Socket = socket (AF_INET, SOCK_STREAM, 0); 
    if (Main_Socket == -1) 
    { 
      printf ("Sorry System Can Not Create Socket!\n"); 
    } 
    /******** Create A Address For Server To Communicate **********/ 
    Server_Address.sin_family = AF_INET; 
    Server_Address.sin_port = htons(Port); 
    Server_Address.sin_addr.s_addr = inet_addr(argv[1]); 
    /******** Bind Address To Socket **********/ 
    Status = bind (Main_Socket, (struct sockaddr*)&Server_Address, sizeof(Server_Address)); 
    if (Status == -1) 
    { 
      printf ("Sorry System Can Not Bind Address to The Socket!\n"); 
    } 
    /******** Listen To The Port to Any Connection **********/   
    listen (Main_Socket,12);  
    socklen_t Lenght = sizeof (Client_Address); 
    while (1) 
    { 
     Communication_Socket = accept (Main_Socket, (struct sockaddr*)&Client_Address, &Lenght); 

     if (!fork()) 
     { 

      FILE *fp=fopen("recv.jpeg","w"); 
      while(1) 
      { 
       char Buffer[2]=""; 
       if (recv(Communication_Socket, Buffer, sizeof(Buffer), 0)) 
       { 
        if (strcmp (Buffer,"Hi") == 0 ) 
        { 
         break; 
        } 
        else 
        { 
         fwrite(Buffer,sizeof(Buffer),1, fp); 
        } 
       } 
      } 
      fclose(fp); 
      send(Communication_Socket, "ACK" ,3,0); 
      printf("ACK Send"); 
      exit(0); 
     } 
    } 
    return 0; 
} 

客戶端代碼:

#include <stdlib.h> 
#include <stdio.h> 
#include <string.h> 
#include <sys/socket.h> 
#include <sys/types.h> 
#include <netinet/in.h> 
#include <arpa/inet.h> 
#include <unistd.h> 

int main (int agrc, char *argv[]) 
{ 
    int Socket; 

    struct sockaddr_in Server_Address; 
    Socket = socket (AF_INET, SOCK_STREAM, 0); 
    if (Socket == -1) 
    { 
     printf ("Can Not Create A Socket!");  
    } 
    int Port ; 
    Port = atoi(argv[2]); 
    Server_Address.sin_family = AF_INET; 
    Server_Address.sin_port = htons (Port); 
    Server_Address.sin_addr.s_addr = inet_addr(argv[1]); 
    if (Server_Address.sin_addr.s_addr == INADDR_NONE) 
    { 
     printf ("Bad Address!"); 
    } 
    connect (Socket, (struct sockaddr *)&Server_Address, sizeof (Server_Address)); 


    FILE *in = fopen("background.jpeg","r"); 
    char Buffer[2] = ""; 
    int len; 
    while ((len = fread(Buffer,sizeof(Buffer),1, in)) > 0) 
    {    
     send(Socket,Buffer,sizeof(Buffer),0);    
    } 
    send(Socket,"Hi",sizeof(Buffer),0); 

    char Buf[BUFSIZ]; 
    recv(Socket, Buf, BUFSIZ, 0); 
    if (strcmp (Buf,"ACK") == 0 ) 
    { 
     printf("Recive ACK\n"); 
    }   
    close (Socket); 
    fclose(in); 
    return 0; 
} 
+0

這真的是Linux特有的? – Wizard79 2010-06-26 18:44:30

+0

@Lorenzo - 如果你不知道是什麼問題,如何將一個事先知道嗎?你可以合理地詢問它是否與C++相關。我們在哪裏畫線? – Duck 2010-06-26 18:57:17

+0

但是我沒有找到任何理由說這是Linux特有的問題。它可能出現在提供套接字接口的每個系統上。 – Wizard79 2010-06-26 19:12:33

回答

0

對於閱讀的ASCII文本文件,一個char緩衝是可以接受的。

對於讀取二進制數據,您將需要使用unsigned char,否則您的數據將變得混亂,因爲二進制數據是無符號字節。

+0

oh thx ill試試 – Warsame 2010-06-26 18:45:33

+0

現在我有一個問題試圖比較無符號字符緩衝區與「嗨」,看看我是否應該結束傳輸(服務器端) – Warsame 2010-06-26 18:52:49

+0

這意味着,這可能不是最好的方式來表示結束的數據傳輸。需要思考的問題:如果您發送的數據實際上最後有「嗨」,會怎麼樣? – Alan 2010-06-26 19:00:17

4

首先,你應該嘗試增加你的緩衝區到更大的東西。緩衝區越大,傳輸效率越高(只是不要誇大並消耗太多本地內存)。其次,你應該檢查使用讀寫函數返回的長度。在所有讀寫操作中,只檢查是否讀取/寫入內容,但在下一次寫入/讀取操作時應使用此字節數。例如,如果客戶端報告已讀取1個字節,則應該只寫入1個字節到磁盤。此外,如果從服務器讀取1024個字節,則應該嘗試將1024個字節寫入磁盤,這可能不會在該調用中發生,並且您可能需要再次調用寫入以完成操作。

我知道這聽起來像很多工作,但這是如何做必須保證I/O操作。所有的讀寫操作基本上都要在自己的循環中完成。

2

你確定知道你的二進制圖像文件中沒有字節序列0x48 0x69"Hi")嗎?只要收到該序列,您的讀取循環就會終止。此外,您可以使用字符緩衝區(長度爲兩個字節)呼叫strcmp(),幾乎可以肯定地確保它沒有空終止字節('\0')。

您可能還會研究這些文件的不同之處? cmp工具可以爲您提供源和目標之間不同的字節列表。

爲了正確,您絕對要檢查read(),write(),send()等的返回結果。使用套接字進行短讀寫的可能性非常高,因此,代碼能夠處理並非所有數據都被傳輸的情況至關重要。由於您不跟蹤recv()返回的字節數,因此您可能只收到一個字節,但在跟隨它的write()調用中寫了兩個字節。

爲了提高性能,較大的緩衝區將有助於減少系統調用移動數據的開銷,但如果帶寬和延遲不是主要問題,則可以採用較小的操作。直到你有正確的行爲,他們才能成立。

最後,爲了與較不開明的操作系統兼容,您應該使用"rb""wb"以二進制模式打開文件,以便在寫入過程中換行符不會被損壞。

0

問題是,您打開文本模式的文件與fopen(..., "r")fopen(..., "w")。您需要使用非文本文件,二進制模式("rb""wb")。

+2

儘管與Windows和古老的Mac OS兼容很重要,但在Unix衍生的平臺(如OS X或Linux)上並不重要(正如海報所說的那樣)。 – Hudson 2010-06-26 19:48:50

0

其他人指出,與您的代碼,即問題:

  • 沒有使用的read(2)write(2)調用的返回值,
  • 混合二進制數據和字符控制數據,
  • 使用strcmp(3)對字符串可能不是零終止(讓我注意,使用依賴於從網絡接收到的數據結束零功能一般是不是一個好主意,並經常導致緩衝區溢出。)

你會好得多定義文件傳輸的簡單協議讓服務器端知道前期有多少你的數據發送(讀​​3210,如果你想知道爲什麼。) - 先用含固定大小的頭轉移文件長度(也可能包括文件名,但那麼你就需要轉移該名稱的長度了。)注意號碼endianness - 總是network byte order發送號碼。

既然你是在Linux上,讓我也指向sendfile(2),這是一種非常有效的發送文件的方式,因爲它避免了將數據複製到userland或從userland複製數據。