前言:
本篇文章將介紹客戶端-服務(wù)端之間從最簡單的Socket模型到I/O多路復(fù)用的模式演變過程,并介紹Reactor和Proactor兩種高性能網(wǎng)絡(luò)模式
文章內(nèi)容摘自:小林Coding
I/O多路復(fù)用+高性能網(wǎng)絡(luò)模式
傳統(tǒng)Socket模型
服務(wù)端流程:
1.創(chuàng)建Socket套接字
2.使用bind函數(shù)綁定ip地址和端口
3.listen函數(shù)監(jiān)聽端口讓套接字變成可以被動(dòng)連接的狀態(tài),此時(shí)服務(wù)端會(huì)被阻塞,等待連接的到來
4.當(dāng)連接到來時(shí),應(yīng)用程序通過accept socket接口從全連接隊(duì)列中拿出一個(gè)已經(jīng)連接好的socket進(jìn)行網(wǎng)絡(luò)通信
5.使用recv/send函數(shù)進(jìn)行數(shù)據(jù)的收發(fā)
6.當(dāng)不使用服務(wù)端時(shí),使用close函數(shù)關(guān)閉服務(wù)端
客戶端流程:
1.創(chuàng)建Socket套接字
2.可以選擇用bind函數(shù)去綁定端口,也可以暫時(shí)不綁定.如果沒有用Bind函數(shù)去指定端口的話,后續(xù)客戶端會(huì)在調(diào)用Connect()函數(shù)時(shí)隨機(jī)的從內(nèi)核參數(shù)指定的范圍內(nèi)選擇一個(gè)隨機(jī)數(shù)作為自己的端口,流程如下:
1.判斷這個(gè)隨機(jī)產(chǎn)生的端口是不是有相同的四元組,如果沒有,則直接使用這個(gè)
端口
2.如果這個(gè)端口有相同的四元組,判斷是不是開啟了參數(shù)tcp_tw_reuse,如果沒
開啟這個(gè)參數(shù),則重新在內(nèi)核參數(shù)指定范圍內(nèi)再選擇一個(gè)端口,重復(fù)判斷過程
3.如果開啟了tcp_tw_reuse參數(shù),則需要判斷相同的四元組連接是不是已經(jīng)進(jìn)入
time_wait狀態(tài)并且時(shí)間超過了1秒,如果是,則可以無縫復(fù)用這個(gè)鏈接,如果
不是,則重新在內(nèi)核參數(shù)指定范圍內(nèi)再選擇一個(gè)端口,重復(fù)判斷過程.
3.用Connect()函數(shù)和服務(wù)端進(jìn)行連接
4.用read/write函數(shù)進(jìn)行數(shù)據(jù)的讀寫
傳統(tǒng)Socket模型的性能瓶頸
傳統(tǒng)Socket模型采用的是阻塞同步的網(wǎng)絡(luò)模式,這里先給大家普及一下
阻塞,非阻塞,同步,異步的概念.
阻塞:當(dāng)一個(gè)任務(wù)發(fā)出一個(gè)請求之后,它需要等待這個(gè)請求被正確處理完畢并返回處理結(jié)果之后,該任務(wù)才能繼續(xù)執(zhí)行.
非阻塞:當(dāng)一個(gè)任務(wù)發(fā)出一個(gè)請求之后,它不需要等待這個(gè)請求被處理完就可以繼續(xù)往后執(zhí)行,并通過輪詢和回調(diào)的方式來獲取請求的處理結(jié)果.
同步:同步的關(guān)鍵詞是等待,只要有等待的過程,就算同步,比如說:任務(wù)需要等待請求的處理結(jié)果之后才能繼續(xù)執(zhí)行,那么這就是一個(gè)同步的過程,所以說阻塞一般和同步掛鉤,但是非阻塞也可以跟同步搭配,非阻塞雖然不用等待任務(wù)被處理完畢,但是可能仍然要等待數(shù)據(jù)從內(nèi)核態(tài)被拷貝的用戶態(tài)的一個(gè)拷貝過程.
異步:沒有等待的過程,既不需要等待請求是否被處理完畢,也不需要等待數(shù)據(jù)拷貝.
所以對于傳統(tǒng)的Socket的阻塞同步網(wǎng)絡(luò)模式來說,服務(wù)端正在處理某一個(gè)客戶端的網(wǎng)絡(luò) I/O,或者因?yàn)樽x寫操作被阻塞住的話,那么服務(wù)端就沒有辦法和其他客戶端正常連接,所以大大限制了服務(wù)端的性能
多進(jìn)程模型
主進(jìn)程負(fù)責(zé)監(jiān)聽連接,一旦連接完成,accept()函數(shù)會(huì)返回一個(gè)已經(jīng)連接好的socket,主進(jìn)程此時(shí)會(huì)通過fork函數(shù)創(chuàng)建若干個(gè)子進(jìn)程.父進(jìn)程和子進(jìn)程之間通過返回值來區(qū)分.
因?yàn)閒ork函數(shù)幾乎可以復(fù)制父進(jìn)程的所有內(nèi)容,所以連同把父進(jìn)程的文件描述符也復(fù)制過來了,子進(jìn)程可以通過文件描述符并使用已經(jīng)連接好的Socket和客戶端進(jìn)行通信
值得注意的是,使用多進(jìn)程模型,需要在使用結(jié)束時(shí)及時(shí)使用wait函數(shù)和waitpid函數(shù)獲取子進(jìn)程的終止?fàn)顟B(tài)并且釋放資源,避免它們成為僵尸進(jìn)程白白占用系統(tǒng)資源
多線程模型
多線程模型由線程池來實(shí)現(xiàn),主要的目的就是避免線程頻繁的創(chuàng)建和銷毀帶來的性能開銷.
線程池的工作邏輯:
1. 創(chuàng)建一個(gè)固定大小的線程池,并初始化一定數(shù)量的工作線程
2. 工作線程按照循環(huán)的方式,從請求隊(duì)列中拿出請求并做出處理
3. 當(dāng)新請求到來時(shí),主線程會(huì)把請求(Socket)放入請求隊(duì)列中
5. 使用互斥鎖,信號(hào)量等工具,讓工作線程安全的從請求隊(duì)列中取出請求
6. 工作線程處理好請求之后,會(huì)再次回到線程池中,再次以循環(huán)的方式等待是否
有請求可以處理
6.服務(wù)器停止工作時(shí),主線程通知工作線程釋放資源,安全關(guān)閉.
不管是多進(jìn)程模型,還是多線程模型,如果一個(gè)TCP連接就要分配一個(gè)進(jìn)程/線程的話,那么如果有10W個(gè)TCP連接就要分配10W個(gè)進(jìn)程/線程,所以多線程模型也不是完美的
普通的線程池工作邏輯
線程池中的所有線程都是工作線程,工作線程中的空閑線程會(huì)從請求隊(duì)列中取出請求并且執(zhí)行
半同步/半反應(yīng)堆線程(模擬Proactor模式)
除了工作線程之外,有專門負(fù)責(zé)監(jiān)聽和任務(wù)調(diào)度的線程存在,例如主線程
當(dāng)請求到來時(shí),主線程或者其他負(fù)責(zé)調(diào)度的線程會(huì)接受請求,然后根據(jù)任務(wù)類型分發(fā)給其他的工作線程.
1.主線程充當(dāng)異步線程,監(jiān)聽所有socket上的事件,步驟如下:
2.當(dāng)請求連接到來時(shí),主線程接收并獲得新連接好的socket
3.將該socket注冊到epoll上,然后用epoll_wait等待事件到來
4.如果監(jiān)聽的socket有讀寫事件發(fā)生時(shí),主線程從socket上接收數(shù)據(jù),并封裝成請求插入到請求隊(duì)列中
I/O多路復(fù)用
I/O多路復(fù)用的思想就是讓一個(gè)進(jìn)程可以去維護(hù)多個(gè)Socket,而不是像多進(jìn)程/線程模型那樣,一個(gè)進(jìn)程/線程只能對應(yīng)一個(gè)Socket
其中select/poll/epoll是系統(tǒng)內(nèi)核提供給用戶態(tài)的多路復(fù)用系統(tǒng)調(diào)用函數(shù),進(jìn)程可以通過這些函數(shù)從內(nèi)核中獲取多個(gè)事件
select/poll
select和poll實(shí)現(xiàn)多路復(fù)用的方式很類似
select是把所有已連接的Socket放進(jìn)一個(gè)文件描述符集合之中,然后把整個(gè)文件描述符集合拷貝到內(nèi)核,由內(nèi)核來檢查是否有事件發(fā)生,內(nèi)核會(huì)以遍歷的方式遍歷整個(gè)文件描述符集合然后把有事件發(fā)生的Socket標(biāo)記為可讀或可寫,然后再把整個(gè)文件描述符集合拷貝回用戶態(tài),再次遍歷文件描述符集合找到可讀或可寫的Socket做出處理
所以select需要經(jīng)歷兩次遍歷文件描述符集合+兩次文件描述符集合的拷貝
而poll跟select很相似,只不過poll沒有用Bitsmap來存儲(chǔ)文件描述符集合,而是用動(dòng)態(tài)數(shù)組+鏈表的方式存儲(chǔ)文件描述符集合的,所以二者都是使用線性結(jié)構(gòu)來存儲(chǔ)Socket集合的.
epoll
1.epoll使用紅黑樹來跟蹤存儲(chǔ)所有待檢測的文件描述符的,因?yàn)榧t黑樹是一個(gè)高效的數(shù)據(jù)結(jié)構(gòu),所以它只需要O(logn)就可以完成插入查詢刪除操作,而select和poll就沒有這樣高效的數(shù)據(jù)結(jié)構(gòu),正是因?yàn)閑poll中內(nèi)核有了紅黑樹來存儲(chǔ)socket,所以他不需要像select/poll那樣,在操作時(shí)將整個(gè)文件描述符集合全部拷貝到內(nèi)核,而是只需要傳入一個(gè)待檢測的文件描述符即可.
2.epoll采用事件驅(qū)動(dòng)機(jī)制,在內(nèi)核中維護(hù)了一個(gè)鏈表來存儲(chǔ)所有的就緒事件,當(dāng)監(jiān)測的Socket上有事件發(fā)生時(shí),內(nèi)核會(huì)通過回調(diào)函數(shù)將事件加入就緒事件隊(duì)列中.當(dāng)用戶調(diào)用epoll_wait函數(shù)獲取事件時(shí),只會(huì)返回有事件發(fā)生的文件描述符個(gè)數(shù),而不會(huì)像select/poll那樣遍歷整個(gè)文件描述符集合
epoll相關(guān)函數(shù)
epoll_create() //創(chuàng)建一個(gè)epoll對象
epoll_ctl() //將待檢測的socket注冊到epoll上
epoll_wait() //等待事件的到來
加入epoll之后的服務(wù)端流程
1.創(chuàng)建Socket套接字
2.使用bind函數(shù)綁定ip地址和端口
3.用listen監(jiān)聽端口
4.將服務(wù)端socket通過epoll_ctl注冊到epoll上
5.epoll_wait等待連接到來,當(dāng)連接到來時(shí),用accept函數(shù)獲取已經(jīng)連接
好的socket
6.將已經(jīng)連接好的socket注冊到epoll上
7.使用epoll_wait函數(shù)等待事件的到來
水平觸發(fā)和邊緣觸發(fā)
邊緣觸發(fā)
當(dāng)監(jiān)測的Socket上有可讀事件發(fā)生時(shí),服務(wù)端會(huì)在epoll_wait上喚醒一次,即使進(jìn)程沒有主動(dòng)調(diào)用read函數(shù)進(jìn)行讀操作,服務(wù)端也僅僅會(huì)喚醒一次,并且盡可能一次性的將內(nèi)核緩沖區(qū)的數(shù)據(jù)讀完
因?yàn)檫吘売|發(fā)只有一次通知,所以服務(wù)端會(huì)盡可能的多讀寫數(shù)據(jù),以免浪費(fèi)了讀寫數(shù)據(jù)的機(jī)會(huì),并且是以循環(huán)的方式在文件描述符上讀寫數(shù)據(jù)的,所以文件描述符此時(shí)必須是非阻塞的,否則就會(huì)被阻塞在讀寫函數(shù)上,程序就沒辦法繼續(xù)進(jìn)行下去了.
水平觸發(fā)
當(dāng)被監(jiān)控的Socket上有可讀事件發(fā)生時(shí),服務(wù)端會(huì)不斷的從epoll_wait上喚醒,并讀取內(nèi)核緩沖區(qū)的數(shù)據(jù)直到內(nèi)核緩沖區(qū)的數(shù)據(jù)被讀完
當(dāng)內(nèi)核通知文件描述符是可讀寫時(shí),接下來還要繼續(xù)檢查它的狀態(tài),確保它依然是可讀或者可寫的,所以沒必要像邊緣觸發(fā)那樣一次性讀寫過多的數(shù)據(jù).
Reactor模式和Proactor模式
Reactor
是一種非阻塞同步網(wǎng)絡(luò)模式,它感知的就緒的網(wǎng)絡(luò)事件,當(dāng)感知到有事件發(fā)生時(shí),應(yīng)用進(jìn)程會(huì)主動(dòng)的使用write/read函數(shù)進(jìn)行讀寫操作,將socket緩沖區(qū)中的數(shù)據(jù)讀入應(yīng)用進(jìn)程的內(nèi)存之中,這個(gè)過程是同步的,讀完之后再進(jìn)行數(shù)據(jù)的處理.
Proactor
是一種異步網(wǎng)絡(luò)模式,它感知的是已完成的網(wǎng)絡(luò)事件,當(dāng)發(fā)出一個(gè)異步請求時(shí),需要提供緩沖區(qū)的地址,然后系統(tǒng)內(nèi)核就會(huì)幫助我們完成讀寫操作,這里的讀寫操作全部都是由操作系統(tǒng)來完成的,而不是像Reactor那樣是由應(yīng)用進(jìn)程主動(dòng)調(diào)用read/write函數(shù)完成的,在操作系統(tǒng)完成讀寫操作之后,交給應(yīng)用進(jìn)程處理文章來源:http://www.zghlxwxcb.cn/news/detail-499464.html
總結(jié)
Reactor模式可以理解為:
當(dāng)事件到來時(shí),操作系統(tǒng)通知應(yīng)用進(jìn)程,由應(yīng)用進(jìn)程處理
Proactor模式可以理解為:
當(dāng)事件到來時(shí),操作系統(tǒng)處理,處理完了通知應(yīng)用進(jìn)程文章來源地址http://www.zghlxwxcb.cn/news/detail-499464.html
到了這里,關(guān)于I/O多路復(fù)用+高性能網(wǎng)絡(luò)模式的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!