使用C语言中的UDP套接字传输文件

时间:2013-04-29 03:04:40

标签: c linux sockets udp file-transfer

我对C语言中的套接字编程相当新,所以下面的代码可能会有很多新手错误。 我正在尝试创建一个客户端 - 服务器应用程序,其中服务器将使用UDP套接字将文件传输到客户端。客户端和服务器都将在Linux主机上运行。这是一项任务,因此必须以这种方式完成。其他客户端 - 服务器通信可能使用TCP套接字,但文件传输必须通过UDP。该程序适用于小文件,但如果我尝试发送稍大的文件(例如,600 kb文本文件),客户端将停止接收数据包,即使服务器将全部发送。这是服务器代码的文件传输部分:

   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”缓冲区最初被声明为char bufferRW [256]并作为参数传递给函数。其他未声明的变量也是如此。 就像我之前说的那样,服务器(显然)会发送整个文件而没有任何问题。但是,客户端在写入大约423936字节后将停止接收数据包(这可能因执行而异)。它只会留在recvfrom线上,而不会读任何东西。

现在,我确定问题不是由连接错误引起的,因为我正在测试同一主机上的两个进程。在您询问“256字节数据包大小是什么?”之前,如果我使用的缓冲区大小为1500,那么这个奇怪的错误会在realFileSize = ntohl(fileSize);客户端行上引发分段错误。< / p>

你能告诉我在这里缺少什么吗?

编辑:我正在尝试使用不同的文件大小。它似乎处理大于256字节的文件没有问题(它在客户端和服务器上正确地进入和退出while循环),但是当文件大于300 kb时,客户端将开始出现问题。

编辑2:我刚刚调试了程序。显然,服务器在客户端甚至可以进入while循环之前发送整个文件。

编辑3:我想我知道是什么导致了这个问题。似乎服务器在客户端开始读取之前发送了一堆数据包,客户端将读取多达278个数据包,无论其大小如何。如果我在客户端开始读取之前尝试发送,比如279,则不会读取第279个数据包。因此,如果服务器足够快地发送其数据包,则客户端尚未读取的数据包数量将超过278,客户端将无法读取所有数据包。关于如何解决这个问题的任何想法?

1 个答案:

答案 0 :(得分:1)

  1. long* fileSize声明了一个指向long的指针,但是在你的代码中,它指向了无处。实际上,它指向一个随机地址。您应将其声明为long fileSize,然后再拨打read(socketTCP, &fileSize, sizeof(long))
  2. 您应该检查readwrite等的返回值,以确保它们没有失败。例如,sendto出错时返回-1。您忽略了这一点,并且无论如何都要使用此值递增sizeCheck
  3. UDP不是文件传输的可靠协议,但如果你不能没有它,你最好实现TCP已经免费提供的一些控件,如数据包重新排序,数据校验和等。这可能是一个非常复杂的任务本身。
  4. 使用-Wall -Wextra编译代码。编译器将为您提供有关可能出错的提示。我发现你在比较中仍在使用*fileSize,这显然是错误的。
  5. 解决*fileSize问题后,您的循环条件仍然使用错误的值(由于fileSize = ntohl(fileSize))。您需要将此值存储在另一个变量中,或更改您的循环条件以使用实际文件大小。
  6. 关于您的编辑3,您需要以某种方式同步您的客户端&amp;服务器,所以他们可以同时开始传输。但是,比接收器快得多的发送方仍会导致丢包。要解决此问题,您还需要实现数据包确认,如果发送方在超时后没有收到相应发送数据包的ACK,则重新传输数据包。这是TCP已经为您做的事情。 一种更简单(但不是完全可靠)的方法是稍微减慢发送过程 - 可能在每次调用nanosleep之间使用sendto