54

我想编写一个 C 程序来生成一个获取请求而不使用任何外部库。这是否可能仅使用 C 库,使用套接字?我正在考虑制作一个http数据包(使用正确的格式)并将其发送到服务器。这是唯一可能的方法还是有更好的方法?

4

4 回答 4

39

使用 BSD 套接字,或者,如果你有一些限制,假设你有一些 RTOS,一些更简单的 TCP 堆栈,如 lwIP,你可以形成 GET/POST 请求。

有许多开源实现。请参阅“happyhttp”作为示例(http://scumways.com/happyhttp/happyhttp.html)。我知道,它是 C++,而不是 C,但唯一“依赖于 C++”的是字符串/数组管理,因此很容易移植到纯 C。

请注意,没有“数据包”,因为 HTTP 通常通过 TCP 连接传输,所以从技术上讲,只有 RFC 格式的符号流。由于 http 请求通常以连接-发送-断开方式完成,因此实际上可以将其称为“数据包”。

基本上,一旦你有一个打开的套接字(sockfd)“所有”你需要做的就是

char sendline[MAXLINE + 1], recvline[MAXLINE + 1];
char* ptr;

size_t n;

/// Form request
snprintf(sendline, MAXSUB, 
     "GET %s HTTP/1.0\r\n"  // POST or GET, both tested and works. Both HTTP 1.0 HTTP 1.1 works, but sometimes 
     "Host: %s\r\n"     // but sometimes HTTP 1.0 works better in localhost type
     "Content-type: application/x-www-form-urlencoded\r\n"
     "Content-length: %d\r\n\r\n"
     "%s\r\n", page, host, (unsigned int)strlen(poststr), poststr);

/// Write the request
if (write(sockfd, sendline, strlen(sendline))>= 0) 
{
    /// Read the response
    while ((n = read(sockfd, recvline, MAXLINE)) > 0) 
    {
        recvline[n] = '\0';

        if(fputs(recvline, stdout) == EOF)
        {
            printf("fputs() error\n");
        }

        /// Remove the trailing chars
        ptr = strstr(recvline, "\r\n\r\n");

        // check len for OutResponse here ?
        snprintf(OutResponse, MAXRESPONSE,"%s", ptr);
    }          
}
于 2012-06-26T13:44:49.217 回答
29

POSIX 7 最小可运行示例

让我们获取http://example.com

wget.c

#define _XOPEN_SOURCE 700
#include <arpa/inet.h>
#include <assert.h>
#include <netdb.h> /* getprotobyname */
#include <netinet/in.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

int main(int argc, char** argv) {
    char buffer[BUFSIZ];
    enum CONSTEXPR { MAX_REQUEST_LEN = 1024};
    char request[MAX_REQUEST_LEN];
    char request_template[] = "GET / HTTP/1.1\r\nHost: %s\r\n\r\n";
    struct protoent *protoent;
    char *hostname = "example.com";
    in_addr_t in_addr;
    int request_len;
    int socket_file_descriptor;
    ssize_t nbytes_total, nbytes_last;
    struct hostent *hostent;
    struct sockaddr_in sockaddr_in;
    unsigned short server_port = 80;

    if (argc > 1)
        hostname = argv[1];
    if (argc > 2)
        server_port = strtoul(argv[2], NULL, 10);

    request_len = snprintf(request, MAX_REQUEST_LEN, request_template, hostname);
    if (request_len >= MAX_REQUEST_LEN) {
        fprintf(stderr, "request length large: %d\n", request_len);
        exit(EXIT_FAILURE);
    }

    /* Build the socket. */
    protoent = getprotobyname("tcp");
    if (protoent == NULL) {
        perror("getprotobyname");
        exit(EXIT_FAILURE);
    }
    socket_file_descriptor = socket(AF_INET, SOCK_STREAM, protoent->p_proto);
    if (socket_file_descriptor == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    /* Build the address. */
    hostent = gethostbyname(hostname);
    if (hostent == NULL) {
        fprintf(stderr, "error: gethostbyname(\"%s\")\n", hostname);
        exit(EXIT_FAILURE);
    }
    in_addr = inet_addr(inet_ntoa(*(struct in_addr*)*(hostent->h_addr_list)));
    if (in_addr == (in_addr_t)-1) {
        fprintf(stderr, "error: inet_addr(\"%s\")\n", *(hostent->h_addr_list));
        exit(EXIT_FAILURE);
    }
    sockaddr_in.sin_addr.s_addr = in_addr;
    sockaddr_in.sin_family = AF_INET;
    sockaddr_in.sin_port = htons(server_port);

    /* Actually connect. */
    if (connect(socket_file_descriptor, (struct sockaddr*)&sockaddr_in, sizeof(sockaddr_in)) == -1) {
        perror("connect");
        exit(EXIT_FAILURE);
    }

    /* Send HTTP request. */
    nbytes_total = 0;
    while (nbytes_total < request_len) {
        nbytes_last = write(socket_file_descriptor, request + nbytes_total, request_len - nbytes_total);
        if (nbytes_last == -1) {
            perror("write");
            exit(EXIT_FAILURE);
        }
        nbytes_total += nbytes_last;
    }

    /* Read the response. */
    fprintf(stderr, "debug: before first read\n");
    while ((nbytes_total = read(socket_file_descriptor, buffer, BUFSIZ)) > 0) {
        fprintf(stderr, "debug: after a read\n");
        write(STDOUT_FILENO, buffer, nbytes_total);
    }
    fprintf(stderr, "debug: after last read\n");
    if (nbytes_total == -1) {
        perror("read");
        exit(EXIT_FAILURE);
    }

    close(socket_file_descriptor);
    exit(EXIT_SUCCESS);
}

GitHub 上游.

编译:

gcc -ggdb3 -std=c99 -Wall -Wextra -o wget wget.c

获取http://example.com并输出到标准输出:

./wget example.com

我们看到类似的东西:

debug: before first read
debug: after a read
HTTP/1.1 200 OK
Age: 540354
Cache-Control: max-age=604800
Content-Type: text/html; charset=UTF-8
Date: Tue, 02 Feb 2021 15:21:14 GMT
Etag: "3147526947+ident"
Expires: Tue, 09 Feb 2021 15:21:14 GMT
Last-Modified: Thu, 17 Oct 2019 07:18:26 GMT
Server: ECS (nyb/1D11)
Vary: Accept-Encoding
X-Cache: HIT
Content-Length: 1256

<!doctype html>
<html>
...
</html>

打印回复后,对于大多数服务器,此命令会挂起,直到超时,这是预期的:

  • 服务器或客户端必须关闭连接
  • 我们(客户)没有这样做
  • 大多数 HTTP 服务器保持连接打开,直到超时期待进一步的请求,例如 HTML 页面后的 JavaScript、CSS 和图像
  • 我们可以解析响应,并在读取 Content-Length 字节时关闭,但为了简单起见我们没有这样做。需要什么 HTTP 响应标头表示如果Content-Length 未发送,则服务器可以关闭以确定长度。

但是,我们可以通过将 HTTP 1.1 标准标头添加Connection: close到服务器来关闭主机:

char request_template[] = "GET / HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n";

连接部分也适用于 IP:

host example.com

给出:

example.com has address 93.184.216.34
example.com has IPv6 address 2606:2800:220:1:248:1893:25c8:1946

所以我们这样做:

./wget 93.184.216.34

然而,回复是一个错误,因为我们没有Host:在我们的程序中正确设置,这是HTTP 1.1 所要求的

在 Ubuntu 18.04 上测试。

服务器示例

于 2016-02-28T09:06:17.963 回答
7

严格来说,“没有任何外部库”也将排除 libc,因此您必须自己编写所有系统调用。不过,我怀疑你的意思是那么严格。如果您不想链接到另一个库,并且不想将另一个库中的源代码复制到您的应用程序中,那么使用套接字 API 直接处理 TCP 流是您最好的方法。

创建HTTP请求并通过TCP 套接字连接发送它很容易,就像阅读答案一样。它解析的答案将非常棘手,特别是如果您的目标是支持相当大的标准部分。如果您正在与任意 Web 服务器通信,诸如错误页面、重定向、内容协商等之类的事情会使我们的生活变得非常困难。另一方面,如果已知服务器表现良好,并且任何意外的服务器响应都可以使用简单的错误消息,那么这也相当简单。

于 2012-06-26T13:58:32.670 回答
1

试试 Socket Programming,下面的 C++ 代码向指定主机发出一个简单的 GET 请求并打印响应头和内容

在 Windows 10 中测试

#include <windows.h>
#include <string>
#include <stdio.h>
#include <winsock2.h>

using std::string;

SOCKET conn;
WSADATA wsaData;
struct hostent *hp;
unsigned int addr;
struct sockaddr_in server;
long fileSize;
const int bufSize = 512;
char readBuffer[bufSize], sendBuffer[bufSize], tmpBuffer[bufSize];
char *memBuffer=NULL;
char *headerBuffer=NULL;
long totalBytesRead, thisReadSize, headerLen;
char *tmpResult=NULL, *result;

char* antenna(string host,string path);
SOCKET connectToServer(char *szServerName, WORD portNum);
int getHeaderLength(char *content);


int main(){  
  
    if(WSAStartup(0x101, &wsaData) != 0){printf("startup failure");}
    memBuffer = antenna("www.spreadsheets.google.com", "/feeds/list/{Published_Sheet_ID-1}/1/public/values?alt=json");
    printf("Response content:\n%s\n\n", memBuffer);

    memBuffer = antenna("www.spreadsheets.google.com", "/feeds/list/{Published_Sheet_ID-2}/1/public/values?alt=json");
    printf("Response content:\n%s", memBuffer);

    WSACleanup();
}


char *antenna(string host, string path){

    fileSize=0;
    totalBytesRead=0;
    memBuffer=NULL;
    headerBuffer=NULL;
    tmpResult=NULL,

    conn = connectToServer((char*)host.c_str(), 80);
    if(conn == 0){printf("No Internet connection");}

    sprintf(sendBuffer, "GET %s HTTP/1.0 \r\nHost: %s\r\nConnection: close\r\n\r\n", path.c_str(),host.c_str());
    send(conn, sendBuffer, strlen(sendBuffer), 0);
    printf("Request Format: \n%s",sendBuffer);
    while(1){

        memset(readBuffer, 0, bufSize);
        thisReadSize = recv (conn, readBuffer, bufSize, 0);

        if ( thisReadSize <= 0 ){break;}

        tmpResult = (char*)realloc(tmpResult, thisReadSize+totalBytesRead);

        memcpy(tmpResult+totalBytesRead, readBuffer, thisReadSize);
        totalBytesRead += thisReadSize;
    }

    headerLen = getHeaderLength(tmpResult);
    long contenLen = totalBytesRead-headerLen;
    result = new char[contenLen+1];
    memcpy(result, tmpResult+headerLen, contenLen);
    result[contenLen] = 0x0;
    char *myTmp;
    myTmp = new char[headerLen+1];
    strncpy(myTmp, tmpResult, headerLen);
    myTmp[headerLen] = 0;
    delete(tmpResult);
    headerBuffer = myTmp;

    printf("Response Header: \n%s",headerBuffer);
    fileSize = contenLen;
    closesocket(conn);

    if(fileSize != 0){

        delete(memBuffer);
        delete(headerBuffer);
    }
    return(result);
}

SOCKET connectToServer(char *szServerName, WORD portNum)
{
    conn = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (conn == INVALID_SOCKET){return 0;}
    if(inet_addr(szServerName)==INADDR_NONE){hp=gethostbyname(szServerName);}
    else{
        addr=inet_addr(szServerName);
        hp=gethostbyaddr((char*)&addr,sizeof(addr),AF_INET);
    }

    if(hp==NULL){closesocket(conn);return 0;}

    server.sin_addr.s_addr=*((unsigned long*)hp->h_addr);
    server.sin_family=AF_INET;
    server.sin_port=htons(portNum);
    if(connect(conn,(struct sockaddr*)&server,sizeof(server)))
    {
        closesocket(conn);
        return 0;
    }
    return conn;
}

int getHeaderLength(char *content)
{
    const char *srchStr1 = "\r\n\r\n", *srchStr2 = "\n\r\n\r";
    char *findPos;
    int ofset = -1;

    findPos = strstr(content, srchStr1);
    if (findPos != NULL)
    {
        ofset = findPos - content;
        ofset += strlen(srchStr1);
    }

    else
    {
        findPos = strstr(content, srchStr2);
        if (findPos != NULL)
        {
            ofset = findPos - content;
            ofset += strlen(srchStr2);
        }
    }
    return ofset;
}


编译(使用 g++):

g++ -static test.cpp -o test.exe -lws2_32

-lws2_32 指定与 winsock dll 链接的链接器

于 2021-01-29T05:16:25.373 回答