1. 程式人生 > 其它 >17水平觸發模式下資料傳送

17水平觸發模式下資料傳送

一、理解socket可寫的概念

每一個tcp連線(socket),都會有一個接收緩衝區  和 一個傳送緩衝;傳送緩衝區預設大小一般10幾k,
接收緩衝區大概幾十k,setsocketopt()來設定;"send(),write()傳送資料時",
實際上這兩個函式是把資料放到了傳送緩衝區,之後這兩個函式返回了;客戶端用recv(),read();
"而如果"伺服器端的傳送緩衝區"滿了",那麼伺服器再呼叫send(),write()傳送資料的時候,
那麼send(),write()函式就會返回一個"EAGAIN";EAGAIN不是一個錯誤,
只是示意傳送緩衝區已經滿了,"遲一些"再呼叫send(),write()來發送資料吧;

二、問題和解決方案

針對 當socket可寫的時候【傳送緩衝區沒滿】,會不停的觸發socket可寫事件 ,我們提出兩種解決方案【面試可能考試】;
兩種解決方案,來自網路,意義在於我們可以通過這種解決方案來指導我們寫程式碼;

1)第一種最普遍的解決方案:
需要向socket寫資料的時候把socket寫事件通知加入到epoll中,等待可寫事件,當可寫事件來時作業系統會通知咱們;
此時咱們可以呼叫wirte/send函式傳送資料,當傳送資料完畢後,把socket的寫事件通知從紅黑樹中移除;
缺點:即使傳送很少的資料,也需要把事件通知加入到epoll,寫完畢後,有需要把寫事件通知從紅黑樹幹掉,
對效率有一定的影響【有一定的操作代價】

2)改進方案;//==========<========
開始不把socket寫事件通知加入到epoll,當我需要寫資料的時候,直接呼叫write/send傳送資料;
如果返回了EAGIN【傳送緩衝區滿了,需要等待可寫事件才能繼續往傳送緩衝區裡寫資料】,
此時,我再把寫事件通知加入到epoll,此時,就變成了在epoll驅動下寫資料,全部資料傳送完畢後,
再把寫事件通知從epoll中幹掉;
優點:資料不多的時候,可以避免epoll的寫事件的增加/刪除,提高了程式的執行效率;

準備採用2)改進方案來指導咱們後續傳送資料的程式碼;

三、實際解決方案

1.在業務邏輯處理完,_HandleRegister(),需要給一連線傳送資料時,在這一步,是已經把資料包打包好了,
其中資料包:訊息頭+包頭+包體。三部分組成。
2.傳送資料包,呼叫:msgSend(p_sendbuf);在這個函式中,會把資料推入傳送訊息佇列,並通過“訊號量”的
sem_post(&m_semEventSendQueue) 來通知 ServerSendQueueThread()(服務傳送訊息佇列的執行緒)流程走下來,
在這個執行緒中,有一個sem_wait()函式,這個函式就是用來接收通知的。
3.在這裡之前,需要對訊號量進行初始化,互斥量進行初始化,執行緒的建立,並且這個對各種的物件的銷燬
放在子程序退出的地方。
4.執行緒函式中有sem_wait(&pSocketObj->m_semEventSendQueue),在經過一系列判斷之後,確定的資料包是
必須要傳送的,這裡傳送的資料包只包括包頭和包體,訊息頭在判斷有效性中已經使用了。
5.pSocketObj->sendproc(p_Conn,p_Conn->psendbuf,p_Conn->isendlen);
是第一步傳送資料到套接字的傳送緩衝區,只用傳送緩衝區滿了,那麼才把需要傳送的資料放在epoll管理中,
pSocketObj->ngx_epoll_oper_event()。
6.傳送緩衝區滿,就一直只發送到epoll中,一旦緩衝區有可寫空間,那麼,就會呼叫
ngx_write_request_handler()資料傳送時的寫處理函式.
7.ngx_write_request_handler()函式中有函式sendproc(),只要epoll中有資料,
那麼就不會把epoll時間的可寫事件去除,在這個過程中,將只要傳送快取區有空餘那麼就可以把資料
從epoll傳送到傳送緩衝區。而一旦epoll中沒有資料了,那我們就把epoll的可寫事件擦除了。
8.到這裡就一個過程就OK了。

四、測試心得

LT模式下,傳送資料採用的 “改進方案” 是非常有效的,在很大程度上提高了效率;
伺服器的套接字傳送緩衝區大概10-幾10K,但是實際測試的時候,成功的傳送出去了1000多k資料才報告發送緩衝區滿;當傳送端呼叫send()傳送資料時,"作業系統底層"已經把資料傳送到了 該連線的接收端(客戶端) 的接收快取,這個接收快取(客戶端的作業系統)大概有幾百K。