【问题标题】:C socket Invalid range headerC套接字无效的范围标头
【发布时间】:2017-12-24 01:15:39
【问题描述】:

我正在尝试制作一个程序来提供文件供其他人下载。 它侦听端口并接受任何请求并通过套接字发送文件。 我还不担心 NAT。

我遇到的问题是使用 aria2c(或 wget)下载该文件会出现“无效的范围标头错误”。但是,本地下载(从 127.0.0.1 下载)工作正常。

知道这可能会导致问题,我通过添加 -x1 标志禁用了 aria2c 上的多线程下载。

我做错了什么?


错误是:

12/23 20:04:07 [ERROR] CUID#8 - Download aborted. URI=http://104.38.127.225:8000
Exception: [AbstractCommand.cc:351] errorCode=8 URI=http://104.38.127.225:8000
  -> [HttpResponse.cc:86] errorCode=8 Invalid range header. Request: 98304-187031/187032, Response: 0-187031/187032

对代码的一点解释:

  1. findFilename 只是从文件路径生成文件名
  2. serveFile 打开一个套接字并监听它以向任何连接到它的人发送指定文件

我的代码:

#include <stdio.h>
#include <stdlib.h>

#include <netdb.h>
#include <netinet/in.h>

#include <string.h>

#include <unistd.h> /* for close() */

/* find file name from path */
/* file name have to be shorter than 512 characters*/
/* remember to deallocate memory */
char *findFilename(char *filepath)
{
  size_t pt;
  size_t filenamePt = 0;
  char ch;
  char *filename = (char*) malloc(512);

  for (pt = 0; pt < strlen(filepath); pt++)
  {
    ch = filepath[pt];
    filename[filenamePt] = ch;
    filenamePt++;

    if(ch == '/')
    {
      bzero(filename, 512);
      filenamePt = 0;
    }
  }

  filename[filenamePt + 1] = '\0';

  return filename;
}

int serveFile(char* filepath, int port)
{
  FILE *file;
  char *buffer;
  long int fileLength;
  size_t bufferSize;

  /* get file name */
  char* filename = findFilename(filepath);
  if (!filename)
  {
    fprintf(stderr, "file path error\n");
    return -1;
  }



  /* open file */
  file = fopen(filepath, "rb");
  if (!file)
  {
    fprintf(stderr, "file open error\n");
    return -1;
  }

  /* get file length */
  fseek(file, 0, SEEK_END);
  fileLength=ftell(file);
  fseek(file, 0, SEEK_SET);
  bufferSize = fileLength + 1;

  /* allocate memory */
  buffer=(char *)malloc(bufferSize);
  if (!buffer)
  {
      fprintf(stderr, "allocate memory error\n");
      fclose(file);
      return -1;
  }

  /* read file */
  fread(buffer, fileLength, 1, file);
  fclose(file);

  /* compose header */
  char header[1024];

  sprintf(header,
          "HTTP/1.1 200 OK\n"
          "Content-Length: %li\n"
          "Accept-Ranges: bytes\n"
          "Content-Disposition: attachment; filename=\"%s\"\n"
          "\n", fileLength, filename);

  /* do not need filename anymore */
  free(filename);

  /* create socket */
  int socketfd = socket(PF_INET, SOCK_STREAM, 0);

  /* configure socket */
  struct sockaddr_in address;

  bzero(&address, sizeof(address));
  address.sin_family = AF_INET;
  address.sin_port = htons(port); /* host to network short */
  address.sin_addr.s_addr = INADDR_ANY;

  /* bind & listen */
  if(bind(socketfd, (struct sockaddr*) &address, sizeof(address)) != 0)
  {
    fprintf(stderr, "bind error\n");
    return -1;
  }

  if (listen(socketfd, 16)!=0)
  {
    fprintf(stderr, "listen error\n");
    return -1;
  }

  /* accept client */
  while (1)
  {
    socklen_t size = sizeof(address);
    int clientSocket = accept(socketfd, (struct sockaddr*) &address, &size);

    puts("client connected");

    if (fork() == 0)
    {
      write(clientSocket, header, strlen(header));
      write(clientSocket, buffer, bufferSize);
      exit(0);
    }
    else
    {
      close(clientSocket);
    }
  }

}


