TCP连接似乎接收不完整的数据

时间:2009-12-03 03:28:57

标签: unix networking sockets 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的全部目的是避免这种问题。

(为了给予应有的信用,我从服务器和客户端代码中大量借用了本书:来自Apress的Davis,Turner和Yocom的Linux网络编程权威指南)

5 个答案:

答案 0 :(得分:11)

recv函数只能接收1个字节,您可能需要多次调用才能获得整个有效负载。因此,您需要知道您期望的数据量。虽然你可以通过关闭连接来 信号完成,但这并不是一个好主意。

更新

我还应该提到send函数与recv具有相同的约定:你必须在循环中调用它,因为你不能假设它会发送你的所有数据。虽然它可能总是在您的开发环境中起作用,但这种假设会在以后咬你。

答案 1 :(得分:3)

Tim Sylvester和gnibbler都有很好的答案,但我认为最明确和最完整的是两者的结合。

recv()函数立即返回缓冲区中的任何内容。这将介于1个字节和MAXBUF之间。如果在recv返回时写入缓冲区,则您将无法获得缓冲区中发送的所有数据。

因此,您需要多次调用recv(),并连接数据,以获取已发送的所有内容。

一种方便的方法(因为我们在cocoa中工作)是使用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;
}

答案 2 :(得分:1)

你应该有一些字符序列来表示文件传输的终止,并且只有当你在块的末尾读取它们时才会中断你的recv循环。

当然,您必须找到一个不会在文件中出现的序列,或者可以轻松转义的序列。如果您正在处理文本文件,这很容易,但如果不是,您必须要聪明。

或者,客户端可以首先发送文件大小(在单独的发送调用中),因此服务器知道文件传输中需要多少字节。

答案 3 :(得分:0)

recv立即返回缓冲区中的任何内容(最大MAXBUF)。如果同时写入缓冲区,则可能无法获取所有数据

答案 4 :(得分: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,后者最近的第三版)都是很好的参考资料。