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

发布于 2024-08-13 17:21:10 字数 3854 浏览 3 评论 0原文

我已经设置了一个简单的 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 网络编程权威指南》一书)

I've setup a simple TCP file transfer. Everything appears to work OK, except for the received file size is sporadically a smaller size than the file that was sent. There doesn't appear to be any pattern to the size of the received file.

(in the code below, note that the typical client/server rolls are reversed)
My client code is like:

#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);

The client method ALWAYS reports that it sent the entire file.

For the server:

#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");

So sporadically, the readCounter variable will not contain the same size as the file that was sent, but some times it does.

If it matters the file transfer is occurring between an iPhone and an iPhone simulator, both over WIFI. This happens regardless of if the phone is the server or if the simulator is the server.

If anyone can help me understand why this is occurring I'd appreciate it. I thought the whole purpose of TCP was to avoid this kind of problem.

(to give credit where it's due, for my server and client code I borrowed heavily from the book: The Definitive Guide to Linux Network Programming, by Davis, Turner and Yocom from Apress)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

月竹挽风 2024-08-20 17:21:10

recv 函数只能接收 1 个字节,您可能需要多次调用它才能获取整个有效负载。因此,您需要知道您期望有多少数据。尽管您可以通过关闭连接来表示完成,但这并不是一个好主意。

更新:

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

The recv function can receive as little as 1 byte, you may have to call it multiple times to get your entire payload. Because of this, you need to know how much data you're expecting. Although you can signal completion by closing the connection, that's not really a good idea.

Update:

I should also mention that the send function has the same conventions as recv: you have to call it in a loop because you cannot assume that it will send all your data. While it might always work in your development environment, that's the kind of assumption that will bite you later.

情绪失控 2024-08-20 17:21:10

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;
}

Tim Sylvester and gnibbler both have very good answers, but I think the most clear and complete is a combination of the two.

The recv() function return immediately with whatever is the in the buffer. This will be somewhere between 1 byte and MAXBUF. If the buffer is being written to while recv returns, you wont have the entire data that was sent in the buffer yet.

So you need to call recv() multiple times, and concatenate the data, to get everything that was sent.

A convenient way to do this (since we are working in cocoa) is to use NSMutableData like:

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;
}
夜唯美灬不弃 2024-08-20 17:21:10

您可能应该有某种字符序列来表示文件传输的终止,并且只有当您读取块末尾的这些字符时,您才会跳出recv循环。

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

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

You should probably have some kind of sequence of characters to signal termination of the file transfer, and only when you read those at the end of a block do you break out of your recv loop.

Of course, you will have to find a sequence that won't occur in your files, or that can be easily escaped. If you're working with text files this is pretty easy, but if not you'll have to be clever.

Alternatively, the client could first send the file size (in a separate send call), so the server knows how many bytes to expect in the file transfer.

古镇旧梦 2024-08-20 17:21:10

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

recv returns right away with whatever is in the buffer (upto MAXBUF). If the buffer is being written to at the same time you might not get all the data

梦途 2024-08-20 17:21:10

TCP 确保您的消息能够正确到达远程对等点。只要它适合发送缓冲区,它就会自动分割成更小的块并由本地对等方发送,并由远程对等方重新排序和重组。在发送消息时,路由动态更改的情况并不罕见,在发送到应用程序之前,您必须手动重新排序消息(较小的块)。

至于您的实际数据传输,您的应用程序需要同意自定义协议。例如,如果您只发送一条消息(文件),则发送方可以向接收方发出信号,表示它不再打算向套接字写入内容(使用 shutdown(sock, SHUT_WR)),这recv() 返回 0 并且您知道传输已完成(这是 HTTP/1.0 服务器向客户端发出传输已完成信号的方式)。如果您打算发送更多数据,那么此替代方案不合适。

另一种方法是让接收方知道发送方将通过包含标头传输多少数据。不需要过于复杂,您只需保留前 8 个字节即可将长度作为 64 位无符号整数发送。在这种情况下,您仍然需要注意字节顺序(大端/小端)。

有一个关于 UNIX 环境网络编程的非常有用的教程:

Beej 网络编程指南

您可以参考它来快速入门,然后再参考如果需要,请预订以确保完整性。即使您没有要求其他参考资料,TCP/IP Illustration Vol. 1UNIX 网络编程卷。 1(均由 W. Richard Stevens 撰写,后者最近出版了第三版)是极好的参考资料。

What TCP ensures is that your message will get to the remote peer correctly. As long as it fits in the send buffer, it will be automatically split into smaller chunks and sent by the local peer, and reordered and reassembled by the remote peer. It is not uncommon for a route to dynamically change while you are sending a message, which you would have to reorder manually (the smaller chunks) before delivering to your application.

As for your actual data transfer, your application needs to agree on a custom protocol. For instance, if you are only sending one message (the file), the sender could signal the receiver that it does not intend to write anymore to the socket (with shutdown(sock, SHUT_WR)), this way recv() returns with 0 and you know the transfer is complete (this is how a HTTP/1.0 server signals the client the transfer is complete). If you intend to send more data, then this alternative is not appropriate.

Another way would be to let the receiver know how much data the sender is going to transmit by including a header, for instance. It does not need to be overly elaborate, you could simply reserve the first 8 bytes to send the length as a 64-bit unsigned integer. In this case, you still need to be careful about byte ordering (big-endian / little-endian).

There is a very useful tutorial on network programming for UNIX environments:

Beej's Guide to Network Programming

You could refer to it to get a quick start, then refer back to the book for completeness, if you need. Even though you did not ask for additional references, TCP/IP Illustrated Vol. 1 and UNIX Network Programming Vol. 1 (both by W. Richard Stevens, the latter with a recent third edition) are excellent references.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文