這是學(xué)習(xí)網(wǎng)絡(luò)編程后寫(xiě)的一個(gè)練手的小程序,可以幫助復(fù)習(xí)socket,I/O復(fù)用,非阻塞I/O等知識(shí)點(diǎn)。

通過(guò)回顧寫(xiě)的過(guò)程中遇到的問(wèn)題的形式記錄程序的關(guān)鍵點(diǎn),最后給出完整程序代碼。

0. 功能

編寫(xiě)一個(gè)簡(jiǎn)易群聊程序,程序具備的基本功能:

服務(wù)器:支持多個(gè)客戶端連接,并將每個(gè)客戶端發(fā)過(guò)來(lái)的消息發(fā)給所有其他的客戶端

客戶端:能夠連接服務(wù)器,并向服務(wù)器發(fā)送消息,同時(shí)接收服務(wù)器發(fā)過(guò)來(lái)的任何消息

 

1. Server I/O模型

采用事件驅(qū)動(dòng)(I/O復(fù)用)+ 非阻塞I/O的模型,即Reactor模式。I/O復(fù)用采用linux下的epoll機(jī)制。

相關(guān)API介紹見(jiàn)最后,先梳理幾個(gè)寫(xiě)程序的時(shí)候想到的問(wèn)題。

1.1  I/O復(fù)用為什么搭配非阻塞I/O?(select/epoll返回可讀后還用非阻塞是不是沒(méi)有意義?)

  select/epoll返回了可讀,并不一定代表能讀,在返回可讀到調(diào)用read函數(shù)之間,是有時(shí)間間隙的。內(nèi)核可能把數(shù)據(jù)丟失,也可能存在比如多個(gè)線程監(jiān)聽(tīng)該socket,

數(shù)據(jù)被別人讀走的情況。所以這里使用非阻塞I/O是有意義的。

可以參考知乎這個(gè)問(wèn)題  https://www.zhihu.com/question/37271342

1.2 epoll的條件觸發(fā)LT(水平觸發(fā))和邊緣觸發(fā)ET區(qū)別,如何正確地處理ET模式下的讀操作?

簡(jiǎn)單講,以讀取數(shù)據(jù)操作舉例。條件觸發(fā),只要輸入緩沖中還有數(shù)據(jù),就會(huì)以事件方式再次注冊(cè);

而邊緣觸發(fā)中僅在輸入緩沖收到數(shù)據(jù)時(shí)注冊(cè)一次該事件(你沒(méi)讀完也epoll_wait也不再返回了)。

所以如果使用邊緣觸發(fā)發(fā)生輸入相關(guān)事件,需要讀取輸入緩沖中的全部數(shù)據(jù)。方法是一直讀,直到read返回-1,并且變量errno中的值為EAGAIN,說(shuō)明沒(méi)有數(shù)據(jù)可讀

所以在這里再次考慮一下1.1中的問(wèn)題,epoll如果采用邊緣觸發(fā),更要使用非阻塞I/O,否則可能就因?yàn)闊o(wú)數(shù)據(jù)可讀阻塞整個(gè)線程了。

1.3  select與epoll的差別

 一個(gè)老生常談的問(wèn)題,select函數(shù)效率低主要有以下兩個(gè)原因,首先是每次調(diào)用select函數(shù)時(shí)需要向操作系統(tǒng)傳遞監(jiān)視對(duì)象信息,其次是調(diào)用后針對(duì)所有文件描述符的循環(huán)語(yǔ)句。

第一點(diǎn)對(duì)效率的影響更大。

此外,epoll還支持ET模式,而select只支持LT模式。

但select也有優(yōu)點(diǎn),比如兼容性好(大多數(shù)操作系統(tǒng)支持),在服務(wù)端介入者少的情況下仍然可以考慮使用s