1:背景介绍
http是基于C/S模式的,一直对如何实现http请求很疑惑。
http的协议理论了解很多,却一直无从下手不知道怎么实现一个http的请求。
作为一个http的客户端,请求服务器的一个消息,实现demo,做代码备份。
1.1:协议简单了解
这里实现一个http的get请求,获取对端服务器的响应进行打印,实现demo
//http请求实际就是按照http协议,构造特定的数据,使用tcp进行发送 //请求行 请求头部 空行 请求数据 // GET %s %s\r\n resource HTTP_VERSION 请求行 // Host: %s\r\n hostname 请求头 // %s\r\n CONNECTION_TYPE 请求头 // \r\n
1.2:关注细节
1.2.1:需要关注hostname转ip的相关方法
把hostname转为目的服务器的具体ip,创建tcp client,进行连接,发送数据得请求。
//转换 url和ip 返回ip char *get_ip_by_host(const char * hostname) { //通过域名获取ip struct hostent *host_entry = gethostbyname(hostname); if(host_entry == NULL) { return NULL; } //通过 struct hostent * 可以获取到域名 ip列表等信息 printf("host name is [%s] \n", host_entry->h_name); printf("the other name is :\n"); for(int i=0; host_entry->h_aliases[i]; ++i) { printf(" [i:%d][%s] \n", i, host_entry->h_aliases[i]); } printf("Address type: %s\n", (host_entry->h_addrtype==AF_INET) ? "AF_INET": "AF_INET6"); printf("address length: %d \n", host_entry->h_length); printf("ip list is :\n"); for(int i=0; host_entry->h_addr_list[i]; ++i) { // printf(" [i:%d][%s] \n", i, host_entry->h_addr_list[i]); printf(" [i:%d][%s] \n", i, inet_ntoa(*(struct in_addr*)host_entry->h_addr_list[i])); } return inet_ntoa(*(struct in_addr*)*host_entry->h_addr_list); }
1.2.2:需要关注如何按照http协议构造要发送的数据
构造发送的字符串,使用tcp进行发送。
#define HTTP_VERSION "HTTP/1.1" //好像不同的浏览器有不同的一些参数 可以在网页用f12请求进行查看 #define USER_AGENT "User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:10.0.2) Gecko/20100101 Firefox/10.0.2\r\n" #define ENCODE_TYPE "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" #define CONNECTION_TYPE "Connection: close\r\n" int len = sprintf(send_buff, "GET %s %s\r\nHost: %s\r\n%s\r\n\r\n",\ resource, HTTP_VERSION, hostname, CONNECTION_TYPE); //send_buff 即为我们对服务器请求的http格式的消息,使用tcp进行发送
2:测试代码
http基于tcp/ip实现的,使用tcp发送特定协议的报文数据而已。
注意按照http的协议格式构造tcp待发送数据,等待对端的回应。 (作为http的客户端,对服务器进行数据请求)
/******************************************************** info: 作为http的客户端,拉取一下远端服务器数据,练习一下 data: 2022/02/10 author: hlp 测试:./XXX www.baidu.com / ./XXX api.seniverse.com /v3/weather/now.json?key=0pyd8z7jouficcil&location=beijing&language=zh-Hans&unit=c ********************************************************/ //练习http的get请求,通过url转换ip,创建tcp,发送构造的http协议,请求服务器的内容 //异步发送和接收 其实是利用了epoll event的指针,指向的函数指针 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/time.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> /* close */ #include <netdb.h> #include <time.h> #include <fcntl.h> #include <errno.h> //转换 url和ip 返回ip char *get_ip_by_host(const char * hostname); //创建socket 根据服务器ip和断开 进行连接 返回fd int create_http_socket(const char * ip); //发送http请求 返回发送成功还是失败 int send_http_request(int fd, const char * hostname, const char* resource); //获取回应消息并打印 监听对端的回应进行打印 返回成功或者失败 int get_recv_http_response(int sockfd); //作为http的客户端,一请求一结束 int main(int argc, char *argv[]) { if(argc < 3) { printf(" please use ./%s hostname resouse \n\n", argv[0]); return -1; } //根据入参,解析获取到的url,保存请求的资源 char * ip = get_ip_by_host(argv[1]); printf("get ip is [%s] \n", ip); //构造tcp连接 作为tcp的客户端,主动连接目标服务器 int connfd = create_http_socket(ip); //构造http报文,发送http请求,并获取到返回结果 send_http_request(connfd, argv[1], argv[2]); //监听获取http的请求的回复 内部做输出查看 get_recv_http_response(connfd); close(connfd); //客户端主动关闭fd return 0; } // struct hostent { // char *h_name; /* 官方域名 */ // char **h_aliases; /* 别名 */ // int h_addrtype; /* 地址簇类型 标明ipv4和ipv6 */ // int h_length; /* ip地址的长度 */ // char **h_addr_list; /* 以整数的形式标识ip */ // } //转换 url和ip 返回ip char *get_ip_by_host(const char * hostname) { //通过域名获取ip struct hostent *host_entry = gethostbyname(hostname); if(host_entry == NULL) { return NULL; } //通过 struct hostent * 可以获取到域名 ip列表等信息 printf("host name is [%s] \n", host_entry->h_name); printf("the other name is :\n"); for(int i=0; host_entry->h_aliases[i]; ++i) { printf(" [i:%d][%s] \n", i, host_entry->h_aliases[i]); } printf("Address type: %s\n", (host_entry->h_addrtype==AF_INET) ? "AF_INET": "AF_INET6"); printf("address length: %d \n", host_entry->h_length); printf("ip list is :\n"); for(int i=0; host_entry->h_addr_list[i]; ++i) { // printf(" [i:%d][%s] \n", i, host_entry->h_addr_list[i]); printf(" [i:%d][%s] \n", i, inet_ntoa(*(struct in_addr*)host_entry->h_addr_list[i])); } return inet_ntoa(*(struct in_addr*)*host_entry->h_addr_list); } //创建socket 根据服务器ip和断开 进行连接 返回fd int create_http_socket(const char * ip) { //创建socket 根据Ip:port连接对端 int sockfd = socket(AF_INET, SOCK_STREAM, 0); if(sockfd < 0) { return -1; } struct sockaddr_in sin = {0}; sin.sin_addr.s_addr = inet_addr(ip); sin.sin_port = htons(80); sin.sin_family = AF_INET; //连接服务器 if (-1 == connect(sockfd, (struct sockaddr*)&sin, sizeof(struct sockaddr_in))) { printf("connect http server error \n"); return -1; } //设置fd非阻塞 fcntl(sockfd, F_SETFL, O_NONBLOCK); return sockfd; } #define HTTP_VERSION "HTTP/1.1" //好像不同的浏览器有不同的一些参数 可以在网页用f12请求进行查看 #define USER_AGENT "User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:10.0.2) Gecko/20100101 Firefox/10.0.2\r\n" #define ENCODE_TYPE "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" #define CONNECTION_TYPE "Connection: close\r\n" #define BUFFER_SIZE 4096 //发送http请求 返回发送成功还是失败 int send_http_request(int fd, const char * hostname, const char* resource) { char send_buff[BUFFER_SIZE] = {0}; //构造发送的请求报文 请求行 请求头部 空行 请求数据 // GET /teacher_6.jpg HTTP/1.1 // Host: www.0voice.com // User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.113 Safari/537.36 // Content-Type: application/x-www-form-urlencoded // Content-Length: 9 // lingsheng //只关注了必须的参数 测试一下 // GET %s %s\r\n resource HTTP_VERSION 请求行 // Host: %s\r\n hostname 请求头 // %s\r\n CONNECTION_TYPE 请求头 // \r\n int len = sprintf(send_buff, "GET %s %s\r\nHost: %s\r\n%s\r\n\r\n",\ resource, HTTP_VERSION, hostname, CONNECTION_TYPE); printf("send request buff is [%lu][%s] \n", strlen(send_buff), send_buff); int buff_len = strlen(send_buff); int sendlen = send(fd, send_buff, strlen(send_buff), 0); if(buff_len != sendlen) { printf("send buffer error \n"); return -1; } return 0; } //获取回应消息并打印 监听对端的回应进行打印 返回成功或者失败 int get_recv_http_response(int sockfd) { //使用io多路复用select对接收 进行输出 struct timeval tv; tv.tv_sec = 5; tv.tv_usec = 0; fd_set fdread; FD_ZERO(&fdread); FD_SET(sockfd, &fdread); //存储最终结果 char *result = malloc(sizeof(int)); result[0] = '\0'; //存储每次recv的数据 int len = 0; char buffer[BUFFER_SIZE] = {0}; while (1) { int selection = select(sockfd+1, &fdread, NULL, NULL, &tv); if (!selection || !(FD_ISSET(sockfd, &fdread))) { break; } else { //这里是客户端 只针对这个连接的场景 len = recv(sockfd, buffer, BUFFER_SIZE, 0); if (len == 0) break; //重新申请大小并对接收到的数据进行追加 result = realloc(result, (strlen(result) + len + 1) * sizeof(char)); strncat(result, buffer, len); } } if(result == NULL) { return -1; } //打印并释放接收到的数据 puts(result); free(result); return 0; }
3:测试结果:
如,请求baidu的主页,有的url是https的,这里暂时只是http的练习,https的相关需要安全认证。
./XXX www.baidu.com /
./XXX api.seniverse.com /v3/weather/now.json?key=0pyd8z7jouficcil&location=beijing&language=zh-Hans&unit=c
hlp@ubuntu:~/220107$ ./get www.baidu.com / ... #这里打印过多 省略 hlp@ubuntu:~/220107$ ./get api.seniverse.com /v3/weather/now.json?key=0pyd8z7jouficcil&location=hefei&language=zh-Hans&unit=c [1] 9103 [2] 9104 [3] 9105 hlp@ubuntu:~/220107$ host name is [api.seniverse.com] the other name is : Address type: AF_INET address length: 4 ip list is : [i:0][116.62.81.138] get ip is [116.62.81.138] send request buff is [104][GET /v3/weather/now.json?key=0pyd8z7jouficcil HTTP/1.1 Host: api.seniverse.com Connection: close ] HTTP/1.1 400 Bad Request Date: Sun, 13 Feb 2022 14:26:26 GMT Content-Type: application/json Content-Length: 55 Connection: close access-control-allow-origin: * x-request-id: FtNerTOXZFLlW0IvMUaB {"status":"Wrong parameters.","status_code":"AP010001"}
我开始试着积累一些常用代码:自己代码库中备用
我的知识储备更多来自这里,推荐你了解:Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,立即学习