17水平觸發模式下資料傳送
阿新 • • 發佈:2022-03-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。