面試過程中又一個常見的問題,http協議,因為做服務器開發如果用http協議的話,現在各種開源軟件都封裝好了,python中只需要簡單的繼承定義好的類,重寫get或者post等方法,幾行代碼就可以搭建起來一個簡單的http服務器,導致底層對程序員來說都是透明的了。但是面試中追求這個底層的問題還不少,所以最近入手了一本據說一天入門http協議的書籍《圖解http》閱讀一番,才覺http協議原來是這個樣子~這里總結一下自己的學習心得吧。
現在理解http協議其實只是一些規定好的通信標准,其實發送的均為字符串,去處理字符串即可。(不知道這句話能不能理解,后面會再次解釋一下)。
HTTP協議在TCP/IP協議棧中屬於應用層協議,如果基於SSL或者TSL,這個協議就演變為更加安全的HTTPS協議。默認HTTP端口號80,HTTPS默認端口號443。
與HTTP協議協同工作的重要協議DNS,TCP,IP等協議,其中DNS同為應用層協議。
HTTP協議規定,請求從客戶端發出,最后服務器端響應該請求並返回。這是目前HTTP協議的規定,服務器不支持主動響應,所以目前的HTTP協議版本都是基於請求,然后響應的這種模型。
另外,HTTP協議是一種無狀態的應用層協議,即使同一個客戶端的兩次連續請求,在協議規定中也沒有對應關系。所以為了解決無狀態的這個問題,存在其他的技術解決方案進行補充。
HTTP請求報文和響應報文:
請求報文: 請求方法 , 請求URI , 協議版本 , 可選的請求首部字段和內容實體。(嚴格定義的字符串)
響應報文:協議版本 , 狀態碼 , 用以解釋狀態碼的原因短語 , 可選的響應首部字段以及實體主體。(嚴格定義的字符串)
以上兩個截圖自己利用socket寫了一個簡單的服務端和客戶端程序,分別用瀏覽器發送請求和向百度發送請求得到的字符串打印。HTTP具體還是基於TCP協議的,所以各種HTTP服務器底層應該是同樣的機制,不過它們處理了很多關於HTTP協議處理的部分,這部分更多的感覺是協議規定好的字符串的內容,各種方法,各種頭的屬性。
HTTP請求,響應的模擬
另外除了自己利用socket去編輯,更簡單觀察相應的方法可以利用telnet去發送http請求頭至百度。
telenet 開啟方法 控制面板-程序-window程序開啟與關閉-開啟telnet客戶端,這樣就能夠在cmd中使用telnet命令了。使用起來不是很方便。
異或是利用wireshark直接去抓取你訪問某個網站整個過程的數據包也可以觀察~。以下是抓取的請求www.baidu.com的數據包。
整個請求,響應過程包過濾后如下:
其中可以可到tcp的三次握手,以及請求,響應的過程。但是其中有幾個包內容是TCP segment of a reassembled PDU不是很清楚,可以看到,tcp每次發送均有響應的ACK回應。具體的TCP得參考TCP協議的知識。
另外HTTP1.0和HTTP1.1的有一個主要區別就是HTTP1.1引入了持續連接的概念,不然完成一次請求必然導致TCP三次握手成本太高,引入持續連接的概念提高傳輸的效率。
HTTP請求支持的方法
GET 請求獲取Request-URI所標識的資源
POST 在Request-URI所標識的資源后附加新的數據
HEAD 請求獲取由Request-URI所標識的資源的響應消息報頭
PUT 請求服務器存儲一個資源,並用Request-URI作為其標識
DELETE 請求服務器刪除Request-URI所標識的資源
OPTIONS 請求查詢服務器的性能,或者查詢與資源相關的選項和需求
TRACE 請求服務器回送收到的請求信息,主要用於測試或診斷
CONNECT 隧道機制
HTTP的狀態響應碼
1XX:指示信息,請求收到,繼續處理
2XX:成功,操作成功收到,分析,接受
3XX:完成請求必須進一步處理,重定向
4XX:請求包含一個錯誤語法,不能完成。指示客戶端錯誤
5XX:服務器執行一個有效請求失敗,指示服務端錯誤
其他HTTP重要的知識就是報頭信息中的具體字段信息:
報頭字段的格式為 (名字)“:”(空格)(值);
通用報頭字段:
Cache-Control
Date
Connection
請求報頭字段:
Accept
Accept-Charset
Accept-Encoding
Accept-Language
Authorization
Host
User-Agent
響應報頭字段:
Location
Server
實體報頭字段:
Content-Encoding
Content-Language
Content-Length
Content-Type
Last-Modified
Expires
最后貼出自己利用winsock實現的測試http內容的簡單程序:
實現流程圖如下:
//TCP 客戶端 #include <iostream> #include <stdio.h> #include <stdlib.h> #include <windows.h> #include <string.h> #include <string> using namespace std; #pragma comment(lib,"ws2_32.lib") #define PORT 8080 #define BACKLOG 10 int main(void) { //client module WSADATA r_wsadata; if( WSAStartup(MAKEWORD(2,2),&r_wsadata) ) { cout<<"WSAStartup error"<<endl; WSACleanup(); } SOCKET client_s; if( (client_s = socket(AF_INET,SOCK_STREAM,0)) == INVALID_SOCKET ) { cout<<"socket error"<<endl; WSACleanup(); } sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_port = htons(80); server_addr.sin_addr.s_addr = inet_addr("202.108.22.5"); if( connect(client_s,(sockaddr *)&server_addr,sizeof(server_addr)) ) { cout<<"connect error"<<endl; WSACleanup(); } char *sbuf ="GET / HTTP/1.1\r\nHost:www.baidu.com\r\nUser-Agent: Mozilla/5.0 (Windows NT 5.1; rv:10.0.2) Gecko/20100101 Firefox/10.0.2\r\nAccept-Language: zh-cn,zh;q=0.5\r\nAccept-Encoding: gzip, deflate\r\nConnection:Keep-Alive\r\n\r\n"; char *sbuf1 ="GET / HTTP/1.1\r\nHost:www.baidu.com\r\nUser-Agent:Mozilla/5.0 (Windows;U;Windows NT 5.1; en-US; rv:1.7.6)\r\nGecko/20050225 Firefox/1.0.1\r\nConnection: Keep Alive\r\n\r\n"; int len = strlen(sbuf) + 1; int sendlen = 0; int res = send(client_s,sbuf,len,0); char buf[10240]; int recvlen = recv(client_s,buf,sizeof(buf),0); if(recvlen > 0) buf[recvlen] = '\0'; cout<<string(buf)<<endl; system("pause"); }
//TCP //服務器端程序 #include <iostream> #include <stdio.h> #include <stdlib.h> #include <windows.h> #include <string.h> #include <string> using namespace std; #pragma comment(lib,"ws2_32.lib") #define PORT 8080 #define BACKLOG 10 int main(void) { //server module WSADATA r_wsadata; if( WSAStartup(MAKEWORD(2,2),&r_wsadata) ) { cout<<"WSAStartup error"<<endl; WSACleanup(); exit(0); } SOCKET server_s; if( ( server_s = socket(AF_INET,SOCK_STREAM,0) ) == INVALID_SOCKET ) { cout<<"socket error"<<endl; WSACleanup(); exit(0); } sockaddr_in server_s_addr; server_s_addr.sin_family = AF_INET; server_s_addr.sin_port = htons(PORT); server_s_addr.sin_addr.s_addr = INADDR_ANY; if( bind(server_s,(sockaddr *)&server_s_addr,sizeof(server_s_addr)) ) { cout<<"bind error"<<endl; WSACleanup(); exit(0); } if( listen(server_s,BACKLOG) ) { cout<<"listen error"<<endl; WSACleanup(); exit(0); } while(true) { SOCKET accept_socket; sockaddr_in client_s_addr; int addr_len = sizeof(client_s_addr); if( (accept_socket = accept(server_s,(sockaddr *)&client_s_addr,&addr_len)) == -1 ) { cout<<"accept error"<<endl; WSACleanup(); exit(0); } cout<<"request from host:"<<string( inet_ntoa(client_s_addr.sin_addr) )<<endl; cout<<"-----------------------http request----------------------"<<endl; //receive char buf[1024]; int readLen = -1; while(readLen < 0) readLen = recv(accept_socket,buf,sizeof(buf),0); buf[readLen] = '\0'; cout<<string(buf)<<endl; closesocket(accept_socket); } }