2009-12-03 37 views
3

我已經設置了一個簡單的TCP文件傳輸。除了收到的文件大小比發送的文件更小以外,一切似乎都正常工作。接收文件的大小似乎沒有任何模式。TCP連接似乎接收不完整的數據

(在下面的代碼,注意典型的客戶機/服務器輥相反) 我的客戶端代碼是這樣的:

#define kMaxBacklog (5) 
// fill out the sockadd_in for the server 
struct sockaddr_in servAdddress; 
//memcpy() to fill in the sockaddr 

//setup the socket 
int sockd, returnStatus;  
sockd = socket(AF_INET, SOCK_STREAM, 0); 
if (sockd == -1) 
    NSLog(@"could not create client socket"); 
else 
    NSLog(@"created client socket"); 

returnStatus = connect(sockd, (struct sockaddr*)&servAdddress, sizeof(servAdddress)); 
if (returnStatus == -1) 
    NSLog(@"could not connect to server - errno:%i", errno); 
else 
    NSLog(@"connected to server"); 

NSData *dataWithHeader = [self getDataToSend]; 
returnStatus = send(sockd, [dataWithHeader bytes], [dataWithHeader length], 0); 
if (returnStatus == -1) 
    NSLog(@"could not send file to server"); 
else if(returnStatus < [dataWithHeader length]) 
    NSLog(@"ONLY PARTIAL FILE SENT"); 
else 
    NSLog(@"file sent of size: %i", returnStatus); 

shutdown(sockd, SHUT_WR); 
close(sockd); 

客戶端方法總是報告它發送整個文件。

對於服務器:

#define MAXBUF (10000) 
int _socket; 
_socket = socket(AF_INET, SOCK_STREAM, 0); // set up the socket 

struct sockaddr_in addr; 
bzero(&addr, sizeof(addr)); 
addr.sin_len = sizeof(addr); 
addr.sin_family = AF_INET; 
addr.sin_addr.s_addr = INADDR_ANY; 
addr.sin_port = htons(0); 

int retval = bind(_socket, (struct sockaddr *)&addr, sizeof(addr)); 
    if (retval == -1) 
     NSLog(@"server could not bind to socket"); 
    else 
     NSLog(@"server socket bound"); 

socklen_t len = sizeof(addr); 
retval = getsockname(_socket, (struct sockaddr *)&addr, &len); 
    if (retval == -1) 
     NSLog(@"server could not get sock name"); 
    else 
     NSLog(@"server socket name got"); 

    int socket1, socket2, clientAddrLen, returnStatus; 
    struct sockaddr_in servAdddress, clientAddress; 
    clientAddrLen = sizeof(servAdddress); 

    socket1 = _socket; 

    returnStatus = listen(socket1, kMaxBacklog); 
    if (returnStatus == -1) 
     NSLog(@"server could not listen on socket"); 
    else 
     NSLog(@"server socket listening"); 

