7

最近,我开始阅读本指南以开始从 Internet 下载文件。我阅读了它并想出了以下代码来下载网站的 HTTP 正文。唯一的问题是,它不起作用。调用 recv() 调用时代码停止。它不会崩溃,它只是继续运行。这是我的错吗?我使用了错误的方法吗?我打算使用该代码不仅下载 .html 文件的内容,还下载其他文件(zip、png、jpg、dmg ...)。我希望有人可以帮助我。这是我的代码:

#include <stdio.h>
#include <sys/socket.h> /* SOCKET */
#include <netdb.h> /* struct addrinfo */
#include <stdlib.h> /* exit() */
#include <string.h> /* memset() */
#include <errno.h> /* errno */
#include <unistd.h> /* close() */
#include <arpa/inet.h> /* IP Conversion */

#include <stdarg.h> /* va_list */

#define SERVERNAME "developerief2.site11.com"
#define PROTOCOL "80"
#define MAXDATASIZE 1024*1024

void errorOut(int status, const char *format, ...);
void *get_in_addr(struct sockaddr *sa);

int main (int argc, const char * argv[]) {
    int status;

    // GET ADDRESS INFO
    struct addrinfo *infos; 
    struct addrinfo hints;

    // fill hints
    memset(&hints, 0, sizeof(hints));
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;
    hints.ai_family = AF_UNSPEC;

    // get address info
    status = getaddrinfo(SERVERNAME, 
                         PROTOCOL, 
                         &hints, 
                         &infos);
    if(status != 0)
        errorOut(-1, "Couldn't get addres information: %s\n", gai_strerror(status));

    // MAKE SOCKET
    int sockfd;

    // loop, use first valid
    struct addrinfo *p;
    for(p = infos; p != NULL; p = p->ai_next) {
        // CREATE SOCKET
        sockfd = socket(p->ai_family, 
                        p->ai_socktype, 
                        p->ai_protocol);
        if(sockfd == -1)
            continue;

        // TRY TO CONNECT
        status = connect(sockfd, 
                         p->ai_addr, 
                         p->ai_addrlen);
        if(status == -1) {
            close(sockfd);
            continue;
        }

        break;
    }

    if(p == NULL) {
        fprintf(stderr, "Failed to connect\n");
        return 1;
    }

    // LET USER KNOW
    char printableIP[INET6_ADDRSTRLEN];
    inet_ntop(p->ai_family,
              get_in_addr((struct sockaddr *)p->ai_addr),
              printableIP,
              sizeof(printableIP));
    printf("Connection to %s\n", printableIP);

    // GET RID OF INFOS
    freeaddrinfo(infos);

    // RECEIVE DATA
    ssize_t receivedBytes;
    char buf[MAXDATASIZE];
    printf("Start receiving\n");
    receivedBytes = recv(sockfd, 
                         buf, 
                         MAXDATASIZE-1, 
                         0);
    printf("Received %d bytes\n", (int)receivedBytes);
    if(receivedBytes == -1)
        errorOut(1, "Error while receiving\n");

    // null terminate
    buf[receivedBytes] = '\0';

    // PRINT
    printf("Received Data:\n\n%s\n", buf);

    // CLOSE
    close(sockfd);

    return 0;
}

void *get_in_addr(struct sockaddr *sa) {
    // IP4
    if(sa->sa_family == AF_INET)
        return &(((struct sockaddr_in *) sa)->sin_addr);

    return &(((struct sockaddr_in6 *) sa)->sin6_addr);
}

void errorOut(int status, const char *format, ...) {
    va_list args;
    va_start(args, format);
    vfprintf(stderr, format, args);
    va_end(args);
    exit(status);
}
4

3 回答 3

12

如果您想使用 HTTP 获取文件,那么libcURL可能是您在 C 语言中的最佳选择。但是,如果您使用它作为学习网络编程的一种方式,那么您必须先了解更多关于 HTTP 的知识,然后才能学习检索文件。

您在当前程序中看到的是,您需要先发送对该文件的显式请求,然后才能检索它。我将从阅读RFC2616开始。不要试图理解这一切——这个例子需要阅读很多内容。阅读第一部分以了解 HTTP 的工作原理,然后阅读第4、5 和 6部分以了解基本消息格式。

以下是 stackoverflow Questions 页面的 HTTP 请求的示例:

GET http://stackoverflow.com/questions HTTP/1.1\r\n
Host: stackoverflow.com:80\r\n
Connection: close\r\n
Accept-Encoding: identity, *;q=0\r\n
\r\n

我相信这是一个最小的要求。我明确添加了 CRLF,以表明使用空行来终止请求标头块,如 RFC2616 中所述。如果您省略了Accept-Encoding标头,那么结果文档可能会作为 gzip 压缩流传输,因为 HTTP 明确允许这样做,除非您告诉服务器您不想要它。

服务器响应还包含描述响应的元数据的 HTTP 标头。以下是上一个请求的响应示例:

HTTP/1.1 200 OK\r\n
Server: nginx\r\n
Date: Sun, 01 Aug 2010 13:54:56 GMT\r\n
Content-Type: text/html; charset=utf-8\r\n
Connection: close\r\n
Cache-Control: private\r\n
Content-Length: 49731\r\n
\r\n
\r\n
\r\n
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" ... 49,667 bytes follow

如果您想使用 HTTP 抓取文件,这个简单的示例应该让您了解要实现的内容。这是最好的情况,最简单的例子。这不是我会轻易承担的事情,但它可能是学习和欣赏 HTTP 的最佳方式。

如果您正在寻找一种简单的方式来学习网络编程,那么这是一个不错的开始方式。我建议您阅读TCP/IP Illustrated, Volume 1UNIX Network Programming, Volume 1的副本。这些可能是真正学习如何编写基于网络的应用程序的最佳方式。我可能会先编写一个FTP 客户端,因为FTP是一个更简单的协议。

如果您正在尝试了解与 HTTP 相关的详细信息,那么:

  1. 购买HTTP:权威指南并阅读
  2. 阅读RFC2616直到你理解它
    • 尝试使用示例telnet server 80并手动输入请求
    • 下载 cURL 客户端并使用--verbose--include命令行选项,以便您可以看到正在发生的事情
  3. 阅读Fielding 的论文,直到 HTTP 真正有意义。

只是不打算为企业使用编写自己的 HTTP 客户端。你不想那样做,相信我作为一个一直在维持这样一个错误的人......

于 2010-08-01T14:36:27.600 回答
7

问题是,您必须实现 HTTP 协议。下载文件不仅仅是连接到服务器的问题,您必须在收到响应之前发送 HTTP 请求(以及正确的 HTTP 标头)。在此之后,您仍然需要解析返回的数据以去除更多的 HTTP 标头。

如果您只是想使用 C 下载文件,我建议您使用cURL 库,它可以为您提供 HTTP 工作。

于 2010-08-01T13:25:56.267 回答
3

您必须先发送 HTTP 请求,然后才能收到响应。您的代码当前只是等待一个永远不会出现的响应。

另外,不要全部大写评论。

于 2010-08-01T13:24:53.590 回答