1

客户端在接收文件时使用这部分代码:

void do_retr_cmd(int f_sockd){
  int fd;
  ssize_t nread = 0;
  uint32_t fsize, fsize_tmp, total_bytes_read, size_to_receive;
  char *filename = NULL, *conferma = NULL, *filebuffer = NULL;
  char buf[256], dirp[256], t_buf[256];

  memset(dirp, 0, sizeof(dirp));
  memset(buf, 0, sizeof(buf));
  memset(t_buf, 0, sizeof(t_buf));
  printf("Write the name of file to download: ");
  fgets(dirp, BUFFGETS, stdin)
  filename = NULL;
  filename = strtok(dirp, "\n");
  sprintf(buf, "RETR %s", dirp);
  if(send(f_sockd, buf, strlen(buf), 0) < 0){
    perror("Errore durante l'invio del nome del file");
    onexit(f_sockd, 0, 0, 1);
  }
  fsize = 0;
  recv(f_sockd, t_buf, sizeof(t_buf), 0)
  fsize = atoi(t_buf);
  fd = open(filename, O_CREAT | O_WRONLY, 0644);
  fsize_tmp = fsize;
  filebuffer = (char *)malloc(fsize);
  total_bytes_read = 0;
  nread = 0;
  for(size_to_receive = fsize; size_to_receive > 0;){
    nread = read(f_sockd, filebuffer, size_to_receive);
    if(nread < 0){
      perror("read error on retr");
      onexit(f_sockd, 0, 0, 1);
    }
    if(write(fd, filebuffer, nread) != nread){
      perror("write error on retr");
      onexit(f_sockd, 0, 0, 1);
    }
    size_to_receive -= nread;
  }
  close(fd);
  fflush(stdout);
  fflush(stdin);
  memset(buf, 0, sizeof(buf));
  recv(f_sockd, buf, 21, 0)
  printf("%s", buf);
  memset(buf, 0, sizeof(buf));
  memset(t_buf, 0, sizeof(t_buf));
  memset(dirp, 0, sizeof(dirp));
  free(filebuffer);
}

服务器在发送文件时会使用这部分代码:

void do_server_retr_cmd(f_sockd, m_sockd){
  int fd, rc;
  uint32_t fsize, size_to_send;
  char *filename = NULL, *other = NULL;
  char buf[512], t_buf[256];
  off_t offset;
  struct stat fileStat;

  memset(buf, 0, sizeof(buf));
  memset(t_buf, 0, sizeof(t_buf));
  recv(f_sockd, buf, sizeof(buf), 0)
  other = NULL;
  filename = NULL;
  other = strtok(buf, " ");
  filename = strtok(NULL, "\n");

  if(strcmp(other, "RETR") == 0){
    printf("Ricevuta richiesta RETR\n");
  } else /* do something */

  fd = open(filename, O_RDONLY);

  memset(&fileStat, 0, sizeof(fileStat));
  fileStat.st_size = 0;
  fstat(fd, &fileStat)
  fsize = fileStat.st_size;
  snprintf(t_buf, 255, "%" PRIu32, fsize);
  send(f_sockd, t_buf, sizeof(t_buf), 0)
  offset = 0;
  for (size_to_send = fsize; size_to_send > 0; ){
    rc = sendfile(f_sockd, fd, &offset, size_to_send);
    if (rc <= 0){
      perror("sendfile");
      onexit(f_sockd, m_sockd, fd, 3);
    }
    size_to_send -= rc;
  }
  close(fd);
  fflush(stdout);
  fflush(stdin);
  memset(buf, 0, sizeof(buf));
  strcpy(buf, "226 File transfered\n");
  send(f_sockd, buf, strlen(buf), 0)
  memset(buf, 0, sizeof(buf));
  memset(t_buf, 0, sizeof(t_buf));
}

--> 省略了错误检查 <--
我对这两段代码有一个大问题。当我启动主程序时,我必须编写:
1.retr 然后按 Enter
2.Write the filename to download: 我写文件名,然后按 Enter
问题是有时文件下载正确,有时没有下载,而是其中的一部分显示在标准输出上(在终端上)。
我不明白为什么我会出现这种奇怪的行为。
PS:我知道我的代码很丑,但我是 C 新手!

我正在 Ubuntu amd64 上开发并使用 GCC-4.6.3(C 语言)。

4

3 回答 3

3

TCP 连接为您提供了可靠的双向字节流,但您的“应用程序消息”的边界不会被保留,这意味着一个send()可以在recv()另一侧的多个 s 中接收,反之,send()可以折叠几个 s成一个recv()(你可以收到你发送的最后一个块的一部分)。好消息是您确实收到了您发送的字节,按照您发送它们的顺序。

服务器代码中的行recv(f_sockd, buf, sizeof(buf), 0);假定您在此处读取文件名,而实际上您可以获取256客户端发送的任何字节。

您需要在裸 TCP 之上施加某种应用程序级协议。一个非常简单的方法是在文件内容前面发送一个文本标题,格式如下:

file-size file-name\n

因此,您的服务器可以查找第一个换行符,在第一个空格处分割行,并获得预期的字节数,以及将这些字节保存到的文件名。不要忽略该换行符之后的接收缓冲区的其余部分,将其保存到文件中。这也使您可以重新使用该连接进行多个文件传输。

希望这可以帮助。

于 2012-08-29T12:15:26.680 回答
1
recv(f_sockd, buf, 21, 0)
printf("%s", buf);

printf会打印出大量随机垃圾,因为没有实施实际的协议来控制它接收的内容和打印的内容。例如,如何printf知道要打印多少字节?

于 2012-08-29T12:16:39.297 回答
1

我的答案的先前版本不太正确,但这就是您看到奇怪行为的原因。您发送的文件大小为

snprintf(t_buf, 255, "%" PRIu32, fsize);

然后你收到

recv(f_sockd, t_buf, sizeof(t_buf), 0)

但这不能保证实际读取sizeof(t_buf)字节。然后atoi有时会返回不正确的大小,并且文件的其余部分被视为状态消息,在末尾打印(直到第一个空字符)。

因为recv可能不会一次返回您要求的所有数据,所以您必须检查其返回值并可能重复调用recv

size_t to_recv = sizeof(t_buf);
size_t rcvd = 0;
while (to_recv > 0) {
    ssize_t r = recv(f_sockd, t_buf + rcvd, sizeof(t_buf) - rcvd, 0);
    if (r < 0) {
        //error
    }
    else {
        to_recv -= r;
        rcvd += r;
    }
}

显然,您要么必须知道预期有多少数据,要么按照其他答案中的建议提出更好的协议(例如,寻找终止符以确定何时读取大小)。

于 2012-08-29T12:17:12.403 回答