while(1){ 
    FILE *fd; 
    int i, readCounter; 
    char file[MAXBUF]; 

    NSLog(@"server blocking on accept()"); 
    socket2 = accept(socket1, (struct sockaddr*)&clientAddress, (socklen_t*)&clientAddrLen); 
    if (socket2 == -1) 
     NSLog(@"server could not accpet the connection"); 
    else 
     NSLog(@"server connection accepted"); 

    i = 0; 
    readCounter = recv(socket2, file, MAXBUF, 0); 
    if(!readCounter) 
     NSLog(@"server connection cancelled, readCount = 0"); 

     else if (readCounter == -1){ 
     NSLog(@"server could not read filename from socket"); 
     close(socket2); 
     continue; 
    } 
    else 
     NSLog(@"server reading file of size: %i", readCounter); 


    fd = fopen([myfilePathObject cStringUsingEncoding:NSASCIIStringEncoding], "wb"); 

    if(!fd){ 
     NSLog(@"server could not open the file for creating"); 
     close(socket2); 
     continue; 
    } 
    else 
     NSLog(@"server file open for creating"); 

    returnStatus = fwrite([myData bytes], 1, [myData length], fd); 
    if (returnStatus == -1) 
     NSLog(@"Error writing data to server side file: %i", errno); 
    else 
     NSLog(@"file written to disk); 

    readCounter = 0; 
    //close (fd); 
    returnStatus = fclose(fd); 
    if(returnStatus) 
     NSLog(@"server error closing file"); 

所以零星的readCounter變量將不包含大小爲所發送的文件,但有些時候也一樣。

如果重要的是在iPhone和iPhone模擬器之間進行文件傳輸,無論是通過WIFI。無論手機是服務器還是仿真器是服務器,都會發生這種情況。

如果有人能幫助我理解爲什麼會發生這種情況,我會很感激。我認爲TCP的全部目的是爲了避免這種問題。

(給予信貸,這是因爲,對於我的服務器和我沉重借用從書客戶端代碼:權威指南Linux網絡編程,戴維斯,特納和Yocom從Apress出版)

+3

我相信這本書一定處理部分正確讀取! – janm 2009-12-03 03:33:29

回答

11

recv函數只能接收1個字節,您可能需要多次調用才能獲得整個有效負載。正因爲如此,您需要知道您期望的數據量。雖然你可以通過關閉連接完成信號,但這不是一個好主意。

更新:

我還要提到的是,send函數具有相同的約定recv:你要調用它在一個循環,因爲你不能假定它會發送所有的數據。雖然它可能始終在您的開發環境中工作,但這種假設會在稍後引起您的注意。

0

recv立即返回緩衝區中的任何內容(直到MAXBUF)。如果緩衝區是在同一時間被寫入到你可能無法得到所有數據

1

你或許應該有某種字符序列的信號文件傳輸的終止,只有當你看到了那些在結束一個塊你打破你的recv循環。

當然,你將不得不找到一個不會出現在你的文件中的序列,或者可以很容易地逃脫。如果你使用的是文本文件,這很容易,但如果不是,你就必須很聰明。

另外,客戶端可以先發送的文件大小(在一個單獨的發送調用),這樣服務器就知道有多少字節的文件傳輸的期望。

+0

這真的有必要嗎?看起來,while循環使得工作更加乾淨。 – SooDesuNe 2009-12-03 05:30:14

+0

那麼,如果客戶端機器在傳輸過程中出現故障,服務器將無法知道文件傳輸不完整。這是確保服務器確切知道何時發生成功傳輸的一種方式。否則,或者文件大小比它應該小,或者尚未發現標記控制序列尚未發出傳輸結束的信號 - 但只是一個簡單的while循環,服務器將不會識別出中斷客戶端的端口被關閉「違反其意願」......不是? – 2009-12-03 05:38:27

+0

@Platinum Azure領先的大小值比特殊的控制序列更簡單,更高效。它可以讓您適當地分配緩衝區,預先分配文件以減少碎片等,並消除與轉義和搜索/匹配序列有關的任何複雜性。 – 2009-12-03 17:30:46

0

TCP確保的是,您的消息將正確地到達遠程對等體。只要它適合於發送緩衝器,它將被自動分割成較小的塊,並通過本地對等發送,重新排序和由遠程對等體重組。在發送消息時動態更改路由的情況並不少見,在將消息發送到應用程序之前,您必須手動重新排序(較小的區塊)。

至於您的實際數據傳輸,您的應用程序需要就自定義協議達成一致。例如,如果您只發送一條消息(文件),則發送方可以向接收方發送它不打算再寫入套接字的信號(使用shutdown(sock, SHUT_WR)),這樣recv()將返回0,並且您知道傳輸已完成(這是HTTP/1.0服務器如何向客戶端傳輸完成的信號)。如果你打算髮送更多的數據,那麼這個選擇是不合適的。

另一種方法是讓接收者通過包含一個頭來知道發送者要發送多少數據。它不需要過於詳細,您可以簡單地保留前8個字節以將該長度作爲64位無符號整數發送。在這種情況下,你仍然需要小心字節順序(big-endian/little-endian)。

有網絡編程一個非常有用的教程UNIX環境:

Beej's Guide to Network Programming

你可以參考它來獲得快速啓動,然後參照回書的完整性,如果你需要。即使您沒有要求提供其他參考文獻,TCP/IP Illustrated Vol. 1UNIX Network Programming Vol. 1(均由W. Richard Stevens編寫,後者在最近的第三版中)都是很好的參考文獻。

+0

我已經多次閱讀Beej的指南(有時需要我幾次),我同意這是一個很好的參考。 需要注意的是,Beej提到的一些功能,比如gethostbyname()在iPhone上不可用(或者說,它們不包含在頭文件中) – SooDesuNe 2009-12-03 04:29:57

+0

對,我假設你也需要一些iPhone在這種情況下是特定的,這樣你就可以使用等價的呼叫(當然,如果你需要它們的話)。 – rnsanchez 2009-12-03 05:45:46

3

Tim Sylvester和gnibbler都有很好的答案,但我認爲最清晰和最完整的是兩者的結合。

recv()函數立即返回任何緩衝區中的內容。這將在1個字節和MAXBUF之間。如果在recv返回時正在寫入緩衝區,則不會獲得緩衝區中已發送的整個數據。

因此,您需要多次調用recv(),並連接數據才能獲取發送的所有內容。

一個方便的方式做到這一點(因爲我們在可可的工作)是使用NSMutableData,如:

NSMutableData *fileData = [[NSMutableData alloc] init]; //Don't forget to release 
while ((readCounter = recv(socket2, file, MAXBUF, 0)) > 0){  
    if (readCounter == -1){ 
     NSLog(@"server could not read filename from socket"); 
     close(socket2); 
     continue; 
    } 
    else{ 
     NSLog(@"server reading file of size: %i", readCounter); 
     [fileData appendData:[NSData dataWithBytes:file length:readCounter]]; 
    } 
    bzero(file, MAXBUF); 
    readCounter = 0; 
}