在閱讀了TLPI和深刻理解計算機系統以後,學會了如何使用linux系統api,想在寫代碼的過程當中來加深本身對知識的理解,更想用這些知識來去作一個更酷的東西,而不只僅是教課書上的簡單服務器。並且在實現過程當中每每能學到教科書外的東西。
私覺得項目爲導向是學習編程的最好方法。並且沒有什麼比本身創造一個東西有趣。
「將一個實際的瀏覽器指向本身的服務器,看着他顯示一個複雜的帶有文本和圖片的web頁面,真是很是使人興奮。"html
首先下載源碼:源碼地址react
而後將web頁面所需的html文件放在/var/www目錄下linux
$ cd /src
, 進入到src目錄$ make
, 產生可執行文件HttpServer$ ./HttpServer \<ipv4 address> \<port number> \<process number> \<connect number per process>
例如:./HttpServer 127.0.0.1 8080 5 1000 ,這一步是開啓web-server服務。git
Unbtun 16.04.2 內核版本是4.8github
1.本服務器採用進程池,epoll和非阻塞I/O實現高效的半同步/半異步模式。以下圖:web
主進程只管理監聽socket,鏈接socket都由進程池中的worker進行管理。當有新的鏈接到來時,主進程會經過socketpair建立的套接字和worker進程通訊,通知子進程接收新鏈接。子進程正確接收鏈接以後,會把該套接字上的讀寫事件註冊到本身的epll內核事件表中。以後該套接字上的任何I/O操做都由被選中的worker來處理,直到客戶關閉鏈接或超時。編程
2.每一個子進程都是一個reactor,採用epoll和非阻塞I/O實現事件循環。以下圖: api
a. epoll負責監聽事件的發生,有事件到來將調用相應的事件處理單元進行處理瀏覽器
i. 對一個鏈接來講,主要監聽的就是讀就緒事件和寫就緒事件。緩存
ii. 統一事件源:
1). 信號:信號是一種異步事件,信號處理函數和程序的主循環是兩條不一樣的執行路線,很顯然,信號處理函數須要儘量的執行完成,以確保信號不被屏蔽(信號是不會排隊的)。一個典型的解決方案是把信號的主要處理邏輯放到事件循環裏,當信號處理函數被觸發時只是經過管道將信號通知給主循環接收和處理信號,只須要將和信號處理函數通訊的管道的可讀事件添加到epoll裏。這樣信號就能和其餘I/O事件同樣被處理。
2). 定時器事件。使用timefd,一樣經過監聽timefd上的可讀事件來統一事件源。將其設置爲邊沿觸發,否則timefd水平觸發將一直告知該事件。
b. 鏈接池和內存池的實現:
bool Init(int connfd,size_t recv_buffer_size,size_t send_buffer_size);
函數,一個Return_Code process(OptType status)
函數。前一個函數會在第一次被調用時分配內存,後一個函數將根據操做類型,來決定要進行的是讀仍是寫操做。同時根據操做結果返回相應的狀態,來決定要給epoll添加什麼事件。f. Http報文請求行和頭部解析:
1. 爲何採用多進程而不是單進程多線程:
a. 雖說多線程的切換開銷比多進程低。若是每個進程都工做在一個cpu上,那麼切換的開銷徹底能夠省去,並且由於咱們採用的是進程池,進程的數目在啓動時是能夠設置的,並且並不會在程序的執行過程當中頻繁的開新進程和銷燬就進程,因此進程銷燬和產生這塊開銷也避免了。
b. 同時,多進程的編碼難度比多線程要低的多,並且也不用過多的考慮到線程安全問題。
c. 綜上,我選擇了多進程。
2. 爲何採用時間堆?
a. 首先和雙鏈表相比,最小堆的時間複雜度是優於他的。和時間輪比,雖然添加和刪除定時器的時間複雜度是O(1),可是其執行一個定時的時間複雜度是O(n),同時其精度和時間輪的槽間隔有關。而最小堆則更適合處理這種每次timer模塊須要頻繁找最小的key(最先超時的事件)而後處理後刪除的場景。其刪除一個定時器是O(lgk)(若是考慮延遲刪除的話,會是O(1),可是考慮到我要複用定時器,因此執行了嚴格的刪除),添加是O(lgn),執行則時O(1)。nigix使用的是紅黑樹,可是「memory locality比heap要差一些,實際速度略慢」,即便用最小堆更容易命中cache。libev使用的是更高效的4叉堆。爲了簡化實現,我採用了二叉堆來實現timer的功能。
3. 爲何採用鏈接池和內存池?
a. 和上面所說的同樣,爲了更好的利用資源,減小內存碎片,下降頻繁的申請和銷燬內存的開銷。
epoll_ctl(int epollfd,int option,int fd,struct epoll_event *evlist)
函數中,將option和fd參數位置換了,致使一直epoll_ctl失敗。調試了一天,最後才發現,參數位置寫錯了,然而其餘地方的調用位置都寫對了。感謝和感嘆於大牛的智慧,編碼的路上,還須要繼續努力。 《linux多線程服務端編程》 《深刻理解計算機系統》 《Linx/unix編程手冊》 《linux高性能服務器編程》 《深刻理解Ngix模塊開發與架構解析》