本文Linux下實(shí)現(xiàn)簡(jiǎn)單的http客戶端請(qǐng)求
HTTP(超文本傳輸協(xié)議)是一種用于在網(wǎng)絡(luò)上進(jìn)行數(shù)據(jù)通信的協(xié)議。HTTP 協(xié)議定義了客戶端和服務(wù)器之間如何交換信息,包括請(qǐng)求和響應(yīng)格式、使用的方法、狀態(tài)碼等。
在 HTTP 協(xié)議中,資源(Resource)指的是由 URL (統(tǒng)一資源定位符)所標(biāo)識(shí)的任意可用于訪問的信息。這些信息可以是各種不同類型的數(shù)據(jù),例如:HTML 頁面、圖片、音頻、視頻等多媒體文件、JavaScript 文件、樣式表文件(CSS)、XML 文件、JSON 數(shù)據(jù)。
要完成一個(gè)完整的 HTTP 網(wǎng)絡(luò)通信過程,需要使用以下一系列函數(shù):
-
socket():創(chuàng)建套接字。
-
connect():連接到指定的服務(wù)器。
-
send():向服務(wù)器發(fā)送請(qǐng)求報(bào)文。
-
listen():監(jiān)聽(多個(gè))io里面有沒有可讀(或可寫)的數(shù)據(jù)
-
recv():從服務(wù)器接收響應(yīng)報(bào)文。
-
close():關(guān)閉套接字,釋放資源。
簡(jiǎn)單的http客戶端請(qǐng)求主要函數(shù)如下,詳細(xì)可看代碼里面的注釋文章來源:http://www.zghlxwxcb.cn/news/detail-565350.html
- char *host_to_ip(const char*hostname):該函數(shù)用于將主機(jī)名(hostname)轉(zhuǎn)換為 IP 地址。
- int http_create_socket(char *ip):該函數(shù)創(chuàng)建一個(gè) TCP 套接字,并連接到指定的 IP 地址和端口號(hào)上。
- char *http_send_request(const char *hostname,const char *resource):該函數(shù)發(fā)送 HTTP 請(qǐng)求并獲取響應(yīng)結(jié)果。
編譯指令文章來源地址http://www.zghlxwxcb.cn/news/detail-565350.html
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"
// /*該函數(shù)用于將主機(jī)名(hostname)轉(zhuǎn)換為 IP 地址。它首先調(diào)用 gethostbyname 函數(shù)
// 獲取主機(jī)名對(duì)應(yīng)的地址信息結(jié)構(gòu)體,然后從中提取出 IP 地址信息,并返回一個(gè)指向字符串類型的 IP 地址。*/
char *host_to_ip(const char*hostname){
/*gethostbyname用于通過主機(jī)名獲取主機(jī)的IP地址信息,
并返回一個(gè)hostent結(jié)構(gòu)體類型的指針,該結(jié)構(gòu)體包含了主機(jī)名、別名和IP地址等信息。*/
struct hostent *host_entry = gethostbyname(hostname);
/*inet_ntoa()函數(shù)是一個(gè)在網(wǎng)絡(luò)編程中常用的函數(shù),它可以將一個(gè)32位整數(shù)IP地址轉(zhuǎn)換成標(biāo)準(zhǔn)的點(diǎn)分十進(jìn)制形式
struct in_addr是一個(gè)C語言中的數(shù)據(jù)結(jié)構(gòu),用于表示32位IPv4地址。*/
if (host_entry){
//h_addr_list 是指向主機(jī)IP地址列表的指針,其類型為 char **
return inet_ntoa(*(struct in_addr*)*host_entry->h_addr_list);
}
return NULL;
}
/*該函數(shù)創(chuàng)建一個(gè) TCP 套接字,并連接到指定的 IP 地址和端口號(hào)上。它首先調(diào)用 socket 函數(shù)創(chuàng)建套接字,
然后調(diào)用 connect 函數(shù)連接到目標(biāo)地址。如果成功則返回套接字文件描述符,否則返回 -1。*/
int http_create_socket(char *ip){
int sockfd=socket(AF_INET,SOCK_STREAM,0);
//sockaddr_in 用于表示 IPv4 地址和端口號(hào)
struct sockaddr_in sin={0}; //
sin.sin_family = AF_INET; //指定 IP 地址的協(xié)議族
sin.sin_port=htons(80); //表示端口號(hào)
sin.sin_addr.s_addr=inet_addr(ip);//表示 IP 地址,inet_addr作用與inte_ntoa相反
/*在網(wǎng)絡(luò)編程中,由于不同協(xié)議族(如IPv4、IPv6等)使用不同的地址結(jié)構(gòu)體,為了保證代碼的通用性和可移植性,
在函數(shù)調(diào)用時(shí)需要將特定協(xié)議族所對(duì)應(yīng)的地址結(jié)構(gòu)體指針轉(zhuǎn)換為通用的 sockaddr 結(jié)構(gòu)體指針。
因此,當(dāng)我們想要將一個(gè)特定協(xié)議族(如IPv4)的地址結(jié)構(gòu)體傳遞給一個(gè)接受 sockaddr 結(jié)構(gòu)體參數(shù)的函數(shù)時(shí),
就需要使用 (struct sockaddr*)&sin 進(jìn)行類型強(qiáng)制轉(zhuǎn)換,以避免編譯器報(bào)錯(cuò)。*/
if ( 0!= connect(sockfd,(struct sockaddr*)&sin,sizeof(struct sockaddr_in))){
return -1;
}
//將 sockfd 文件描述符設(shè)置為非阻塞模式
fcntl(sockfd, F_SETFL, O_NONBLOCK);
return sockfd;
}
/*該函數(shù)發(fā)送 HTTP 請(qǐng)求并獲取響應(yīng)結(jié)果。它首先通過 host_to_ip 函數(shù)將主機(jī)名轉(zhuǎn)換為 IP 地址,
然后使用 http_create_socket 函數(shù)創(chuàng)建套接字并連接到目標(biāo)服務(wù)器。接著構(gòu)建 HTTP 請(qǐng)求報(bào)文,
并使用 send 函數(shù)將其發(fā)送給服務(wù)器。最后等待服務(wù)器響應(yīng)并讀取響應(yīng)結(jié)果。*/
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協(xié)議中使用的行結(jié)束符是 "\r\n" ,每個(gè)字段之間需要用兩個(gè)連續(xù)的回車符和換行符 "\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 協(xié)議進(jìn)行通信或者需要對(duì)多個(gè)目標(biāo)地址進(jìn)行發(fā)送數(shù)據(jù)操作,則應(yīng)該選擇使用 sendto 函數(shù);
如果在使用 TCP 協(xié)議進(jìn)行通信且只涉及一個(gè)目標(biāo)地址,則可以選擇使用 send 函數(shù)。*/
send(sockfd,buffer,strlen(buffer),0);
/*fd_set 是一個(gè)數(shù)據(jù)類型,可以同時(shí)監(jiān)視多個(gè)文件描述符的狀態(tài),比如可讀、可寫或者異常等。
在使用前必須先通過FD_ZERO宏清空,并使用FD_SET來添加或刪除其中的文件描述符*/
fd_set fdread;
FD_ZERO(&fdread);
FD_SET(sockfd,&fdread);
/*timeval 結(jié)構(gòu)體是一個(gè)用于表示時(shí)間的結(jié)構(gòu)體,tv_sec 表示秒數(shù),tv_usec 表示微秒數(shù)*/
struct timeval tv;
tv.tv_sec=5;
tv.tv_usec=0;
char *result=malloc(sizeof(int));
memset(result,0,sizeof(int));
while (1){
/*select函數(shù)可以監(jiān)聽多個(gè)文件描述符(包括Socket),并等待其中任意一個(gè)或多個(gè)文件描述符上有I/O事件發(fā)生時(shí)通知調(diào)用者
通過使用select函數(shù),程序可以同時(shí)監(jiān)聽多個(gè)I/O事件,并且只要有任何一個(gè)事件發(fā)生了變化就會(huì)被立即通知。
這樣可以大大提高程序的性能和響應(yīng)速度,避免阻塞或死循環(huán)等問題*,具體參數(shù)分別如下:
nfds:需要監(jiān)聽的文件描述符最大值+1。即所有待監(jiān)聽文件描述符中最大的那個(gè)數(shù)加一。
readfds:指向fd_set類型的指針,用于設(shè)置需要檢查可讀性的文件描述符集合。
writefds:指向fd_set類型的指針,用于設(shè)置需要檢查可寫性的文件描述符集合。
exceptfds:指向fd_set類型的指針,用于設(shè)置需要檢查異常條件(錯(cuò)誤)的文件描述符集合。
timeout:等待超時(shí)時(shí)間。如果為NULL,則表示無限等待;如果為0,則表示立即返回。*/
int selection=select(sockfd+1,&fdread,NULL,NULL,&tv);
if( !selection || !FD_ISSET(sockfd,&fdread)){
break;
}
else{
//buffer之前已經(jīng)被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()將指定長(zhǎng)度的字符串追加到目標(biāo)字符串末尾
}
}
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);
}
到了這里,關(guān)于10. Linux下實(shí)現(xiàn)簡(jiǎn)單的http客戶端請(qǐng)求的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!