10. Linux下实现简单的http客户端请求

本文Linux下实现简单的http客户端请求

HTTP(超文本传输协议)是一种用于在网络上进行数据通信的协议。HTTP 协议定义了客户端和服务器之间如何交换信息,包括请求和响应格式、使用的方法、状态码等。
在 HTTP 协议中,资源(Resource)指的是由 URL (统一资源定位符)所标识的任意可用于访问的信息。这些信息可以是各种不同类型的数据,例如:HTML 页面、图片、音频、视频等多媒体文件、JavaScript 文件、样式表文件(CSS)、XML 文件、JSON 数据。
要完成一个完整的 HTTP 网络通信过程,需要使用以下一系列函数

  1. socket():创建套接字。

  2. connect():连接到指定的服务器。

  3. send():向服务器发送请求报文。

  4. listen():监听(多个)io里面有没有可读(或可写)的数据

  5. recv():从服务器接收响应报文。

  6. close():关闭套接字,释放资源。

简单的http客户端请求主要函数如下,详细可看代码里面的注释

  • char *host_to_ip(const char*hostname):该函数用于将主机名(hostname)转换为 IP 地址。
  • int http_create_socket(char *ip):该函数创建一个 TCP 套接字,并连接到指定的 IP 地址和端口号上。
  • char *http_send_request(const char *hostname,const char *resource):该函数发送 HTTP 请求并获取响应结果。

编译指令

zxmgcc -o http http.c
./http www.baidu.com /
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>

//Linux下的头文件
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
#include <fcntl.h>


#define BUFFER_SIZE     4096
#define HTTP_VERSION    "HTTP/1.1"
#define CONNETION_TYPE "Connection: close\r\n"

// /*该函数用于将主机名(hostname)转换为 IP 地址。它首先调用 gethostbyname 函数
// 获取主机名对应的地址信息结构体,然后从中提取出 IP 地址信息,并返回一个指向字符串类型的 IP 地址。*/
char *host_to_ip(const char*hostname){
    /*gethostbyname用于通过主机名获取主机的IP地址信息,
    并返回一个hostent结构体类型的指针,该结构体包含了主机名、别名和IP地址等信息。*/
    struct hostent *host_entry = gethostbyname(hostname);

    /*inet_ntoa()函数是一个在网络编程中常用的函数,它可以将一个32位整数IP地址转换成标准的点分十进制形式
    struct in_addr是一个C语言中的数据结构,用于表示32位IPv4地址。*/
    if (host_entry){
        //h_addr_list 是指向主机IP地址列表的指针,其类型为 char **
        return inet_ntoa(*(struct in_addr*)*host_entry->h_addr_list);
    }
    return NULL;
}

/*该函数创建一个 TCP 套接字,并连接到指定的 IP 地址和端口号上。它首先调用 socket 函数创建套接字,
然后调用 connect 函数连接到目标地址。如果成功则返回套接字文件描述符,否则返回 -1。*/
int http_create_socket(char *ip){
    int sockfd=socket(AF_INET,SOCK_STREAM,0);

    //sockaddr_in 用于表示 IPv4 地址和端口号
    struct sockaddr_in sin={0}; //
    sin.sin_family = AF_INET;   //指定 IP 地址的协议族
    sin.sin_port=htons(80);  //表示端口号
    sin.sin_addr.s_addr=inet_addr(ip);//表示 IP 地址,inet_addr作用与inte_ntoa相反

    /*在网络编程中,由于不同协议族(如IPv4、IPv6等)使用不同的地址结构体,为了保证代码的通用性和可移植性,
    在函数调用时需要将特定协议族所对应的地址结构体指针转换为通用的 sockaddr 结构体指针。
    因此,当我们想要将一个特定协议族(如IPv4)的地址结构体传递给一个接受 sockaddr 结构体参数的函数时,
    就需要使用 (struct sockaddr*)&sin 进行类型强制转换,以避免编译器报错。*/
    if ( 0!= connect(sockfd,(struct sockaddr*)&sin,sizeof(struct sockaddr_in))){
        return -1;
    }

    //将 sockfd 文件描述符设置为非阻塞模式
    fcntl(sockfd, F_SETFL, O_NONBLOCK);

    return sockfd;
}

