views:

247

answers:

5

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)

+8  A: 

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.

Tim Sylvester
A: 

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

gnibbler
+1  A: 

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.

Platinum Azure
Is this really necessary? It seems like the while loop get the job done a lot more cleanly.
SooDesuNe
Well, if the client machine goes down in the middle of a transfer, the server would have no way of knowing the file transfer is incomplete. This is a way to insure that the server knows exactly when a successful transfer has happened. Otherwise, either the file size is smaller than it should be or the sentinel control sequence hasn't been seen yet to signal the end of the transmission-- but with just a naive while loop, the server wouldn't recognize an interruption if the client's end of the socket is shut down "against its wishes"... no?
Platinum Azure
@Platinum Azure A leading size value is simpler and more efficient than special control sequences. It lets you allocate buffers appropriately, pre-allocate files to reduce fragmentation, etc., as well as eliminating any complexity having to do with escaping and searching/matching sequences.
Tim Sylvester
I'm aware. I just presented both options.
Platinum Azure
A: 

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.

rnsanchez
I have read Beej's guide several times (some times it takes me a few times), and I agree, it is a great reference.It should be noted, that some of the functions Beej mentions, such as gethostbyname() are not available on iPhone (or rather, they're not included in the headers)
SooDesuNe
Right, I'd assume you also need something iPhone-specific in this case, so that you can use equivalent calls (if you need them, of course).
rnsanchez
+3  A: 

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() fuction return immediatly 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;
}
SooDesuNe