2013-04-29 1 views
0

Я довольно новичок в программировании сокетов на C, поэтому в приведенном ниже коде может быть много ошибок новичка. Я пытаюсь создать клиент-серверное приложение, в котором сервер будет передавать файл клиенту с использованием сокета UDP. И клиент, и сервер будут работать на хостах Linux. Это задание, так что это должно быть сделано именно так. Другие коммуникации клиент-сервер могут использовать сокет TCP, но передача файлов ДОЛЖНА быть через UDP. Программа работает корректно для небольших файлов, но если я попытаюсь отправить немного больший файл (скажем, текстовый файл размером 600 килобайт), клиент перестанет получать пакеты, даже если сервер отправит их все. Вот передача файла часть кода сервера:Перенос файлов с использованием сокетов UDP в C

FILE* myFile; 
    long fileSize, readBytes, sentBytes, sizeCheck; 
    uint32_t encodedFileSize; 

    myFile = fopen(fileName, "rb"); 
    if(myFile == NULL) 
    { 
     perror("Error when opening file."); 
     exit(1); 
    } 

    fseek(myFile, 0, SEEK_END); 
    fileSize = ftell(myFile); 
    encodedFileSize = htonl(fileSize); 
    rewind(myFile); 
    sizeCheck = 0; 

    write(myTCPSocket, &encodedFileSize, sizeof(encodedFileSize)); 

    if(fileSize > 255) 
    { 
     while(sizeCheck < fileSize) 
     { 
     readBytes = fread(bufferRW, 1, 256, myFile); 
     sentBytes = sendto(sockfdUDP, bufferRW, readBytes, 0, (struct sockaddr*)&cli_addr, udpAddressSize); 
     sizeCheck += sentBytes; 
     } 
    } 
    else 
    { 
     readBytes = fread(bufferRW, 1, 256, myFile); 
     sentBytes = sendto(sockfdUDP, bufferRW, readBytes, 0, (struct sockaddr*)&cli_addr, udpAddressSize); 
    } 

    if(fileSize == sizeCheck) 
    { 
     printf("Success.\n"); 
    } 
    else 
    { 
     printf("Fail.\n"); 
    } 
    fclose(myFile); 
    fflush(stdout); 
    close(sockfdUDP); 

Как вы можете видеть, я использовал сокет TCP для отправки клиента размера файла. Вот код клиента:

FILE *myFile; 
    long receivedBytes, writtenBytes, sizeCheck; 
    long fileSize, realFileSize; 
    char ack2[5] = "Ok"; 
    sockfdUDP = socket(AF_INET, SOCK_DGRAM, 0); 

    read(socketTCP, &fileSize, sizeof(long)); 

    realFileSize = ntohl(fileSize); 

    myFile = fopen(fileName, "wb"); 

    if(myFile == NULL) 
    { 
    perror("Error when creating file."); 
    exit(1); 
    } 

    sizeCheck = 0; 

    if((realFileSize) > 255) 
    { 
     while(sizeCheck < (realFileSize)) 
     { 
     receivedBytes = recvfrom(sockfdUDP, bufferRW, 256, 0, (struct sockaddr*)&serv_addr, &serv_addr_size); 
     writtenBytes = fwrite(bufferRW, 1, receivedBytes, myFile); 
     fflush(myFile); 
     sizeCheck += writtenBytes; 
     } 
    } 
    else 
    { 
     receivedBytes = recvfrom(sockfdUDP, bufferRW, 256, 0, (struct sockaddr*)&serv_addr, &serv_addr_size); 
     fwrite(bufferRW, 1, receivedBytes, myFile); 
     fflush(myFile); 
    } 

    if(realFileSize == sizeCheck) 
    { 
    printf("Success."); 
    } 
    else 
    { 
     printf("Fail."); 
    } 
    fclose(myFile); 
    close(sockfdUDP); 

«bufferRW» буфер был первоначально объявлен как полукокс bufferRW [256] и передаются функции в качестве аргумента. То же самое касается других необъявленных переменных. Как я уже говорил, сервер (по-видимому) отправит весь файл без каких-либо проблем. Тем не менее, клиент перестанет получать пакеты после того, как будет написано около 423936 байт (это может варьироваться в зависимости от исполнения). Он просто останется на линии recvfrom, не прочитав ничего.

