字符串缓冲区太小怎么解决_epoll的两种模式 ET和LT printf的缓冲区问题 边缘非阻塞模式...
學習于:https://www.bilibili.com/video/av44660437/?p=9
前文:何柄融:多路復用I/O select poll epoll 何柄融:select poll epoll 再一次的了解
這里就不講epoll的基礎了,如果對epoll沒了解的請去前文學習。
當時有:
epoll除了提供select/ poll那種IO事件的電平觸發(Level Triggered) 外,還提供了邊沿觸發(Edge Triggered) ,這就使得用戶空間程序有可能緩存IO狀態,減少epoll_ wait/epoll_ pwait的調用,提高應用程序效率。(這個沒怎么看得懂。。)
所以,特地來學習一下epoll的ET和LT。
epoll的三種工作模式:
1.水平觸發模式(默認):Level Triggered LT
2.邊沿觸發模式 : Edge Triggered ET
3.邊緣非阻塞觸發
先來介紹這個默認使用的水平觸發模式(LT)
特點:1.只要fd對應的緩沖區有數據,epoll_wait就返回
2.返回的次數與發送數據的次數沒有關系。
3.epoll默認的使用模式
看代碼:
核心:關鍵就在于處理已連接描述符時的緩沖區大小。之前我們都是使用1024個字節,而寫的時候也不會太大。 而這里把1024改成了5,也就是說每次處理5個字節。而如果客戶端一次發送過來100個字節,那么就會因為客戶端的這一次發送,服務器這邊要調用20次的epoll_wait(),因為 100個字節發送過來,服務器這邊的已連接描述符這里會有個對應的緩沖區緩沖這100個字節,然后因為緩沖區有數據,所以就會出發epoll_wait()的返回,而返回之后服務器又只從緩沖區接收5個字節來處理。那么就會剩下95個字節。那么當服務器處理完這5個字節后,本來應該阻塞在epollwait這里,不斷的喚醒沉睡的,可是由于緩沖區還有數據,所以又馬上讓epollwait返回,然后再一次處理這個文件描述符的事件。所以,這里就會多次調用epollwait。下面是一個比較形象的圖:
運行代碼的的輸出:
注意到,每次都多了69.123.31,這屬于亂碼,具體解釋如下:
printf函數:有時候會發現打印不出來。因為printf有一個緩沖區,好像是8k,當你打印字符串的時候,如果沒加后面的 "n" ,那么它就有可能因為緩沖區沒有滿而導致不輸出。如果加了后面的“n”,就會強制把字符串從緩沖區中輸出。而有時候,n 也不能強制從緩沖區輸出。(這個緩沖區其實很像我們在文件io這篇文章中提到的8k的緩沖區,機制都是一樣的,緩沖區滿/調用flush/進程結束 使數據刷新,關于這里 https://blog.csdn.net/u013790372/article/details/54645860 這篇文章寫得不錯,大家可以了解一下)
后面改為:
直接向標準輸出寫就ok了。
正確輸出如下:
即多次讀取數據,直到把緩沖區的數據讀完。
而由前面的知識,我們知道epollwait調用次數越多,系統的開銷越大。(總結一下就是:epollwait會進入內核空間檢測內核文件描述符的事件,這里就有cpu的在系統調用時產生的上下文切換,估計還有些我不知道的東西。。)
然后,epoll的水平觸發模式就到這里了。主要就是緩沖區有數據就epollwait返回。
然后來看邊沿觸發模式(ET)
特點:
客戶端給server發數據,發一次數據,server的epoll_wait返回一次,不在乎客戶端的緩沖區的數據是否讀完。(其它客戶端的連接也會導致返回和數據讀取,不要忘了前面的基礎)
也就是說,緩沖區里面可能存有大量的客戶端之前發來的數據。這樣就不太好。
修改后:如下圖:也就是設置了結構里的events。或上 epollet ,這樣就能變為邊沿觸發模式。這里是添加新連接的時候的修改,而代碼前面一開始把監聽集合添加進紅黑樹的時候,也要修改這個events。(此時代碼的buf大小依然是5個字節)
演示:
具體就是:客戶端一次性輸入比較多的字符,服務端連進一個客戶端,就返回一次epollwait,然后讀取一次緩沖區的5個字節。后面客戶端再次發送,還是讀取緩沖區的前5個字節,也就是上次服務器接收了但是沒有從緩沖區刷新出來的數據,而此次發送的數據被放在了緩沖區的最后面,所以沒顯示出來。(這個要理解清楚!)
現在來總結一下兩者的區別:LT是我服務端一次只讀這么多,我可以多次讀你很多數據。消耗系統資源多。 ET是客戶端發送一次或者連接一次我就返回一次,然后慢慢讀。理解了前面的,其實基本上全部說下來都沒關系。
然后我們來看看怎么解決EL讀取的總是緩沖區的舊數據的問題:
下面這張圖片改為while(recv())就可以了,也就是說,只要緩沖區有數據,我就把它全部讀出來。這樣就不會說每次都讀取很少的字節了。
可是這又產生了一個問題: 這樣的話,因為io默認設置是阻塞的啊,也就是說fd默認阻塞屬性啊。那么在while(recv())這里,讀完數據之后,如果沒有數據來的話,就會阻塞在這里。那么就不會繼續返回到外面的while(1)的大循環中了。也就不會再epollwait函數那里阻塞了(也就是沒有辦法委托再去內核檢測了),這就完全把整個程序的功能弄錯了。
所以,我們要解決阻塞問題,所以,必須設置fd為非阻塞。
所以,我們前面介紹其實是邊緣阻塞模式。
接下來我們要介紹邊緣非阻塞模式。
我們要解決的是如何設置fd的非阻塞的屬性。
讓我們先來看看如何設置非阻塞(先脫離前面的問題先)
1.open()函數
它可以設置flags
必須 O_WDRE | O_NONBLOCK
終端文件 : /dev/tty 當前所操作的終端。前面已經講得很多次了。(也就是說,終端文件就可以使用open來設置非阻塞)
比如 fd=open("/dev/tty" ,O_WDRE | O_NONBLOCK); 那么返回正常的話就是返回的fd就是非阻塞的。這個是針對終端而言的。
2. 由于上面的方法針對的是終端,而我們現在要操作的是內核緩沖區。
fcntl :1.復制文件描述符 2.設置文件描述符的屬性為非阻塞。(第二個是我們要用的)
使用流程
int flag=fcntl(fd.F_GETFL); 第一步:獲取文件描述符的flags屬性 get flag
然后我們要把非阻塞屬性給它設置進去
flag |=O_NONBLOCK 修改flag屬性
fcntl(fd,F_SETFL.flags) 此處是吧flag屬性又設置進去
下面是視頻中的文檔:
然后我們來到epoll使用代碼上看怎么改:下面這里我們只修改已連接的文件描述符,不修改前面的監聽描述符了。
然后是接收數據那里:
注意此時的buf還是5個字節!
下面先是len>0的情況,len>0說明此時緩沖區有數據可讀,所以不斷地循環從緩沖區中接收5個字節,然后打印到終端,并且返回給客戶端。
下面是len返回為0和-1的情況。len等于0說明客戶端斷開了連接,此時把此客戶端對應的文件描述符從內核中的紅黑樹移除,然后關閉掉該文件描述符。
len等于-1。此時會有一個errno的量(好像在線程那里看見過),它如果等于EAGAIN,就說明此時是緩沖區數據讀完,那么就跳出這里,回到epollwait中阻塞;如果不等于,那么就是出現異常了,那就退出程序。
成功的返回:
這樣的話,我們就很完美地結束了epoll的邊緣非阻塞的編程。
這樣的話,邊緣非阻塞效率最高。
既沒有LT的多次重復調用epoll_wait的系統開銷大,也沒有ET的讀取到的都是舊數據或者是ET的阻塞。 客戶端一次發送數據,我就一次性把發到緩沖區的數據全部接收并且處理。
簡直perfect。
感覺那個視頻很不錯,最起碼epoll的幾種模式講得很不錯,推薦一下。
如果大家對java的nio有了解的話,我強烈建議來看看我這篇文章: 何柄融:nio基礎和手寫一個基于nio的demo , 然后你就會發現epoll和nio的編程居然是一模一樣的,沒任何差別。
歡迎交流討論。
總結
以上是生活随笔為你收集整理的字符串缓冲区太小怎么解决_epoll的两种模式 ET和LT printf的缓冲区问题 边缘非阻塞模式...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python tcp服务器_Python
- 下一篇: python flag函数_Python