c - 使用 TCP 通过套接字传输文件时卡住

标签 c sockets tcp sendfile

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

服务器端:

  • 发送姓名=确定
  • 发送 md5 校验和 = ok
  • 发送尺寸 = ok
  • 发送文件 = ko

客户端:

  • recv name = ok
  • recv md5 cecksum = ok
  • 接收大小 = 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 循环。感谢所有的帮助。

最佳答案

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->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 最多比缓冲区大小少一个字节,那么您至少可以避免未定义的行为。但是,如果您没有将缓冲区清零,或者如果您读取了缓冲区的最后一个字节,那么您的文件名或 md5 总和也可能没有以 nul 结尾的字符串。然后,当您尝试将其视为以 nul 结尾的字符串时,您会得到未定义的行为。

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

关于c - 使用 TCP 通过套接字传输文件时卡住,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45659119/

相关文章:

c++ - .o、.a 和 .so 文件有什么区别?

c - Cygwin 的 fseeko 替代品?

c# - 网络适​​配器关闭时检测客户端断开连接

c - 对非阻塞recvfrom的单次调用

tcp - 了解 Linux 内核中的 TCP 内存管理

c - 哪种数据结构在 C 中被广泛使用?

c - 指向 int 和 char(数组)的指针

java - ObjectInputStream 消耗过多内存

java tcp服务器到多个连接

connect() 或accept() 的客户端服务器问题