oracle 监听程序当前无法识别连接描述符中请求的服务_最新版Web服务器项目详解 04 http连接处理(上)...
點 擊 關 注 上?方"兩猿社"
設 為"置 頂 或 星 標",干 貨 第 一?時 間 送 達。
互 聯 網 猿 | 兩 猿 社
本文內容
在服務器項目中,http請求的處理與響應至關重要,關系到用戶界面的跳轉與反饋。這里,社長將其分為上、中、下三個部分來講解,具體的:
上篇,梳理基礎知識,結合代碼分析http類及請求接收
中篇,結合代碼分析請求報文解析
下篇,結合代碼分析請求報文響應
基礎知識方面,包括epoll、HTTP報文格式、狀態碼和有限狀態機。
代碼分析方面,首先對服務器端處理http請求的全部流程進行簡要介紹,然后結合代碼對http類及請求接收進行詳細分析。
epoll
epoll涉及的知識較多,這里僅對API和基礎知識作介紹。更多資料請查閱資料,或查閱游雙的Linux高性能服務器編程 第9章 I/O復用
epoll_create函數
1#include?2int?epoll_create(int?size)3
創建一個指示epoll內核事件表的文件描述符,該描述符將用作其他epoll系統調用的第一個參數,size不起作用。
epoll_ctl函數
1#include?2int?epoll_ctl(int?epfd,?int?op,?int?fd,?struct?epoll_event?*event)3
該函數用于操作內核事件表監控的文件描述符上的事件:注冊、修改、刪除
epfd:為epoll_creat的句柄
op:表示動作,用3個宏來表示:
EPOLL_CTL_ADD (注冊新的fd到epfd),
EPOLL_CTL_MOD (修改已經注冊的fd的監聽事件),
EPOLL_CTL_DEL (從epfd刪除一個fd);
event:告訴內核需要監聽的事件
上述event是epoll_event結構體指針類型,表示內核所監聽的事件,具體定義如下:
1struct?epoll_event?{2__uint32_t?events;?/*?Epoll?events?*/
3epoll_data_t?data;?/*?User?data?variable?*/
4};
events描述事件類型,其中epoll事件類型有以下幾種
EPOLLIN:表示對應的文件描述符可以讀(包括對端SOCKET正常關閉)
EPOLLOUT:表示對應的文件描述符可以寫
EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這里應該表示有帶外數據到來)
EPOLLERR:表示對應的文件描述符發生錯誤
EPOLLHUP:表示對應的文件描述符被掛斷;
EPOLLET:將EPOLL設為邊緣觸發(Edge Triggered)模式,這是相對于水平觸發(Level Triggered)而言的
EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之后,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列里
epoll_wait函數
1#include?2int?epoll_wait(int?epfd,?struct?epoll_event?*events,?int?maxevents,?int?timeout)3
該函數用于等待所監控文件描述符上有事件的產生,返回就緒的文件描述符個數
events:用來存內核得到事件的集合,
maxevents:告之內核這個events有多大,這個maxevents的值不能大于創建epoll_create()時的size,
timeout:是超時時間
-1:阻塞
0:立即返回,非阻塞
>0:指定毫秒
返回值:成功返回有多少文件描述符就緒,時間到時返回0,出錯返回-1
select/poll/epoll
調用函數
select和poll都是一個函數,epoll是一組函數
文件描述符數量
select通過線性表描述文件描述符集合,文件描述符有上限,一般是1024,但可以修改源碼,重新編譯內核,不推薦
poll是鏈表描述,突破了文件描述符上限,最大可以打開文件的數目
epoll通過紅黑樹描述,最大可以打開文件的數目,可以通過命令ulimit -n number修改,僅對當前終端有效
將文件描述符從用戶傳給內核
select和poll通過將所有文件描述符拷貝到內核態,每次調用都需要拷貝
epoll通過epoll_create建立一棵紅黑樹,通過epoll_ctl將要監聽的文件描述符注冊到紅黑樹上
內核判斷就緒的文件描述符
select和poll通過遍歷文件描述符集合,判斷哪個文件描述符上有事件發生
epoll_create時,內核除了幫我們在epoll文件系統里建了個紅黑樹用于存儲以后epoll_ctl傳來的fd外,還會再建立一個list鏈表,用于存儲準備就緒的事件,當epoll_wait調用時,僅僅觀察這個list鏈表里有沒有數據即可。
epoll是根據每個fd上面的回調函數(中斷函數)判斷,只有發生了事件的socket才會主動的去調用 callback函數,其他空閑狀態socket則不會,若是就緒事件,插入list
應用程序索引就緒文件描述符
select/poll只返回發生了事件的文件描述符的個數,若知道是哪個發生了事件,同樣需要遍歷
epoll返回的發生了事件的個數和結構體數組,結構體包含socket的信息,因此直接處理返回的數組即可
工作模式
select和poll都只能工作在相對低效的LT模式下
epoll則可以工作在ET高效模式,并且epoll還支持EPOLLONESHOT事件,該事件能進一步減少可讀、可寫和異常事件被觸發的次數。?
應用場景
當所有的fd都是活躍連接,使用epoll,需要建立文件系統,紅黑書和鏈表對于此來說,效率反而不高,不如selece和poll
當監測的fd數目較小,且各個fd都比較活躍,建議使用select或者poll
當監測的fd數目非常大,成千上萬,且單位時間只有其中的一部分fd處于就緒狀態,這個時候使用epoll能夠明顯提升性能
ET、LT、EPOLLONESHOT
LT水平觸發模式
epoll_wait檢測到文件描述符有事件發生,則將其通知給應用程序,應用程序可以不立即處理該事件。
當下一次調用epoll_wait時,epoll_wait還會再次向應用程序報告此事件,直至被處理
ET邊緣觸發模式
epoll_wait檢測到文件描述符有事件發生,則將其通知給應用程序,應用程序必須立即處理該事件
必須要一次性將數據讀取完,使用非阻塞I/O,讀取到出現eagain
EPOLLONESHOT
一個線程讀取某個socket上的數據后開始處理數據,在處理過程中該socket上又有新數據可讀,此時另一個線程被喚醒讀取,此時出現兩個線程處理同一個socket
我們期望的是一個socket連接在任一時刻都只被一個線程處理,通過epoll_ctl對該文件描述符注冊epolloneshot事件,一個線程處理socket時,其他線程將無法處理,當該線程處理完后,需要通過epoll_ctl重置epolloneshot事件
HTTP報文格式
HTTP報文分為請求報文和響應報文兩種,每種報文必須按照特有格式生成,才能被瀏覽器端識別。
其中,瀏覽器端向服務器發送的為請求報文,服務器處理后返回給瀏覽器端的為響應報文。
請求報文
HTTP請求報文由請求行(request line)、請求頭部(header)、空行和請求數據四個部分組成。
其中,請求分為兩種,GET和POST,具體的:
GET
2????Host:img.mukewang.com
3????User-Agent:Mozilla/5.0?(Windows?NT?10.0;?WOW64)
4????AppleWebKit/537.36?(KHTML,?like?Gecko)?Chrome/51.0.2704.106?Safari/537.36
5????Accept:image/webp,image/*,*/*;q=0.8
6????Referer:http://www.imooc.com/
7????Accept-Encoding:gzip,?deflate,?sdch
8????Accept-Language:zh-CN,zh;q=0.8
9????空行
10????請求數據為空
POST
2????Host:www.wrox.com
3????User-Agent:Mozilla/4.0?(compatible;?MSIE?6.0;?Windows?NT?5.1;?SV1;?.NET?CLR?2.0.50727;?.NET?CLR?3.0.04506.648;?.NET?CLR?3.5.21022)
4????Content-Type:application/x-www-form-urlencoded
5????Content-Length:40
6????Connection:?Keep-Alive
7????空行
8????name=Professional%20Ajax&publisher=Wiley
請求行,用來說明請求類型,要訪問的資源以及所使用的HTTP版本。
GET說明請求類型為GET,/562f25980001b1b106000338.jpg(URL)為要訪問的資源,該行的最后一部分說明使用的是HTTP1.1版本。請求頭部,緊接著請求行(即第一行)之后的部分,用來說明服務器要使用的附加信息。
HOST,給出請求資源所在服務器的域名。
User-Agent,HTTP客戶端程序的信息,該信息由你發出請求使用的瀏覽器來定義,并且在每個請求中自動發送等。
Accept,說明用戶代理可處理的媒體類型。
Accept-Encoding,說明用戶代理支持的內容編碼。
Accept-Language,說明用戶代理能夠處理的自然語言集。
Content-Type,說明實現主體的媒體類型。
Content-Length,說明實現主體的大小。
Connection,連接管理,可以是Keep-Alive或close。
空行,請求頭部后面的空行是必須的即使第四部分的請求數據為空,也必須有空行。
請求數據也叫主體,可以添加任意的其他數據。
響應報文
HTTP響應也由四個部分組成,分別是:狀態行、消息報頭、空行和響應正文。
1HTTP/1.1?200?OK2Date:?Fri,?22?May?2009?06:07:21?GMT
3Content-Type:?text/html;?charset=UTF-8
4空行
5
6??????
7??????
8????????????
9??????
10
狀態行,由HTTP協議版本號, 狀態碼, 狀態消息 三部分組成。
第一行為狀態行,(HTTP/1.1)表明HTTP版本為1.1版本,狀態碼為200,狀態消息為OK。消息報頭,用來說明客戶端要使用的一些附加信息。
第二行和第三行為消息報頭,Date:生成響應的日期和時間;Content-Type:指定了MIME類型的HTML(text/html),編碼類型是UTF-8。空行,消息報頭后面的空行是必須的。
響應正文,服務器返回給客戶端的文本信息。空行后面的html部分為響應正文。
HTTP狀態碼
HTTP有5種類型的狀態碼,具體的:
1xx:指示信息--表示請求已接收,繼續處理。
2xx:成功--表示請求正常處理完畢。
200 OK:客戶端請求被正常處理。
206 Partial content:客戶端進行了范圍請求。
3xx:重定向--要完成請求必須進行更進一步的操作。
301 Moved Permanently:永久重定向,該資源已被永久移動到新位置,將來任何對該資源的訪問都要使用本響應返回的若干個URI之一。
302 Found:臨時重定向,請求的資源現在臨時從不同的URI中獲得。
4xx:客戶端錯誤--請求有語法錯誤,服務器無法處理請求。
400 Bad Request:請求報文存在語法錯誤。
403 Forbidden:請求被服務器拒絕。
404 Not Found:請求不存在,服務器上找不到請求的資源。
5xx:服務器端錯誤--服務器處理請求出錯。
500 Internal Server Error:服務器在執行請求時出現錯誤。
有限狀態機
有限狀態機,是一種抽象的理論模型,它能夠把有限個變量描述的狀態變化過程,以可構造可驗證的方式呈現出來。比如,封閉的有向圖。
有限狀態機可以通過if-else,switch-case和函數指針來實現,從軟件工程的角度看,主要是為了封裝邏輯。
帶有狀態轉移的有限狀態機示例代碼。
1STATE_MACHINE(){2????State?cur_State?=?type_A;
3????while(cur_State?!=?type_C){
4????????Package?_pack?=?getNewPackage();
5????????switch(){
6????????????case?type_A:
7????????????????process_pkg_state_A(_pack);
8????????????????cur_State?=?type_B;
9????????????????break;
10????????????case?type_B:
11????????????????process_pkg_state_B(_pack);
12????????????????cur_State?=?type_C;
13????????????????break;
14????????}
15????}
16}
該狀態機包含三種狀態:type_A,type_B和type_C。其中,type_A是初始狀態,type_C是結束狀態。
狀態機的當前狀態記錄在cur_State變量中,邏輯處理時,狀態機先通過getNewPackage獲取數據包,然后根據當前狀態對數據進行處理,處理完后,狀態機通過改變cur_State完成狀態轉移。
有限狀態機一種邏輯單元內部的一種高效編程方法,在服務器編程中,服務器可以根據不同狀態或者消息類型進行相應的處理邏輯,使得程序邏輯清晰易懂。
http處理流程
首先對http報文處理的流程進行簡要介紹,然后具體介紹http類的定義和服務器接收http請求的具體過程。
http報文處理流程
瀏覽器端發出http連接請求,主線程創建http對象接收請求并將所有數據讀入對應buffer,將該對象插入任務隊列,工作線程從任務隊列中取出一個任務進行處理。(本篇講)
工作線程取出任務后,調用process_read函數,通過主、從狀態機對請求報文進行解析。(中篇講)
解析完之后,跳轉do_request函數生成響應報文,通過process_write寫入buffer,返回給瀏覽器端。(下篇講)
http類
這一部分代碼在TinyWebServer/http/http_conn.h中,主要是http類的定義。
1class?http_conn{2????public:
3????????//設置讀取文件的名稱m_real_file大小
4????????static?const?int?FILENAME_LEN=200;
5????????//設置讀緩沖區m_read_buf大小
6????????static?const?int?READ_BUFFER_SIZE=2048;
7????????//設置寫緩沖區m_write_buf大小
8????????static?const?int?WRITE_BUFFER_SIZE=1024;
9????????//報文的請求方法,本項目只用到GET和POST
10????????enum?METHOD{GET=0,POST,HEAD,PUT,DELETE,TRACE,OPTIONS,CONNECT,PATH};
11????????//主狀態機的狀態
12????????enum?CHECK_STATE{CHECK_STATE_REQUESTLINE=0,CHECK_STATE_HEADER,CHECK_STATE_CONTENT};
13????????//報文解析的結果
14????????enum?HTTP_CODE{NO_REQUEST,GET_REQUEST,BAD_REQUEST,NO_RESOURCE,FORBIDDEN_REQUEST,FILE_REQUEST,INTERNAL_ERROR,CLOSED_CONNECTION};
15????????//從狀態機的狀態
16????????enum?LINE_STATUS{LINE_OK=0,LINE_BAD,LINE_OPEN};
17
18????public:
19????????http_conn(){}
20????????~http_conn(){}
21
22????public:
23????????//初始化套接字地址,函數內部會調用私有方法init
24????????void?init(int?sockfd,const?sockaddr_in?&addr);
25????????//關閉http連接
26????????void?close_conn(bool?real_close=true);
27????????void?process();
28????????//讀取瀏覽器端發來的全部數據
29????????bool?read_once();
30????????//響應報文寫入函數
31????????bool?write();
32????????sockaddr_in?*get_address(){
33????????????return?&m_address;??
34????????}
35????????//同步線程初始化數據庫讀取表
36????????void?initmysql_result();
37????????//CGI使用線程池初始化數據庫表
38????????void?initresultFile(connection_pool?*connPool);
39
40????private:
41????????void?init();
42????????//從m_read_buf讀取,并處理請求報文
43????????HTTP_CODE?process_read();
44????????//向m_write_buf寫入響應報文數據
45????????bool?process_write(HTTP_CODE?ret);
46????????//主狀態機解析報文中的請求行數據
47????????HTTP_CODE?parse_request_line(char?*text);
48????????//主狀態機解析報文中的請求頭數據
49????????HTTP_CODE?parse_headers(char?*text);
50????????//主狀態機解析報文中的請求內容
51????????HTTP_CODE?parse_content(char?*text);
52????????//生成響應報文
53????????HTTP_CODE?do_request();
54
55????????//m_start_line是已經解析的字符
56????????//get_line用于將指針向后偏移,指向未處理的字符
57????????char*?get_line(){return?m_read_buf+m_start_line;};
58
59????????//從狀態機讀取一行,分析是請求報文的哪一部分
60????????LINE_STATUS?parse_line();
61
62????????void?unmap();
63
64????????//根據響應報文格式,生成對應8個部分,以下函數均由do_request調用
65????????bool?add_response(const?char*?format,...);
66????????bool?add_content(const?char*?content);
67????????bool?add_status_line(int?status,const?char*?title);
68????????bool?add_headers(int?content_length);
69????????bool?add_content_type();
70????????bool?add_content_length(int?content_length);
71????????bool?add_linger();
72????????bool?add_blank_line();
73
74????public:
75????????static?int?m_epollfd;
76????????static?int?m_user_count;
77????????MYSQL?*mysql;
78
79????private:
80????????int?m_sockfd;
81????????sockaddr_in?m_address;
82
83????????//存儲讀取的請求報文數據
84????????char?m_read_buf[READ_BUFFER_SIZE];
85????????//緩沖區中m_read_buf中數據的最后一個字節的下一個位置
86????????int?m_read_idx;
87????????//m_read_buf讀取的位置m_checked_idx
88????????int?m_checked_idx;
89????????//m_read_buf中已經解析的字符個數
90????????int?m_start_line;
91
92????????//存儲發出的響應報文數據
93????????char?m_write_buf[WRITE_BUFFER_SIZE];
94????????//指示buffer中的長度
95????????int?m_write_idx;
96
97????????//主狀態機的狀態
98????????CHECK_STATE?m_check_state;
99????????//請求方法
100????????METHOD?m_method;
101
102????????//以下為解析請求報文中對應的6個變量
103????????//存儲讀取文件的名稱
104????????char?m_real_file[FILENAME_LEN];
105????????char?*m_url;
106????????char?*m_version;
107????????char?*m_host;
108????????int?m_content_length;
109????????bool?m_linger;
110
111????????char?*m_file_address;????????//讀取服務器上的文件地址
112????????struct?stat?m_file_stat;
113????????struct?iovec?m_iv[2];????????//io向量機制iovec
114????????int?m_iv_count;
115????????int?cgi;????????????????????//是否啟用的POST
116????????char?*m_string;????????????????//存儲請求頭數據
117????????int?bytes_to_send;??????????//剩余發送字節數
118????????int?bytes_have_send;????????//已發送字節數
119};
在http請求接收部分,會涉及到init和read_once函數,但init僅僅是對私有成員變量進行初始化,不用過多講解。
這里,對read_once進行介紹。read_once讀取瀏覽器端發送來的請求報文,直到無數據可讀或對方關閉連接,讀取到m_read_buffer中,并更新m_read_idx。
1//循環讀取客戶數據,直到無數據可讀或對方關閉連接2bool?http_conn::read_once()
3{
4????if(m_read_idx>=READ_BUFFER_SIZE)
5????{
6????????return?false;
7????}
8????int?bytes_read=0;
9????while(true)
10????{
11????????//從套接字接收數據,存儲在m_read_buf緩沖區
12????????bytes_read=recv(m_sockfd,m_read_buf+m_read_idx,READ_BUFFER_SIZE-m_read_idx,0);
13????????if(bytes_read==-1)
14????????{????
15????????????//非阻塞ET模式下,需要一次性將數據讀完
16????????????if(errno==EAGAIN||errno==EWOULDBLOCK)
17????????????????break;
18????????????return?false;
19????????}
20????????else?if(bytes_read==0)
21????????{
22????????????return?false;
23????????}
24????????//修改m_read_idx的讀取字節數
25????????m_read_idx+=bytes_read;
26????}
27????return?true;
28}
epoll相關代碼
項目中epoll相關代碼部分包括非阻塞模式、內核事件表注冊事件、刪除事件、重置EPOLLONESHOT事件四種。
非阻塞模式
2int?setnonblocking(int?fd)3{
4????int?old_option?=?fcntl(fd,?F_GETFL);
5????int?new_option?=?old_option?|?O_NONBLOCK;
6????fcntl(fd,?F_SETFL,?new_option);
7????return?old_option;
8}
內核事件表注冊新事件,開啟EPOLLONESHOT,針對客戶端連接的描述符,listenfd不用開啟
3????epoll_event?event;
4????event.data.fd?=?fd;
5
6#ifdef?ET
7????event.events?=?EPOLLIN?|?EPOLLET?|?EPOLLRDHUP;
8#endif
9
10#ifdef?LT
11????event.events?=?EPOLLIN?|?EPOLLRDHUP;
12#endif
13
14????if?(one_shot)
15????????event.events?|=?EPOLLONESHOT;
16????epoll_ctl(epollfd,?EPOLL_CTL_ADD,?fd,?&event);
17????setnonblocking(fd);
18}
內核事件表刪除事件
3????epoll_ctl(epollfd,?EPOLL_CTL_DEL,?fd,?0);
4????close(fd);
5}
重置EPOLLONESHOT事件
3????epoll_event?event;
4????event.data.fd?=?fd;
5
6#ifdef?ET
7????event.events?=?ev?|?EPOLLET?|?EPOLLONESHOT?|?EPOLLRDHUP;
8#endif
9
10#ifdef?LT
11????event.events?=?ev?|?EPOLLONESHOT?|?EPOLLRDHUP;
12#endif
13
14????epoll_ctl(epollfd,?EPOLL_CTL_MOD,?fd,?&event);
15}
服務器接收http請求
瀏覽器端發出http連接請求,主線程創建http對象接收請求并將所有數據讀入對應buffer,將該對象插入任務隊列,工作線程從任務隊列中取出一個任務進行處理。
1//創建MAX_FD個http類對象2http_conn*?users=new?http_conn[MAX_FD];
3
4//創建內核事件表
5epoll_event?events[MAX_EVENT_NUMBER];
6epollfd?=?epoll_create(5);
7assert(epollfd?!=?-1);
8
9//將listenfd放在epoll樹上
10addfd(epollfd,?listenfd,?false);
11
12//將上述epollfd賦值給http類對象的m_epollfd屬性
13http_conn::m_epollfd?=?epollfd;
14
15while?(!stop_server)
16{
17????//等待所監控文件描述符上有事件的產生
18????int?number?=?epoll_wait(epollfd,?events,?MAX_EVENT_NUMBER,?-1);
19????if?(number?0?&&?errno?!=?EINTR)
20????{
21????????break;
22????}
23????//對所有就緒事件進行處理
24????for?(int?i?=?0;?i?25????{
26????????int?sockfd?=?events[i].data.fd;
27
28????????//處理新到的客戶連接
29????????if?(sockfd?==?listenfd)
30????????{
31????????????struct?sockaddr_in?client_address;
32????????????socklen_t?client_addrlength?=?sizeof(client_address);
33//LT水平觸發
34#ifdef?LT
35????????????int?connfd?=?accept(listenfd,?(struct?sockaddr?*)&client_address,?&client_addrlength);
36????????????if?(connfd?0)
37????????????{
38????????????????continue;
39????????????}
40????????????if?(http_conn::m_user_count?>=?MAX_FD)
41????????????{
42????????????????show_error(connfd,?"Internal?server?busy");
43????????????????continue;
44????????????}
45????????????users[connfd].init(connfd,?client_address);
46#endif
47
48//ET非阻塞邊緣觸發
49#ifdef?ET
50????????????//需要循環接收數據
51????????????while?(1)
52????????????{
53????????????????int?connfd?=?accept(listenfd,?(struct?sockaddr?*)&client_address,?&client_addrlength);
54????????????????if?(connfd?0)
55????????????????{
56????????????????????break;
57????????????????}
58????????????????if?(http_conn::m_user_count?>=?MAX_FD)
59????????????????{
60????????????????????show_error(connfd,?"Internal?server?busy");
61????????????????????break;
62????????????????}
63????????????????users[connfd].init(connfd,?client_address);
64????????????}
65????????????continue;
66#endif
67????????}
68
69????????//處理異常事件
70????????else?if?(events[i].events?&?(EPOLLRDHUP?|?EPOLLHUP?|?EPOLLERR))
71????????{
72????????????//服務器端關閉連接
73????????}
74
75????????//處理信號
76????????else?if?((sockfd?==?pipefd[0])?&&?(events[i].events?&?EPOLLIN))
77????????{
78????????}
79
80????????//處理客戶連接上接收到的數據
81????????else?if?(events[i].events?&?EPOLLIN)
82????????{
83????????????//讀入對應緩沖區
84????????????if?(users[sockfd].read_once())
85????????????{
86????????????????//若監測到讀事件,將該事件放入請求隊列
87????????????????pool->append(users?+?sockfd);
88????????????}
89????????????else
90????????????{
91???????????????//服務器關閉連接
92????????????}
93????????}
94
95????}
96}
如果本文對你有幫助,閱讀原文star一下服務器項目,我們需要你的星星^_^.
完。
總結
以上是生活随笔為你收集整理的oracle 监听程序当前无法识别连接描述符中请求的服务_最新版Web服务器项目详解 04 http连接处理(上)...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 什么叫基于web的网站_什么叫响应式网站
- 下一篇: 两个服务器之间怎么传输大量数据速度快 j