2009-12-03 110 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; 
}