面試過程中又一個常見的問題,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);
}
}
