创建HTTP客户端以下载网页以便在C中进行脱机查看

时间:2014-12-11 11:19:10

标签: c sockets http buffer webpage

我正在创建一个HTTP客户端,它根据命令行参数下载网页。它接受参数,查找域名以获取IP地址,创建套接字,连接到服务器并发送GET请求并等待回复。这一切都很好但是当我使用缓冲区和while循环读取我的回复时,我也收到一些不可读的字符。如果您运行代码并查看html,您将在页面上看到不可读的字符。

我的代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
    int socket_desc, i, bytes_read;    
    char server_reply[1024], ip[100], request[100];;
    char *hostname = argv[1];
    struct sockaddr_in server;
    struct hostent *he;
    struct in_addr **addr_list;
    FILE *fp;

    if ((he = gethostbyname(hostname)) == NULL) {
        //gethostbyname failed
        herror("gethostbyname\n");
        return 1;
    }

    addr_list = (struct in_addr **) he->h_addr_list;

    for(i = 0; addr_list[i] != NULL; i++) {
        //Return the first one;
        strcpy(ip , inet_ntoa(*addr_list[i]) );
    }

    //Create socket
    socket_desc = socket(AF_INET, SOCK_STREAM, 0);
    if (socket_desc == -1) {
        printf("Could not create socket!\n");
    }

    server.sin_addr.s_addr = inet_addr(ip);
    server.sin_family = AF_INET;
    server.sin_port = htons(80);

    //Connect to remote server
    if (connect(socket_desc , (struct sockaddr *)&server , sizeof(server)) < 0) {
        printf("connect error!\n");
        return 1;
    }

    printf("Connected...\n");

    //Send some data
    snprintf(request, 99, "GET / HTTP/1.1\r\n"
            "Host: %s\r\n"
            "\r\n\r\n", hostname
    );

    if (send(socket_desc, request, strlen(request), 0) < 0) {
        puts("Send failed!\n");
        return 1;
    }
    puts("Data Sent...\n");

    //Receive a reply from the server

    fp = fopen("/home/localusr/Desktop/ouput.html", "w+");

    while (bytes_read = read(socket_desc, server_reply, sizeof(server_reply)) > 0) {
        fputs(server_reply, fp);
        memset(server_reply, 0, sizeof(server_reply));
    } 
    do {
        bytes_read = read(socket_desc, server_reply, sizeof(server_reply));
        fputs(server_reply, fp);
        memset(server_reply, 0, sizeof(server_reply));
    } while (bytes_read > 0);

    printf("reply received...\n");

    fclose(fp);
    close(socket_desc);

    return 0;
}

抱歉这段代码很糟糕。任何帮助非常感谢。我正在使用Ubuntu机器并使用gcc编译我的代码。

编辑:

