【问题标题】:Stuck in while when transfer file through socket using TCP使用 TCP 通过套接字传输文件时卡住
【发布时间】:2018-01-21 09:11:55
【问题描述】:

我编写了程序,它运行良好,但我想使用 sendfile() 重写它,现在我陷入了一个循环。

服务器端:

  • 发送姓名 = ok
  • 发送 md5 校验和 = ok
  • 发送大小 = ok
  • 发送文件 = ko

客户端:

  • recv 名称 = 好的
  • recv md5 cecksum = ok
  • recv 大小 = ok
  • 创建目录并创建文件 = ok
  • 将数据写入创建的文件 = ko

P.S 在以前版本的程序中,我坚持了一段时间,但这取决于我使用 printf 的程度,为什么?例如,我添加一行 printf 程序卡住,删除它,工作正常。

UPDT:重写代码客户端/服务器

客户

/* Received file name */
  int rc_byte = 0;
  rc_byte = recv(fd, rx_tx_file->out_name, sizeof(rx_tx_file->out_name),0);
  if (rc_byte < 0){
    perror("Failed to receive file name: ");
    exit(-1);
  } else
    printf("Recv out name %s\n", rx_tx_file->out_name);
  //printf("file name rc %s\n", rx_tx_file->out_name);                                                                           
  trimm_path_name(rx_tx_file);


  /* Received md5sum */
  rc_byte = recv(fd, rx_tx_file->md5sum, sizeof(rx_tx_file->md5sum), 0);
  if (rc_byte < 0) {
    perror("Failed to receive check sum: ");
    exit(-1);
  } else
    printf("recv md5s %s\n", rx_tx_file->md5sum);


  /* Received file size */
  rc_byte = recv(fd, &size, sizeof(size), 0);
  if(rc_byte < 0) {
    perror("Recevid size of file: ");
     exit(-1);
  }
  printf("%d recv size\n", size);
  to_read = size;

  if (stat(dir, &st) == -1){
    mkdir(dir, 0777);
  }

send_data:(添加函数到服务器)

void send_data(int client_fd, m_file *rx_tx_file, int option, int size) {

  int send_byte = 0;
  int total_send = 0;
  if (option == SEND_NAME) {
    while (total_send < strlen(rx_tx_file->in_name)) {
      send_byte = send(client_fd, rx_tx_file->in_name, sizeof(rx_tx_file->in_name),0);
      if(send_byte == -1) {
        perror("Failed to send file name to client: ");
        exit(SEND_TO_CLIENT_ERROR);
      }
      total_send += send_byte;
    }
  }
  else if (option == SEND_MD5) {
    total_send = 0;
    send_byte = 0;

    while (total_send < strlen(rx_tx_file->md5sum)) {
      send_byte = send(client_fd, rx_tx_file->md5sum, sizeof(rx_tx_file->md5sum),0);
      if(send_byte == -1){
        perror("Failed to send file md5sum to client: ");
        exit(-1);
      }
      total_send += send_byte;
    }
  }
  else if (option == SEND_SIZE) {
    send_byte = send(client_fd, &size, sizeof(size),0);
    if (send_byte == -1) {
      perror("Failed to send size: ");
    }
  }
}

服务器:

client_fd = accept(server_fd, (struct sockaddr*) &client_addr, &length)
    /*send name of file*/
    send_data(client_fd, rx_tx_file, SEND_NAME, 0);
    /*send md5 sum*/
    take_check_sum(rx_tx_file,rx_tx_file->file_in, 0);
    send_data(client_fd, rx_tx_file, SEND_MD5, 0);
    /*send size of file*/
    size = stats.st_size;
    send_data(client_fd, rx_tx_file, SEND_SIZE, size);

    remain_data = stats.st_size;
    printf("File [%s] ready to send\ncheck sum [%s]\n", rx_tx_file->in_name,rx_tx_file->md5sum);
    while (((send_byte = sendfile(client_fd, file_fd, &offset, size)) > 0) && (remain_data > 0))
      {
        remain_data -= send_byte;
        printf("remain %d", remain_data);
      }
    printf("Succesfully");

由于我使用一个客户端并传递应该通过命令行参数在服务器端发送的文件,我不需要等待 (client_fd = accpet) 我只使用一个连接并关闭服务器。现在它的工作很好。但是一个问题是开放的,我应该如何重写客户端以循环接收数据。我不知道我应该接收哪个大小,因此我无法将正确的条件写入我的 while 循环。感谢大家的帮助。

