TCP-соединение для получения неполных данных - PullRequest
3 голосов
/ 03 декабря 2009

Я настроил простую передачу файлов по протоколу 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)

Ответы [ 5 ]

11 голосов
/ 03 декабря 2009

Функция recv может получить всего 1 байт, возможно, вам придется вызывать ее несколько раз, чтобы получить всю полезную нагрузку. Из-за этого вам нужно знать, сколько данных вы ожидаете. Хотя вы можете сообщить о завершении, закрыв соединение, это не очень хорошая идея.

Обновление:

Следует также упомянуть, что функция send имеет те же соглашения, что и recv: вы должны вызывать ее в цикле, потому что вы не можете предполагать, что она отправит все ваши данные. Хотя это может всегда работать в вашей среде разработки, такое предположение вас укусит позже.

3 голосов
/ 03 декабря 2009

Тим Сильвестр и Гнибблер оба имеют очень хорошие ответы, но я думаю, что наиболее ясной и полной является комбинация двух.

Функция 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;
}
1 голос
/ 03 декабря 2009

Вероятно, у вас должна быть какая-то последовательность символов, сигнализирующая об окончании передачи файла, и только когда вы читаете их в конце блока, вы прерываете цикл recv.

Конечно, вам нужно будет найти последовательность, которая не будет встречаться в ваших файлах, или которую можно легко избежать. Если вы работаете с текстовыми файлами, это довольно легко, но если нет, то вам нужно быть умным.

В качестве альтернативы клиент может сначала отправить размер файла (в отдельном вызове отправки), чтобы сервер знал, сколько байтов следует ожидать при передаче файла.

0 голосов
/ 03 декабря 2009

TCP гарантирует, что ваше сообщение будет правильно доставлено удаленному узлу. Пока он помещается в буфер отправки, он будет автоматически разбиваться на более мелкие порции и отправляться локальным узлом, а также переупорядочиваться и повторно собираться удаленным узлом. Весьма распространено динамическое изменение маршрута во время отправки сообщения, которое вам нужно будет переупорядочить вручную (меньшие порции) перед доставкой в ​​ваше приложение.

Что касается фактической передачи данных, ваше приложение должно согласовать пользовательский протокол. Например, если вы отправляете только одно сообщение (файл), отправитель может сообщить получателю, что он больше не собирается записывать в сокет (с shutdown(sock, SHUT_WR)), таким образом recv() возвращается с 0, и вы знаете, передача завершена (так сервер HTTP / 1.0 сигнализирует клиенту о завершении передачи). Если вы намереваетесь отправить больше данных, этот вариант не подходит.

Другой способ - сообщить получателю, сколько данных отправитель собирается передать, например, включив заголовок. Это не должно быть слишком сложным, вы можете просто зарезервировать первые 8 байтов для отправки длины в виде 64-разрядного целого числа без знака. В этом случае вам все равно нужно быть осторожным с порядком байтов (big-endian / little-endian).

Существует очень полезное руководство по сетевому программированию для сред UNIX:

Руководство Биджа по сетевому программированию

Вы могли бы обратиться к нему, чтобы получить быстрый старт, а затем вернуться к книге для полноты, если вам нужно. Даже если вы не запрашивали дополнительных ссылок, TCP / IP Illustrated Vol. 1 и Сетевое программирование UNIX Vol. 1 (оба У. Ричард Стивенс, последний с последним третьим изданием) - отличные ссылки.

0 голосов
/ 03 декабря 2009

recv сразу возвращается с тем, что находится в буфере (до MAXBUF). Если буфер записывается в одно и то же время, вы можете не получить все данные

...