orb.ws.require.lib--> <script type="text/javascript">/*
be2

be2不应该在那里。 *也获得'@'符号  

3 个答案:

答案 0 :(得分:1)

编辑: 要在此处发表评论:

请注意,例如www.bbc.co.uk响应标题说“Transfer-Encoding:chunked”,这意味着每个块的长度都是十六进制数字,后跟数据后跟\ r \ n。

也就是说,根据你的例子:

be2\r\n => 0xbe2\r\n => 3042\r\n

“此处跟随3042字节”(在\ r \ n又称CRLF或十六进制0d0a之后)。

Example块:

e\r\nStack Exchange
|  | ||||||||||||||
|  | +............+
|  |        |
|  |        +-------- 14 bytes
|  +----------------- \r\n
+-------------------- 0x0e == 14 dec in hex

<强> 旧:


您可以通过以下方式正确终止读取字节,而不是memset等。

while ( (bytes_read = read(socket_desc, server_reply, sizeof(server_reply) - 1)) > 0) {
    server_reply[bytes_read] = 0x00;

此后bytes_read之外的任何内容都不会fputs


当您将memset整个缓冲区移至0 读入整个缓冲区时 - 除非读取次数少于缓冲区,否则memset无效尺寸。您只需在完整(1024)读取时覆盖所有零,然后写入1024 +垃圾直到第一个零。


read()返回读取的字节数。通过将server_reply[bytes_read]设置为0,您实际上会终止实际数据。将它变成C字符串。如果不将最后一个字节设置为零,fputs()将在bytes_read之后继续输出垃圾,直到第一个零或崩溃。

换句话说; read()读取最多size个字节,如果全部为零字节则不关心。如果告诉read()读取356GiB数据,文件描述符提供356GiB的零(如0x00字节,而不是ASCII 0) - 这就是你得到的。

您的套接字 以0结束传递。它像服务器一样提供零字节作为数据的一部分。假设您使用零字节传输图像或其他数据;换句话说:它不是一个零终止的字符串read()得到的。

另请注意- 1之后的sizeof - 为空字节腾出空间。

然而

fputs 写入,直到第一次终止空字节,但不包括在输出中(如果您正在编写缓冲的 string <,这通常是您想要的/强>数据)。


实施例

char buf[8];

Char未初始化且包含垃圾。例如,它可能是:

buf[0] == 0x13
buf[1] == 0x0a
buf[2] == 0x00
buf[3] == 0x65
buf[4] == 0x78
buf[5] == 0xf3
buf[6] == 0x00
buf[7] == 0xaf

超出buf你有随机垃圾,例如

buf[7+1] == 0xde
buf[7+2] == 0xa0
buf[7+3] == 0x33
buf[7+3] == 0x00
  
    

bytes_read = read(soc,buf,8);     soc提供:'ABCDEFG'

  

缓冲区现在是:

buf[0] == 0x41 (A)
buf[1] == 0x42 (B)
buf[2] == 0x43 (C)
buf[3] == 0x44 (D)
buf[4] == 0x45 (E)
buf[5] == 0x46 (F)
buf[6] == 0x47 (G)
buf[7] == 0xaf (H)

buf[7]以外的字节仍然充满了垃圾;并且你的fputs()将会读取并将数据传递给文件,直到第一个零。

这就是为什么你改为说:

bytes_read = read(soc, buf, 7);
buf[bytes_read] = 0x00;

现在我们只读A-G。最后一个字节设置为0。

此处fputs(buf, fh)要先写\0,换句话说ABCDEFG

如果服务器现在在下次运行中提供,例如,只提供两个字节:

buf[0] == 0x48 (H)
buf[1] == 0x5A (Z)

然后bytes_read将为2,声明为:

buf[bytes_read] = 0x00 ===> buf[2] = 0x00

给你

buf[0] == 0x48 (H)
buf[1] == 0x5A (Z)
buf[2] == 0x00 (0x00) <<--- nulled out
                      +---.
buf[3] == 0x44 (D)    |    \
buf[4] == 0x45 (E)    |     \
buf[5] == 0x46 (F)    |      }--->>> garbage from previous read.
buf[6] == 0x47 (G)    |     / 
buf[7] == 0x00 (0x00) |    /
                      +---/

此处fputs(buf, fh)要先写\0,换句话说HZ

答案 1 :(得分:0)

您是否尝试使用telnet访问网页?

请执行以下操作:

telnet [hostname] [port]

在telnet shell中输入:

GET / HTTP/1.1
Host: [hostname]
<return>

(介意主持人之后的额外回报!

请发布telnet的结果和代码中的结果

  • 编辑*

发现问题:

您使用fputs而不是fwrite。 fputs需要一个字符串,它通过查找NULL字符来检测。

但是,在你的情况下,不承诺这样的NULL字符,所以你必须明确。 作为奖励,您的程序现在终止并刷新输入到文件。 修复:

使用以下do while循环替换你的while和while循环:

do
{
    int write;
    bytes_read = read(socket_desc, server_reply, sizeof(server_reply));
    write = fwrite(server_reply, 1, bytes_read, fp);
    printf("Written %d bytes_read: %d\n", write, bytes_read);
    memset(server_reply, 0, sizeof(server_reply));
    fflush(fp);
} while (bytes_read > 0); // This termination is wrong! You should look at Content-Length from the server's reply to detect the actual length

现在有效....

答案 2 :(得分:0)

read()不会终止字节。但fputs()取决于空终止,因此如果要将其传递给fputs(),则必须在char数组的末尾附加0x00。