網(wǎng)絡(luò)通信的本質(zhì)是兩臺(tái)計(jì)算機(jī)上的兩個(gè)進(jìn)程之間的通信。比如蒿涎,瀏覽器進(jìn)程和新浪服務(wù)器上的某個(gè)Web服務(wù)進(jìn)程在通信哀托,而QQ進(jìn)程是和騰訊的某個(gè)服務(wù)器上的某個(gè)進(jìn)程在通信。
當(dāng)我們?cè)L問新浪的時(shí)候劳秋,發(fā)生了什么仓手?
本地電腦上的一個(gè)進(jìn)程(瀏覽器)向 新浪的服務(wù)器發(fā)起一個(gè)tcp的連接請(qǐng)求。這個(gè)請(qǐng)求的格式是什么玻淑?
下面寫一個(gè)python實(shí)現(xiàn)的例子嗽冒,建立一個(gè)socket,然后連接新浪补履,連接之后添坊,發(fā)送一個(gè)字符串。代碼如下:
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(('www.sina.com.cn',80))
s.send(b'GET / HTTP/1.1\r\nHost: www.sina.com.cn\r\nConnection: close\r\n\r\n')
當(dāng)建立連接之后箫锤,本地進(jìn)程向新浪的服務(wù)器發(fā)送的消息的格式是上面這段代碼贬蛙。
GET / HTTP/1.1\r\nHost: www.sina.com.cn\r\nConnection: close\r\n\r\n
這個(gè)字符串,其實(shí)就是http協(xié)議的 request請(qǐng)求谚攒。
下面討論的是http協(xié)議的格式:
http協(xié)議分成兩個(gè)大的部分阳准,一個(gè)是請(qǐng)求,一個(gè)是相應(yīng)馏臭。無論是請(qǐng)求還是相應(yīng)都包含兩個(gè)部分野蝇,一個(gè)是header,另外一個(gè)是body位喂。(body是可選 的)
HTTP GET請(qǐng)求的格式:
GET /path HTTP/1.1
Header1: Value1
Header2: Value2
Header3: Value3
注意:每個(gè)Header一行一個(gè)浪耘,換行符是\r\n。
HTTP POST請(qǐng)求的格式:
POST /path HTTP/1.1
Header1: Value1
Header2: Value2
Header3: Value3
body data goes here...
注意:當(dāng)遇到連續(xù)兩個(gè)\r\n時(shí)塑崖,Header部分結(jié)束七冲,后面的數(shù)據(jù)全部是Body。
HTTP響應(yīng)的格式:
200 OK
Header1: Value1
Header2: Value2
Header3: Value3
body data goes here...
再次注意:HTTP響應(yīng)如果包含body规婆,也是通過\r\n\r\n來分隔的澜躺。
請(qǐng)?jiān)俅巫⒁猓珺ody的數(shù)據(jù)類型由Content-Type頭來確定抒蚜,如果是網(wǎng)頁掘鄙,Body就是文本,如果是圖片嗡髓,Body就是圖片的二進(jìn)制數(shù)據(jù)操漠。
通過上面的描述,利用socket寫一個(gè)小的demo,理解一下http協(xié)議
思路:在本地創(chuàng)建一個(gè)socket浊伙,向新浪的服務(wù)器發(fā)起連接撞秋,然后偽造一個(gè)request請(qǐng)求。請(qǐng)求如下:
GET / HTTP/1.1\r\nHost: www.sina.com.cn\r\nConnection: close\r\n\r\n
執(zhí)行如下代碼:
#coding:utf-8
import socket
#創(chuàng)建tcp socket
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#建立鏈接
s.connect(('www.sina.com.cn',80))
s.send(b'GET / HTTP/1.1\r\nHost: www.sina.com.cn\r\nConnection: close\r\n\r\n')
#創(chuàng)建一個(gè)buff等待接受
buffer=[]
while True:
d=s.recv(1024)
if d:
buffer.append(d)
else:
break;
#把接受緩存的數(shù)據(jù)都保存到data
data = b''.join(buffer)
print (data)
#斷開socket
s.close()
#把網(wǎng)頁的header和body分離
header, html = data.split(b'\r\n\r\n', 1)
print(header.decode('utf-8'))
# 把接收的數(shù)據(jù)寫入文件:
with open('sina.html', 'wb') as f:
f.write(html)
運(yùn)行結(jié)果:
PS:
HTTP之狀態(tài)碼
狀態(tài)代碼有三位數(shù)字組成嚣鄙,第一個(gè)數(shù)字定義了響應(yīng)的類別吻贿,共分五種類別:
1xx:指示信息--表示請(qǐng)求已接收,繼續(xù)處理
2xx:成功--表示請(qǐng)求已被成功接收哑子、理解舅列、接受
3xx:重定向--要完成請(qǐng)求必須進(jìn)行更進(jìn)一步的操作
4xx:客戶端錯(cuò)誤--請(qǐng)求有語法錯(cuò)誤或請(qǐng)求無法實(shí)現(xiàn)
5xx:服務(wù)器端錯(cuò)誤--服務(wù)器未能實(shí)現(xiàn)合法的請(qǐng)求
PPS:
常見狀態(tài)碼:
200 OK //客戶端請(qǐng)求成功
400 Bad Request //客戶端請(qǐng)求有語法錯(cuò)誤,不能被服務(wù)器所理解
401 Unauthorized //請(qǐng)求未經(jīng)授權(quán)帐要,這個(gè)狀態(tài)代碼必須和WWW-Authenticate報(bào)頭域一起使用
403 Forbidden //服務(wù)器收到請(qǐng)求弥奸,但是拒絕提供服務(wù)
404 Not Found //請(qǐng)求資源不存在,eg:輸入了錯(cuò)誤的URL
500 Internal Server Error //服務(wù)器發(fā)生不可預(yù)期的錯(cuò)誤
503 Server Unavailable //服務(wù)器當(dāng)前不能處理客戶端的請(qǐng)求其爵,一段時(shí)間后可能恢復(fù)正常
PPPS: 補(bǔ)充一個(gè)小例子冒冬,提供一個(gè)掉坑的例子
打算寫一個(gè)模擬并發(fā)請(qǐng)求的壓力測試demo,核心的思路就是多進(jìn)程+每個(gè)進(jìn)程發(fā)送http請(qǐng)求摩渺。要做到不錯(cuò)的性能,打算用c去寫横侦。
問題是這樣的,在構(gòu)建http請(qǐng)求的時(shí)候绰姻,
char buf[1500];
strcpy(request,"GET / HTTP/1.0");
strcat(request,"\r\n");
strcat(request,"User-Agent: WebBench 1.5");
strcat(request,"\r\n");
strcat(request,"Host: localhost");
strcat(request,"\r\n");
//bug 出現(xiàn)在這里枉侧,剛開始沒有加上這一行。http get請(qǐng)求每一行是通過\r\n 來換行的
//結(jié)尾的標(biāo)識(shí)是通過兩個(gè)\r\r 來表示狂芋,但是第一次的時(shí)候,我只寫了一個(gè)翼虫。
//但是把請(qǐng)求打印出來屡萤,是看不來少了一個(gè)\r\n的,一通好找死陆,找不到bug
//最后,我測試用的服務(wù)器是nginx,去看nginx的log
//看到一個(gè)log里面的狀態(tài)碼是400劈愚,400對(duì)應(yīng)的是 請(qǐng)求無效闻妓,然后就執(zhí)行的查請(qǐng)求這個(gè)掠械,最夠終于找到這個(gè)bug
strcat(request,"\r\n");
int rlen=strlen(request);
我把源碼貼在這里猾蒂,感興趣的可以復(fù)盤一下問題
main.c
#include "socket.c"
#include <unistd.h>
#include <sys/param.h>
#include <rpc/types.h>
#include <getopt.h>
#include <strings.h>
#include <time.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#define REQUEST_SIZE 2048
char request[REQUEST_SIZE]; // 發(fā)送的構(gòu)造的HTTP請(qǐng)求
int main(){
char buf[1500];
strcpy(request,"GET / HTTP/1.0");
strcat(request,"\r\n");
strcat(request,"User-Agent: WebBench 1.5");
strcat(request,"\r\n");
strcat(request,"Host: localhost");
strcat(request,"\r\n");
strcat(request,"\r\n");
int rlen=strlen(request);
printf("----test ----- the http request is ---- : \n");
printf("%s",request);
printf("----end ------\n");
char *host="localhost";
int port=80;
int s=Socket(host,port);
if(s<0){
printf("error \n");
return -1;
}
else{
printf("ok \n");
}
//write
if(rlen!=write(s,request,rlen)){
printf("fail \n");
close(s);
return -1;
}
printf("write len is %d",rlen);
//read
int i=0;
while(1){
i=read(s,buf,1500);
printf("len i is : %d",i);
if(i<0){
printf("fail \n");
close(s);
return -1;
}
if(i==0){
printf("%s",buf);
printf("read comlete \n");
break;
}
else{
printf("%s",buf);
}
}
close(s);
return 0;
}
socket.c
#include <sys/types.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/time.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
int Socket(const char *host, int clientPort)
{
int sock;
unsigned long inaddr;
struct sockaddr_in ad;
struct hostent *hp;
memset(&ad, 0, sizeof(ad));
ad.sin_family = AF_INET;
// 將字符串轉(zhuǎn)換為32位二進(jìn)制網(wǎng)絡(luò)字節(jié)序的IPv4地址
inaddr = inet_addr(host);
if (inaddr != INADDR_NONE)
memcpy(&ad.sin_addr, &inaddr, sizeof(inaddr));
else
{
// 使用域名或主機(jī)名獲取ip地址
hp = gethostbyname(host);
if (hp == NULL)
return -1;
memcpy(&ad.sin_addr, hp->h_addr, hp->h_length);
}
ad.sin_port = htons(clientPort);
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0)
return sock;
if (connect(sock, (struct sockaddr *)&ad, sizeof(ad)) < 0)
return -1;
return sock;
}