Теперь я уверен, что проблема не вызвана неправильным подключением, так как я тестирую оба процесса на одном хосте. И прежде чем вы спросите: «Что это такое с размером пакета в 256 байтов?», Есть такая странная ошибка, которая вызовет у меня ошибку сегментации на клиентской линии realFileSize = ntohl(fileSize);, если я использую размер буфера, например, 1500.

Не удалось скажите, пожалуйста, что мне здесь не хватает?

EDIT: Я пытаюсь с разными размерами файлов сейчас. Кажется, что он обрабатывает файлы размером более 256 байт без проблем (он входит и выходит из цикла while правильно и на клиенте, и на сервере), но у клиента появятся проблемы, когда файл больше, чем, скажем, 300 кб.

EDIT 2: Я только что отладил программу. По-видимому, сервер отправляет весь файл до того, как клиент может даже ввести свой цикл while.

EDIT 3: Я думаю, что знаю, что вызывает проблему. Похоже, если сервер отправляет кучу пакетов до того, как клиент начнет чтение, клиент будет считывать до 278 пакетов независимо от их размера. Если я попробую отправить, скажем, 279, прежде чем клиент начнет читать, он не прочитает 279-й пакет. Поэтому, если сервер достаточно быстро отправляет свои пакеты, количество пакетов, которые клиент еще не прочитал, превысит 278, и клиент не завершит чтение всех пакетов. Есть какие нибудь идеи как это починить?

+2

Надеюсь, вы поймете, что если вы используете UDP, вам придется иметь дело с падением пакетов и переупорядочением самостоятельно, чего вы сейчас не делаете. – icktoofay

ответ

1
  1. long* fileSize объявил указатель на длинный, но в вашем коде он нигде не указывает. Фактически, это указывает на случайный адрес. Вы должны объявить его как long fileSize и вместо этого позвонить read(socketTCP, &fileSize, sizeof(long)).
  2. Вы должны проверить возвращаемое значение read, write и т. Д., Чтобы гарантировать, что они не подведут. Например, sendto возвращает -1 при ошибке. Вы игнорируете это и увеличиваете sizeCheck с этим значением в любом случае.
  3. UDP не является надежным протоколом для передачи файлов, но если вы не можете обойтись без него, вам лучше реализовать некоторые элементы управления, которые TCP уже предоставляет вам бесплатно, например переупорядочение пакетов, контрольную сумму данных и т. Д. И это может быть очень сложным задача сама по себе.
  4. Скомпилируйте свой код с помощью -Wall -Wextra.Компилятор даст вам подсказки о возможных ошибках. Я вижу, что вы все еще используете *fileSize в сравнении, что явно неверно.
  5. После устранения проблемы *fileSize ваше условие цикла по-прежнему использует неправильное значение (из-за fileSize = ntohl(fileSize)). Вам нужно сохранить это значение в другой переменной или изменить условие цикла для использования реального размера файла.

Что касается вашего EDIT 3, вам нужно как-то синхронизировать сервер клиента &, так что они могут начать передачу одновременно. Однако отправитель, который намного быстрее, чем приемник, по-прежнему будет вызывать потерю пакетов. Чтобы решить эту проблему, вам также необходимо реализовать подтверждение пакета и повторно передать пакет, если отправитель не получит ACK для соответствующего отправленного пакета после таймаута. Это то, что TCP уже делает для вас. Простейший (но не полностью надежный) способ будет немного замедлить процесс отправки - возможно, используя nanosleep между каждым вызовом до sendto.

+0

1. Готово. Все еще не работает. 2. Готово, очевидно, и fread, и fwrite работают нормально. Sendto не вызывает никаких ошибок. 3. Интересно, что когда я тестировал программу с меньшими файлами (но больше 256 байтов), она работала отлично, если бы я не делал ничего из этого. Я не думаю, что это проблема здесь, потому что клиент даже не получает все пакеты. Тем не менее, те, что он действительно получает, прекрасно упорядочены. – Phillipe

+0

Не рассчитывайте на это ... – japreiss

+0

В локальной сети вы получите правильный заказ, и вы, вероятно, не потеряете дейтаграммы UDP. Если вам нужно пройти через облако, вам нужно будет беспокоиться о порядке и ненадежной доставке. – Fred