【问题标题】:C program file transfer, printing out server side errors on client sideC程序文件传输,在客户端打印服务器端错误
【发布时间】:2018-03-22 10:50:23
【问题描述】:

我正在创建一个程序,该程序从服务器请求文件并将文件发回,创建一个新文件并将该文件的内容写入新文件。除了服务器端错误消息(即文件不存在)应在客户端打印外,一切都运行良好。谁能给我一个有关如何完成此任务的线索?我是 C 的新手,但愿意学习更多。我的主要问题是,如果我直接发送消息,客户端不知道这是一个错误,并且将完成并创建一个文件并将消息写入该文件。我在想我需要发回一个-1,所以当客户端调用recv时,它会返回-1作为长度并打印一条消息。

这是我的客户:

  ssize_t recvx(int sockfd, void *buf, size_t len) {
    int var = recv(sockfd, buf, len, 0);
    if(var != -1)
    {
      return var;
    } else {
      printf("%s \n","Did not receive.");
      exit(1);
    }
  }

  int main(int argc, char *argv[])
  {
    char buf[MAX_LINE];
    struct addrinfo hints;
    struct addrinfo *rp, *result;
    int bytes_received;
    int s;
    char *server;
    char *port;
    char *file;
    int fd = -1; //file descriptor
    int bytes_written;

    if (argc==4)
    {
      server = argv[1];
      port = argv[2];
      file = argv[3];
    }
    else
    {
      fprintf(stderr, "invalid # of arguments\n");
      exit(1);
    }

    /* Translate host name into peer's IP address */
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = 0;
    hints.ai_protocol = 0;

    if ((s = getaddrinfo(server, port, &hints, &result)) != 0 )
    {
      fprintf(stderr, "%s: getaddrinfo: %s\n", argv[0], gai_strerror(s));
      exit(1);
    }

    /* Iterate through the address list and try to connect */
    for (rp = result; rp != NULL; rp = rp->ai_next)
    {
      if ((s = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) == -1 )
      {
        continue;
      }
      if (connect(s, rp->ai_addr, rp->ai_addrlen) != -1)
 {
        break;
      }
      close(s);
    }

    if (rp == NULL)
    {
      perror("stream-talk-client: connect");
      exit(1);
    }
    freeaddrinfo(result); 
    /*send lines of text */ 
   send(s, file, sizeof(file), 0);


   while(bytes_received != 0)
    {
      bytes_received = recvx(s, buf, 20);
      if(bytes_received == -1)
      {
        fprintf(stderr, "Client Error: Error receiving file \n");
        exit(1);
      } else {
        if(fd == -1)
        {
          fd = open(file, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
          if(fd == -1)
          {
           fprintf(stderr,"Client Error: Open failed \n");
           exit(1);
         }
         bytes_written = write(fd,buf,bytes_received);
         if(bytes_written == -1)
         {
           fprintf(stderr,"%s \n", "Client Error: Write error");
           exit(1);
         }
       } else {
         bytes_written = write(fd,buf,bytes_received);
         if(bytes_written == -1)
         {
           fprintf(stderr,"%s \n", "Client Error: Write error");
           exit(1);
         }
       }
     }
   }
   if(close(fd) != 0)
   {
     printf("%s \n", "Client Error: File did not close successfully");
     exit(1);
   }


   close(s);

   return 0;
 }  

这是我的服务器:

int main(int argc, char *argv[])
  {
    struct addrinfo hints;
    struct addrinfo *rp, *result;
    char filename[MAX_LINE];
    int s, new_s;
    int bytes_transferred;
    int fd; //file descriptor
    struct stat statBuffer; //to hold file info
    off_t offset = 0;

    /* Build address data structure */
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;
    hints.ai_protocol = 0;
    hints.ai_canonname = NULL;
    hints.ai_addr = NULL;
    hints.ai_next = NULL;

    /* Get local address info */
    if ((s = getaddrinfo(NULL, argv[1], &hints, &result)) != 0 )
    {
      fprintf(stderr, "%s: getaddrinfo: %s\n", argv[0], gai_strerror(s));
      exit(1);
    }

    /* Iterate through the address list and try to perform passive open */
    for (rp = result; rp != NULL; rp = rp->ai_next)
    {
      if ((s = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) == -1 )
      {
        continue;
      }

      if (!bind(s, rp->ai_addr, rp->ai_addrlen))
      {
        break;
      }
      close(s);
    }
    if (rp == NULL)
    {
      perror("stream-talk-server: bind");
      exit(1);
    }
    if (listen(s, MAX_PENDING) == -1)
    {
      perror("stream-talk-server: listen");
      close(s);
      exit(1);
    }

    freeaddrinfo(result);

    /* Wait for connection, then receive and print text */
    while(1)
     { 
      if ((new_s = accept(s, rp->ai_addr, &(rp->ai_addrlen))) < 0)
      {
        perror("stream-talk-server: accept");
        close(s);
        exit(1);
      }
      while(bytes_transferred == recv(new_s,filename,sizeof(filename),0))
      {
        if(bytes_transferred == -1)
        {
          fprintf(stderr, "Server Error: Error receiving filename \n");
          exit(1);
        }
      }
      printf("%s \n", filename);
      fd =open(filename,O_RDONLY);

      if(fd < 0)
      {
        fprintf(stderr,"Server Error: file doesn't exist\n");
        exit(1);
     }
     else
     {
       printf("%s \n","file opened successfully");
     }
     /*get info from file descriptor (fd) and store it in statBuffer struct */
     fstat(fd, &statBuffer);
     bytes_transferred = sendfile(new_s,fd,&offset,statBuffer.st_size);
     if(bytes_transferred == -1)
     {
       fprintf(stderr, "Server Error: File not transferred successfully");
     }
     else
     {
       printf("%s \n", "File transferred successfully");
     }
     if(close(fd) != 0)
     {
       fprintf(stderr, "Server Error: File not close successfully");
     }
     else{
       break;
     }
   }
   close(new_s);

   return 0;
 }

【问题讨论】:

  • 感谢您的回复。我的具体问题是弄清楚如何将错误消息发送回客户端以打印出来。 (即服务器错误:文件不存在)。如果我直接发送消息,客户端会收到一些内容并将其存储在要存储文件的位置。不希望这样,所以我想知道服务器是否有某种方法可以向客户端指示该文件不存在并且客户端打印出该错误发生在服务器端。谢谢
  • 在这种情况下,open 将返回一个负值。检查errno,如果是找不到文件的情况,向客户端返回一些值,并让客户端在写入文件之前检查其返回值?如果文件不存在,则服务器中的 Open 系统调用将失败,并且 errno 将设置为 ENOENT
  • 你需要一个应用协议。也就是说,客户端和服务器期望彼此看到的一组消息/响应。对于你这里的小项目,不必很复杂。
  • 啊好的。所以如果服务器上没有错误消息,我应该只接收。我将查找应用程序协议的文档。我在想我需要将errno存储在某些东西中,并在recv和recv返回返回的字节数时将其发送回客户端,我希望为-1,因为不存在文件,但它返回0,因为没有发送文件,大声笑。
  • 除非您在客户端收到错误,否则您永远不会知道服务器端是否有错误。 recv() 返回零,因为对等方关闭了连接。

标签: c networking server client


【解决方案1】:

基本上,您要实现的内容与 HTTP 和 FTP 服务器的功能类似。两者都有用于处理错误的特定但相当不同的协议步骤。 HTTP 案例很容易理解——服务器不只是发回文件:它发回一堆头文件,然后是文件。客户端可以通过检查 headers 来了解是否有任何文件数据要跟踪。

在一个非常简单的情况下,我怀疑您可以在代码中实现类似的东西而无需太多工作——只需让服务器在文件的其余部分之前发送一个单字节的响应代码。您可以使用 0(例如)表示后面跟着一个有效文件,而其他数字则表示特定的错误情况。客户端会像当前一样读取数据,但如果第一个字节不为零,它就知道后面的任何其他内容(如果有的话)都是有效的。

或者您可以安排,如果第一个字节不为零,则后面的文本不是预期的文件,而是一条错误消息,应该由客户端显示。

您可以随意扩展这种方案的复杂性,直到最终使用 HTTP :)

