Netcat軟件的基本使用
Netcat(簡(jiǎn)寫(xiě)nc)是一個(gè)強(qiáng)大的網(wǎng)絡(luò)命令工具剃浇,能夠在linux中執(zhí)行與TCP巾兆、UDP相關(guān)的操作,例如端口掃描虎囚,端口重定向角塑、端口監(jiān)聽(tīng)甚至遠(yuǎn)程連接。
在這里淘讥,我們使用 nc 來(lái)模擬一臺(tái)接收message的服務(wù)器圃伶,和一臺(tái)發(fā)送message的客戶(hù)端。
1蒲列、安裝 nc 軟件
sudo yum install -y nc
2窒朋、使用 nc 創(chuàng)建一臺(tái)監(jiān)聽(tīng)9999端口的服務(wù)器
nc -l -p 9999 # -l表示listening,監(jiān)聽(tīng)
啟動(dòng)成功后 nc 進(jìn)行阻塞
3嫉嘀、新建一個(gè)bash炼邀,使用 nc 創(chuàng)建一個(gè)發(fā)送message的客戶(hù)端
nc localhost 9999
在控制臺(tái)上輸入要發(fā)送的信息,查看服務(wù)端是否接收到
4剪侮、查看上面的nc進(jìn)程中的文件描述符
ps -ef | grep nc # 查看nc的進(jìn)程號(hào)拭宁,這里假設(shè)是2603
ls /proc/2603/fd # 查看2603進(jìn)程下的文件描述符
[圖片上傳中...(image-f69c24-1615989342820-15)]
可以看到這個(gè)進(jìn)程下有一個(gè)socket洛退,這就是nc的客戶(hù)端和服務(wù)端之間創(chuàng)建的一個(gè)socket
經(jīng)過(guò)這一系列的操作,相信我們對(duì)Netcat軟件有了基本的了解杰标,下面來(lái)介紹BIO
strace追蹤系統(tǒng)調(diào)用
strace軟件說(shuō)明: 它是一個(gè)可以追蹤系統(tǒng)調(diào)用和信號(hào)的軟件兵怯,通過(guò)它我們來(lái)了解BIO
環(huán)境說(shuō)明: 這里演示的都是基于老版本的linux,因?yàn)樾掳姹镜膌inux都不用BIO了腔剂,演示不出來(lái)
1媒区、使用strace來(lái)追蹤系統(tǒng)調(diào)用
sudo yum install -y strace # 安裝strace軟件
mkdir ~/strace # 新建一個(gè)目錄,存放追蹤的信息
cd ~/strace # 進(jìn)入到這個(gè)目錄
strace -ff -o out nc -l -p 8080 # 使用strace追蹤后邊的命令進(jìn)行的系統(tǒng)調(diào)用
# -ff 表示追蹤后面命令創(chuàng)建的進(jìn)程及子進(jìn)程的所有系統(tǒng)調(diào)用掸犬,
# 并根據(jù)進(jìn)程id號(hào)分開(kāi)輸出到文件
# -o 表示追蹤到的信息輸出到指定名稱(chēng)的文件袜漩,這里是out
2、查看服務(wù)端創(chuàng)建的系統(tǒng)調(diào)用
在上一步進(jìn)入的目錄下湾碎,出現(xiàn)了一個(gè) out.pid 文件宙攻,里的內(nèi)容都是 nc -l -p 9999 這個(gè)命令執(zhí)行后的系統(tǒng)調(diào)用過(guò)程,使用vim命令來(lái)查看
vim out.92459 # nc進(jìn)程id為92459
這里accept()方法進(jìn)行了阻塞介褥,它要等待其他socket對(duì)它進(jìn)行連接
3座掘、客戶(hù)端連接,查看系統(tǒng)調(diào)用
退出vim柔滔,使用tail來(lái)進(jìn)行查看
tail -f out.92459
-f 參數(shù):當(dāng)文件有追加的內(nèi)容溢陪,可以實(shí)時(shí)地打印在控制臺(tái),這樣就能很方便來(lái)查看客戶(hù)端連接后進(jìn)行的系統(tǒng)調(diào)用
nc localhost 8080
查看系統(tǒng)調(diào)用
這里客戶(hù)端連接后睛廊,accept() 方法獲取到客戶(hù)端連接并返回文件描述符4形真,這個(gè)4就是服務(wù)端新創(chuàng)建的socket,用于和這個(gè)客戶(hù)端進(jìn)行通信
之后使用多路復(fù)用器poll來(lái)監(jiān)聽(tīng)服務(wù)端上文件描述符4和0超全,0是標(biāo)準(zhǔn)輸入文件描述符没酣,哪個(gè)有事件發(fā)生就讀取哪個(gè)文件描述符,如果都沒(méi)有事件發(fā)生就進(jìn)行堵塞
4卵迂、客戶(hù)端發(fā)送message,查看系統(tǒng)調(diào)用
客戶(hù)端向服務(wù)端發(fā)送數(shù)據(jù)绒净,服務(wù)端就能從socket中監(jiān)聽(tīng)到有事件發(fā)生见咒,就能進(jìn)行相應(yīng)的處理,處理完繼續(xù)堵塞挂疆,等待下一個(gè)事件發(fā)生
5改览、服務(wù)端發(fā)送數(shù)據(jù)到客戶(hù)端,查看系統(tǒng)調(diào)用
服務(wù)端發(fā)數(shù)據(jù)缤言,肯定從鍵盤(pán)輸入宝当,也就是標(biāo)準(zhǔn)輸入0,從0中讀取到數(shù)據(jù)發(fā)送給socket 4
BIO(阻塞式IO)
在我們第三節(jié)中胆萧,我們使用 strace 工具查看了 nc 軟件使用過(guò)程中的系統(tǒng)調(diào)用庆揩,其實(shí)上一節(jié)中體現(xiàn)的就是BIO俐东,我們把上面的一系列系統(tǒng)調(diào)用總結(jié)一下,根據(jù)直觀的理解BIO
1订晌、單線程模式
1.1虏辫、過(guò)程演示
1、服務(wù)端啟動(dòng)
啟動(dòng)服務(wù)端锈拨,等待socket連接砌庄,accept()方法阻塞
2、客戶(hù)端連接,未發(fā)送數(shù)據(jù)
連接客戶(hù)端奕枢,accept() 方法執(zhí)行娄昆,未收到client1發(fā)送的數(shù)據(jù),read()方法阻塞
3缝彬、另一個(gè)客戶(hù)端連接
由于read()方法阻塞萌焰,無(wú)法執(zhí)行到accept()方法,所以這樣cpu一次只能處理一個(gè)socket
1.2跌造、存在的問(wèn)題
上面的模型存在很大的問(wèn)題杆怕,如果客戶(hù)端與服務(wù)端建立了連接,客戶(hù)端遲遲不發(fā)數(shù)據(jù)壳贪,進(jìn)程就會(huì)一直堵塞在read()方法上陵珍,這樣其他客戶(hù)端也不能進(jìn)行連接,也就是一次只能處理一個(gè)客戶(hù)端违施,對(duì)客戶(hù)很不友好
1.3互纯、如何解決
其實(shí)要解決這個(gè)問(wèn)題很簡(jiǎn)單,利用多線程就可以磕蒲,只要連接了一個(gè)socket留潦,操作系統(tǒng)分配一個(gè)線程來(lái)處理,這樣read()方法堵塞在每個(gè)線程上辣往,不堵塞主線程兔院,就能操作多個(gè)socket了,有哪個(gè)線程中的socket有數(shù)據(jù)站削,就讀哪個(gè)socket
2坊萝、多線程模式
1.1 過(guò)程演示
- 程序服務(wù)端只負(fù)責(zé)監(jiān)聽(tīng)是否有客戶(hù)端連接,使用 accept() 阻塞
- 客戶(hù)端1連接服務(wù)端许起,就開(kāi)辟一個(gè)線程(thread1)來(lái)執(zhí)行 read() 方法十偶,程序服務(wù)端繼續(xù)監(jiān)聽(tīng)
- 客戶(hù)端2連接服務(wù)端,也開(kāi)辟一個(gè)線程园细,執(zhí)行read()方法
- 任何一個(gè)線程上的socket有數(shù)據(jù)發(fā)送過(guò)來(lái)惦积,read()就能立馬讀到,cpu就能進(jìn)行處理
1.2猛频、存在的問(wèn)題
上面這個(gè)多線程模型狮崩,看似已經(jīng)十分的完美蛛勉,其實(shí)也有很大的問(wèn)題。每來(lái)一個(gè)客戶(hù)端厉亏,就要開(kāi)辟一個(gè)線程董习,如果來(lái)1萬(wàn)個(gè)客戶(hù)端,那就要開(kāi)辟1萬(wàn)個(gè)線程爱只。在操作系統(tǒng)中皿淋,用戶(hù)態(tài)不能直接開(kāi)辟線程,需要調(diào)用cpu的80軟中斷恬试,讓內(nèi)核來(lái)創(chuàng)建的一個(gè)線程窝趣,這其中還涉及到用戶(hù)狀態(tài)的切換(上下文的切換),十分耗資源训柴。
1.3哑舒、如何解決
第一個(gè)辦法:使用線程池,這個(gè)在客戶(hù)端連接少的情況下可以使用幻馁,但是用戶(hù)量大的情況下洗鸵,你不知道線程池要多大,太大了內(nèi)存可能不夠仗嗦,也不可行
第二個(gè)辦法:因?yàn)閞ead()方法堵塞了膘滨,所有要開(kāi)辟多個(gè)線程,如果什么方法能使read()方法不堵塞稀拐,這樣就不用開(kāi)辟多個(gè)線程了火邓,這就用到了另一個(gè)IO模型,NIO(非阻塞式IO)
NIO(非阻塞式IO)
1德撬、過(guò)程演示
1铲咨、服務(wù)端剛創(chuàng)建,沒(méi)有客戶(hù)端連接
在NIO中蜓洪,accept()方法也是非阻塞的纤勒,它在一個(gè)while死循環(huán)中
2、當(dāng)有一個(gè)客戶(hù)端進(jìn)行連接時(shí)
3隆檀、當(dāng)有第二個(gè)客戶(hù)端進(jìn)行連接時(shí)
2踊东、總結(jié)
在NIO模式中,一切都是非阻塞的:
- accept()方法是非阻塞的刚操,如果沒(méi)有客戶(hù)端連接,就返回error
- read()方法是非阻塞的再芋,如果read()方法讀取不到數(shù)據(jù)就返回error菊霜,如果讀取到數(shù)據(jù)時(shí)只阻塞read()方法讀數(shù)據(jù)的時(shí)間
在NIO模式中,只有一個(gè)線程:
- 當(dāng)一個(gè)客戶(hù)端與服務(wù)端進(jìn)行連接济赎,這個(gè)socket就會(huì)加入到一個(gè)數(shù)組中记某,隔一段時(shí)間遍歷一次,看這個(gè)socket的read()方法能否讀到數(shù)據(jù)
- 這樣一個(gè)線程就能處理多個(gè)客戶(hù)端的連接和讀取了
3液南、存在的問(wèn)題
NIO成功的解決了BIO需要開(kāi)啟多線程的問(wèn)題勾徽,NIO中一個(gè)線程就能解決多個(gè)socket,看似已經(jīng) perfect喘帚,但是還存在問(wèn)題。
這個(gè)模型在客戶(hù)端少的時(shí)候十分好用吹由,但是客戶(hù)端如果很多,比如有1萬(wàn)個(gè)客戶(hù)端進(jìn)行連接倾鲫,那么每次循環(huán)就要遍歷1萬(wàn)個(gè)socket,如果一萬(wàn)個(gè)socket中只有10個(gè)socket有數(shù)據(jù)乌昔,也會(huì)變量一萬(wàn)個(gè)socket,就會(huì)做很多無(wú)用功甚淡。而且這個(gè)遍歷過(guò)程是在用戶(hù)態(tài)進(jìn)行的,用戶(hù)態(tài)判斷socket是否有數(shù)據(jù)還是調(diào)用內(nèi)核的read()方法實(shí)現(xiàn)的贯卦,這就涉及到用戶(hù)態(tài)和內(nèi)核態(tài)的切換,每遍歷一個(gè)就要切換一次撵割,開(kāi)銷(xiāo)很大
因?yàn)檫@些問(wèn)題的存在辙芍,IO多路復(fù)用應(yīng)運(yùn)而生
IO Multiplexing(IO多路復(fù)用)
IO多路復(fù)用有三種實(shí)現(xiàn)方式,select故硅、poll、epoll吃衅,現(xiàn)在讓我們來(lái)看看這三種實(shí)現(xiàn)的真面目吧
1、select
1.1 優(yōu)點(diǎn)
select 其實(shí)就是把NIO中用戶(hù)態(tài)要遍歷的 fd 數(shù)組拷貝到了內(nèi)核態(tài)峻呕,讓內(nèi)核態(tài)來(lái)遍歷,因?yàn)橛脩?hù)態(tài)判斷socket是否有數(shù)據(jù)還是要調(diào)用內(nèi)核態(tài)的瘦癌,所有拷貝到內(nèi)核態(tài)后,這樣遍歷判斷的時(shí)候就不用一直用戶(hù)態(tài)和內(nèi)核態(tài)頻繁切換了
從代碼中可以看出讯私,select系統(tǒng)調(diào)用后,返回了一個(gè)置位后的&rset妄帘,這樣用戶(hù)態(tài)只需進(jìn)行很簡(jiǎn)單的二進(jìn)制比較,就能很快知道哪些socket需要read數(shù)據(jù)抡驼,有效提高了效率
1.2 存在的問(wèn)題
1、bitmap最大1024位致盟,一個(gè)進(jìn)程最多只能處理1024個(gè)客戶(hù)端2、&rset不可重用馏锡,每次socket有數(shù)據(jù)就相應(yīng)的位會(huì)被置位3、文件描述符數(shù)組拷貝到了內(nèi)核態(tài)匪煌,仍然有開(kāi)銷(xiāo)4、select并沒(méi)有通知用戶(hù)態(tài)哪一個(gè)socket有數(shù)據(jù)萎庭,仍然需要O(n)的遍歷
2、poll
2.1 代碼例子
在poll中驳规,文件描述符有一份獨(dú)立的數(shù)據(jù)結(jié)構(gòu)pollfd署海,傳入poll中的是pollfd的數(shù)組,其他的實(shí)現(xiàn)邏輯和select一樣
2.2 優(yōu)點(diǎn)
1砸狞、poll使用pollfd數(shù)組來(lái)代替select中的bitmap,數(shù)組沒(méi)有1024的限制刀森,可以一次管理更多的client2、當(dāng)pollfds數(shù)組中有事件發(fā)生,相應(yīng)的revents置位為1,遍歷的時(shí)候又置位回0飘哨,實(shí)現(xiàn)了pollfd數(shù)組的重用
2.3 缺點(diǎn)
poll 解決了select缺點(diǎn)中的前兩條,其本質(zhì)原理還是select的方法芽隆,還存在select中原來(lái)的問(wèn)題
1、pollfds數(shù)組拷貝到了內(nèi)核態(tài)胚吁,仍然有開(kāi)銷(xiāo)2、poll并沒(méi)有通知用戶(hù)態(tài)哪一個(gè)socket有數(shù)據(jù)孽拷,仍然需要O(n)的遍歷
3、epoll
3.1 代碼例子
3.2 事件通知機(jī)制
1脓恕、當(dāng)有網(wǎng)卡上有數(shù)據(jù)到達(dá)了,首先會(huì)放到DMA(內(nèi)存中的一個(gè)buffer炼幔,網(wǎng)卡可以直接訪問(wèn)這個(gè)數(shù)據(jù)區(qū)域)中2史简、網(wǎng)卡向cpu發(fā)起中斷,讓cpu先處理網(wǎng)卡的事3圆兵、中斷號(hào)在內(nèi)存中會(huì)綁定一個(gè)回調(diào),哪個(gè)socket中有數(shù)據(jù)衙傀,回調(diào)函數(shù)就把哪個(gè)socket放入就緒鏈表中
3.3 詳細(xì)過(guò)程
首先epoll_create創(chuàng)建epoll實(shí)例,它會(huì)創(chuàng)建所需要的紅黑樹(shù)统抬,以及就緒鏈表,以及代表epoll實(shí)例的文件句柄聪建,其實(shí)就是在內(nèi)核開(kāi)辟一塊內(nèi)存空間,所有與服務(wù)器連接的socket都會(huì)放到這塊空間中金麸,這些socket以紅黑樹(shù)的形式存在,同時(shí)還會(huì)有一塊空間存放就緒鏈表揍魂;紅黑樹(shù)存儲(chǔ)所監(jiān)控的文件描述符的節(jié)點(diǎn)數(shù)據(jù),就緒鏈表存儲(chǔ)就緒的文件描述符的節(jié)點(diǎn)數(shù)據(jù)现斋;epoll_ctl添加新的描述符,首先判斷是紅黑樹(shù)上是否有此文件描述符節(jié)點(diǎn)庄蹋,如果有,則立即返回限书。如果沒(méi)有, 則在樹(shù)干上插入新的節(jié)點(diǎn)倦西,并且告知內(nèi)核注冊(cè)回調(diào)函數(shù)。當(dāng)接收到某個(gè)文件描述符過(guò)來(lái)數(shù)據(jù)時(shí)调限,那么內(nèi)核將該節(jié)點(diǎn)插入到就緒鏈表里面。epoll_wait將會(huì)接收到消息耻矮,并且將數(shù)據(jù)拷貝到用戶(hù)空間,清空鏈表裆装。
3.4 水平觸發(fā)和邊沿觸發(fā)
Level_triggered(水平觸發(fā)):當(dāng)被監(jiān)控的文件描述符上有可讀寫(xiě)事件發(fā)生時(shí),epoll_wait()會(huì)通知處理程序去讀寫(xiě)茎活。如果這次沒(méi)有把數(shù)據(jù)一次性全部讀寫(xiě)完(如讀寫(xiě)緩沖區(qū)太小),那么下次調(diào)用 epoll_wait()時(shí)载荔,它還會(huì)通知你在上沒(méi)讀寫(xiě)完的文件描述符上繼續(xù)讀寫(xiě),當(dāng)然如果你一直不去讀寫(xiě)懒熙,它會(huì)一直通知你!9ぴ!如果系統(tǒng)中有大量你不需要讀寫(xiě)的就緒文件描述符肢娘,而它們每次都會(huì)返回,這樣會(huì)大大降低處理程序檢索自己關(guān)心的就緒文件描述符的效率3鹘 !拘荡!Edge_triggered(邊緣觸發(fā)):當(dāng)被監(jiān)控的文件描述符上有可讀寫(xiě)事件發(fā)生時(shí),epoll_wait()會(huì)通知處理程序去讀寫(xiě)。如果這次沒(méi)有把數(shù)據(jù)全部讀寫(xiě)完(如讀寫(xiě)緩沖區(qū)太小)袱结,那么下次調(diào)用epoll_wait()時(shí),它不會(huì)通知你垢夹,也就是它只會(huì)通知你一次,直到該文件描述符上出現(xiàn)第二次可讀寫(xiě)事件才會(huì)通知你9!而晒!這種模式比水平觸發(fā)效率高,系統(tǒng)不會(huì)充斥大量你不關(guān)心的就緒文件描述符3酢!监署!
3.5 優(yōu)點(diǎn)
epoll是現(xiàn)在最先進(jìn)的IO多路復(fù)用器,Redis钠乏、Nginx,linux中的Java NIO都使用的是epoll
1晓避、一個(gè)socket的生命周期中只有一次從用戶(hù)態(tài)拷貝到內(nèi)核態(tài)的過(guò)程,開(kāi)銷(xiāo)小2够滑、使用event事件通知機(jī)制,每次socket中有數(shù)據(jù)會(huì)主動(dòng)通知內(nèi)核彰触,并加入到就緒鏈表中,不需要遍歷所有的socket
Linux、C/C++技術(shù)交流群 整理了一些個(gè)人覺(jué)得比較好Linux服務(wù)器架構(gòu)師學(xué)習(xí)書(shū)籍尔艇、大廠面試題、和熱門(mén)技術(shù)教學(xué)視頻資料(資料包括C/C++么鹤,Linux,golang技術(shù)蒸甜,Nginx,ZeroMQ柠新,MySQL,Redis恨憎,fastdfs,MongoDB憔恳,ZK,流媒體钥组,CDN,P2P腔丧,K8S,Docker愉粤,TCP/IP,協(xié)程衣厘,DPDK,ffmpeg等)影暴,免費(fèi)分享有需要的可以自行添加哦!
image.png
以上不足的地方歡迎指出討論型宙,覺(jué)得不錯(cuò)的朋友伦吠,希望能得到您的點(diǎn)贊支持