int main(int argc, char *argv[])
{
  if (argc < 3 || argc > 3)
  {
    fprintf(stderr, "needs 2 arguments: file path & port\n");
    return -1;
  }
  char *filepath = argv[1];
  int port = atoi(argv[2]);

  printf("serving %s on port %d\n"
       "file name(not path) cannot exceed 512 characters\n"
         "keyboard interrupt to kill\n", filepath, port);
  serveFile(filepath, port);
}

【问题讨论】:

  • 您正在实施 HTTP,尽管您在问题的任何地方都没有这么说。
  • write 确实需要错误检查,并且确实需要处理不完整的操作。请记住,write 写入最多字节数,但它也可以写入更少(尤其是在较慢的网络连接上)...
  • @YuanFu, read the man page. write 可靠地发送它返回的字节数(即,如果 write 返回 100,则您知道发送了 100 个字节)。 TCP/IP 驱动程序有一个有限的缓冲区。您的程序应将数据流式传输到 TCP/IP 套接字,管理任何无法发送的缓冲区数据。请注意,“可靠”这个词是相对的,可靠的send 并不承诺recv。
  • 您根本没有实现范围。那么为什么要发送Accept-ranges 标头?您需要对 RFC 2616 和后续版本有很好的了解才能实现 HTTP。你有很多阅读要做。
  • @Myst 如果有中断,它只能在阻塞模式下写入更少。但它肯定需要一个错误检查,就像这段代码中的所有其他系统调用一样。

标签: c sockets http


【解决方案1】:
12/23 20:04:07 [ERROR] CUID#8 - Download aborted. URI=http://104.38.127.225:8000
Exception: [AbstractCommand.cc:351] errorCode=8 URI=http://104.38.127.225:8000
   -> [HttpResponse.cc:86] errorCode=8 Invalid range header. Request: 98304-187031/187032, Response: 0-187031/187032

你附加的错误说明了一切,真的。

您的代码假定向服务器发出的每个请求都是请求整个文件的请求。

由于许多充分的理由,情况并非总是如此(参见编辑)。

Range 请求 (using the Range header) 允许客户端请求文件的一部分,这允许将下载分成块并在网络故障后继续下载。

但是,无论发出什么请求,您的代码也会发送整个文件。

此外,您的代码正在发送 Accept-Ranges 标头,表明 Range 请求将被尊重(即使不是)。

在这种情况下,客户端抱怨您的代码,表明它请求了一个块并且您的代码发送了整个内容。

编辑

正如@RemyLebeau 所指出的,the Range function is optional, though widely used。

这就是为什么您有时只会收到错误...

aria2c 似乎在一定程度上需要范围功能,否则它将无法用作下载管理器(毕竟,管理下载是Range 功能的目的)。

其他下载工具不需要(或利用)此功能。

【讨论】:

  • 客户端发送Range 请求并不意味着服务器必须接受它。客户端可以区分部分文件响应(206)和整个文件响应(200)。服务器发回Accept-Ranges 但实际上没有实现范围并不是致命的,尽管它也不可取。如果客户端失败是因为服务器为范围请求发送 200 而不是 206,则客户端存在逻辑错误。允许发送 200。
  • @RemyLebeau 是的,你是对的。但是,在这种特定情况下,问题也与aria2c 有关,它是一个利用(并且似乎需要)Range 请求的下载管理器。
  • 如果一个下载管理器不能处理一个没有实现范围的服务器,那么它就不是一个很好的下载管理器。在这种情况下,我用过的大多数下载管理器都会忽略前 X 个字节,直到它们达到所需的偏移量,然后在保存 Y 个字节后断开连接。
  • @RemyLebeau - 由 OP 编写的服务器发布它确实处理 Range 请求(也许我的回答对这个问题不够清楚)......我认为这种行为冲突是代码中的问题。此外,我不是aria2c 的追随者。如果您认为这是一个糟糕的下载管理,那对我来说很好。我同意更好的下载管理器可能会更优雅地降级。另一方面,如果服务器不支持Range 请求,则不需要下载管理器,因为不重新启动下载就无法从错误中恢复(使得下载管理器大多是多余的)。
猜你喜欢
  • 2021-04-29
  • 2012-12-17
  • 1970-01-01
  • 2021-07-16
  • 1970-01-01
  • 2011-03-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多