【讨论】:

  • 啊好的,我明白你在说什么。我对如何实现接收有点困惑。接收到的任何内容都存储在 char buf 数组中,recv 返回接收到的字节数(或失败时返回 -1)。我收到的文件中可能有任何内容。我将如何区分它是我收到的文件还是错误代码。例如,如果我使用 -1 表示接收文件失败,它会将其视为存储在 buf 中的内容并将其写入新文件。
  • 如果recv() 返回 -1(作为它的返回值),那么检查放入其缓冲区的任何数据是没有意义的——在您的通信协议之外发生了某种错误.您的客户只需要报告这一点,或许可以查询errno 以获取更多详细信息。我的建议是将成功/失败代码放入服务器返回的数据中,可能作为第一个字节。客户端会将所有返回的数据视为数据,除了第一个字节,即成功/失败代码。这种报告状态的机制除了recv()调用提供的任何东西。
  • 啊哈,我明白了。在我的 while 循环之前,我调用了 recvx,如果它立即等于 0,那么我什么也没有收到,我可以退出。但是如果它是一个空文件呢?即使文件是空白的,那仍然需要传输一些字节吗?
  • 啊,很高兴知道。我要发回的第一个字节将位于文件名 [0] 中(接收端的 buf [0])?当我尝试将 errno 存储到其中并不断收到垃圾字符时,我打印了该值。并在错误编号中硬编码以发回 yield 编译器错误。需要 const *void 作为参数。
  • 尝试存储errno+32,然后在接收端减去32。这会将 errno 值转移到可显示的 ASCII 范围内。这有点厚颜无耻,因为原则上,errno 值可能太大而无法放入单个字节。或许更优雅的是,将 errno 写为四位十六进制数,并将返回数据的前四个字节用作该值。无论如何,乱码显示本身并不表示有问题——errno 值通常是两个小值,被视为可显示字符。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-17
  • 1970-01-01
  • 2019-03-17
  • 1970-01-01
相关资源
最近更新 更多