【问题讨论】:

  • 这里buffer = malloc(size); while (((len = recv(fd, buffer, sizeof(buffer), ... 操作员sizeof 不符合您的预期。它返回指针的大小(4 或 8)。它返回size
  • 您还希望客户端在rev() 也返回0 时立即中止操作。正如这告诉客户端,服务器关闭了连接。客户端将无法再通过当前套接字描述符接收任何内容。
  • 为了传输文件名,代码发送strlen(rx_tx_file-&gt;in_name)+1字节并接收sizeof(rx_tx_file-&gt;out_name)字节,这很可能是不同的,然后是发送的数字。
  • 我更新了一些问题,关于它应该如何在一段时间内从服务器接收数据,它似乎如果我开始在客户端循环接收数据,发生类似不匹配的事情,在服务器一个循环中更快比另一个,在客户端我在同一个循环中启动文件的recv名称和md5总和,当我传递到下一个时,所有日期都是前一个循环中的recv并且它卡住了。
  • 关于如何通过 TCP 套接字接收/读取 n 字节请查看我在类似问题上给出的答案:stackoverflow.com/a/20149925/694576

标签: c sockets tcp tcp-ip sendfile


【解决方案1】:

TCP 是一个。它没有消息边界。因此,您的代码将无法正常工作。

首先,您发送文件名:

send(client_fd, rx_tx_file->in_name, strlen(rx_tx_file->in_name)+1,0)

然后您立即发送 md5 总和,然后发送文件大小:

send(client_fd, rx_tx_file->md5sum, strlen(rx_tx_file->md5sum)+1, 0)

send(client_fd, &size, sizeof(int),0)

由于前两个字符串没有固定的字节数,很可能当您尝试从服务器读取文件大小或 md5 总和时,您还读取了文件的大小,甚至可能是其中的一些文件数据。

首先,停止尝试将尽可能多的发送和读取代码放入ifwhile 语句的条件子句中。

具体是做什么的

if (send(client_fd, rx_tx_file->md5sum, strlen(rx_tx_file->md5sum)+1, 0) == -1) {
  perror("Failed to send file md5sum to client: ");
  exit(-1);
}

战胜你

ssize_t bytes_sent = send(client_fd, rx_tx_file->md5sum, strlen(rx_tx_file->md5sum)+1, 0);
if ( bytes_sent < 0 )
{
  perror("Failed to send file md5sum to client: ");
  exit(-1);
}

将所有这些代码放入if 子句中不会在发送时为您带来任何好处。如果strlen(rx_tx_file-&gt;md5sum)+187 并且send() 调用返回15 怎么办?这是您的代码无法处理的可能返回值,因为它会将所有内容都塞入 if 子句中。

ssize_t bytes_sent = send(client_fd, rx_tx_file->md5sum, strlen(rx_tx_file->md5sum)+1, 0);
if ( bytes_sent < 0 )
{
  perror("Failed to send file md5sum to client: ");
  exit(-1);
}
else if ( bytes_sent < strlen(rx_tx_file->md5sum)+1 )
{
    // partial send...
}

实际上最好将其编码为循环。

您没有发布您的接收代码,但如果它采用相同的样式,您不仅不会获得任何东西,通过将所有内容放入 if 子句中,您再次无法进行任何体面的错误检测或更正。

如果你的文件名recv代码类似于

char filename[1024];
if (recv(fd, &filename, sizeof(filename), 0) < 0) {
   perror("Failed to read file name: ");
   exit(-1);
}

你无法说出你刚刚收到了什么。你刚刚收到多少字节?您可能已收到文件名。您可能只收到了文件名的部分。您可能已经收到文件名、md5 总和以及一些文件内容本身。

您不知道自己收到了什么,而且您的代码无法分辨。如果您将文件名和 md5 接收缓冲区清零,并且仅将 recv 比缓冲区大小小一个字节,您至少可以避免未定义的行为。但是,如果您不将缓冲区清零,或者如果您读取了缓冲区的最后一个字节,您也可以在没有以 nul 结尾的字符串作为文件名或 md5 总和的情况下结束。然后,当您尝试将其视为以 nul 结尾的字符串时,您会得到未定义的行为。

如果您在尝试读取文件数据之前在recv 调用中确实获得了额外的字节,这就解释了为什么您的代码会卡住 - 它在进入循环之前已经读取了一些文件内容,所以循环永远不会看到所有的内容——其中一些已经消失了。

【讨论】:

  • "由于前两个字符串没有固定的字节数 ..." MD5 校验和很可能总是相同的大小:32 字节
  • 文件名 (file_in) 和 md5sum 它是一个 char file_in[128]、md5sum[128],在我的 struct tx_rx_file 中定义。我在客户端按顺序执行所有操作,发送文件名、接收文件名等关于...您的建议是使用循环?每次发送和revc的时候?在客户端有 3 个,在服务器端有 4-5 个是不是很奇怪?
  • @NickS 没有。这是八位字节(字节)流的正常行为。
【解决方案2】:

你应该避免在你的服务器中使用 strlen:

if(send(client_fd, rx_tx_file->in_name, strlen(rx_tx_file->in_name)+1,0) == -1)

而只是发送大小为sizeof(rx_tx_file-&gt;out_name) 的固定长度字符串,正如您在客户端中所期望的那样 如果文件名较小,只需用空格填充它以使其长度为sizeof(rx_tx_file-&gt;out_name)

您还应该将每个接收调用放在 while 循环中,并添加检查它是否实际接收到预期的字节数,有时 recv 只会返回部分数据,您需要发布另一个 recv 以接收其余的预期数据

【讨论】:

  • 但我不知道我应该收到哪个大小(例如文件大小)我应该如何为它写条件?
  • @NickS:你不会到处定义协议。所有基于 TCP 的应用程序都使用其中一种。
猜你喜欢
  • 2012-07-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多