/*该函数发送 HTTP 请求并获取响应结果。它首先通过 host_to_ip 函数将主机名转换为 IP 地址,
然后使用 http_create_socket 函数创建套接字并连接到目标服务器。接着构建 HTTP 请求报文,
并使用 send 函数将其发送给服务器。最后等待服务器响应并读取响应结果。*/
char *http_send_request(const char *hostname,const char *resource){
    char *ip=host_to_ip(hostname);
    int sockfd=http_create_socket(ip);

    char buffer[BUFFER_SIZE]={0};

    //HTTP协议中使用的行结束符是 "\r\n" ,每个字段之间需要用两个连续的回车符和换行符 "\r\n\r\n" 分隔开来
	sprintf(buffer, "GET %s %s\r\nHost: %s\r\n%s\r\n\r\n",
        resource, HTTP_VERSION, hostname, CONNETION_TYPE);


    /*如果在使用 UDP 协议进行通信或者需要对多个目标地址进行发送数据操作,则应该选择使用 sendto 函数;
    如果在使用 TCP 协议进行通信且只涉及一个目标地址,则可以选择使用 send 函数。*/
    send(sockfd,buffer,strlen(buffer),0);


    /*fd_set 是一个数据类型,可以同时监视多个文件描述符的状态,比如可读、可写或者异常等。
    在使用前必须先通过FD_ZERO宏清空,并使用FD_SET来添加或删除其中的文件描述符*/
    fd_set fdread;
    FD_ZERO(&fdread);
    FD_SET(sockfd,&fdread);

    /*timeval 结构体是一个用于表示时间的结构体,tv_sec 表示秒数,tv_usec 表示微秒数*/
    struct timeval tv;
    tv.tv_sec=5;
    tv.tv_usec=0;

    char *result=malloc(sizeof(int));
    memset(result,0,sizeof(int));
    while (1){
        /*select函数可以监听多个文件描述符(包括Socket),并等待其中任意一个或多个文件描述符上有I/O事件发生时通知调用者
        通过使用select函数,程序可以同时监听多个I/O事件,并且只要有任何一个事件发生了变化就会被立即通知。
        这样可以大大提高程序的性能和响应速度,避免阻塞或死循环等问题*,具体参数分别如下:
        nfds:需要监听的文件描述符最大值+1。即所有待监听文件描述符中最大的那个数加一。
        readfds:指向fd_set类型的指针,用于设置需要检查可读性的文件描述符集合。
        writefds:指向fd_set类型的指针,用于设置需要检查可写性的文件描述符集合。
        exceptfds:指向fd_set类型的指针,用于设置需要检查异常条件(错误)的文件描述符集合。
        timeout:等待超时时间。如果为NULL,则表示无限等待;如果为0,则表示立即返回。*/
        int selection=select(sockfd+1,&fdread,NULL,NULL,&tv);
        if( !selection || !FD_ISSET(sockfd,&fdread)){
            break;
        }
        else{
            //buffer之前已经被send之后,需要清空
            memset(buffer,0,BUFFER_SIZE);
            int len=recv(sockfd,buffer,BUFFER_SIZE,0);
            if (len==0){
                break;
            }
            
            result=realloc(result,(strlen(result)+len+1)*sizeof(char));
            strncat(result,buffer,len); //strncat()将指定长度的字符串追加到目标字符串末尾
        }
    }
    return result;
}


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

	if (argc < 3) return -1;

	char *response = http_send_request(argv[1], argv[2]);
	printf("response : %s\n", response);

	free(response);
	
}

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

闲谈社

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值