我一直堅(jiān)信,如果不是處理大規(guī)模客戶端連接,是不需要使用epoll和IOCP的。我傾向于簡(jiǎn)單的東西,所以我一直用著select。
一直以來,我的網(wǎng)絡(luò)程序結(jié)構(gòu)就是在每一幀的開始select,有什么消息就處理一下,然后跑程序的主邏輯。我覺得這個(gè)結(jié)構(gòu)挺好,單線程,簡(jiǎn)單、明了、優(yōu)雅。
不過最近有頭兒告訴我,這個(gè)事情雖然可以,但是感覺上不太對(duì)頭,網(wǎng)絡(luò)組件的工作應(yīng)該是獨(dú)立的,不可以占用主邏輯的時(shí)間。
好吧,我改成多線程就是了。一個(gè)主線程,負(fù)責(zé)處理客戶端消息和運(yùn)算主邏輯;一個(gè)網(wǎng)絡(luò)線程,負(fù)責(zé)從網(wǎng)絡(luò)上讀取數(shù)據(jù)和將數(shù)據(jù)發(fā)送到網(wǎng)絡(luò)上,基本上就是select,recv,send三調(diào)用。
這里出現(xiàn)了第一個(gè)問題:主線程和網(wǎng)絡(luò)線程之間如何進(jìn)行數(shù)據(jù)交換?主線程中待發(fā)送的數(shù)據(jù)需要交給網(wǎng)絡(luò)線程做實(shí)際的發(fā)送,網(wǎng)絡(luò)線程接收到的數(shù)據(jù)需要交給主線程處理。
基于盡可能短的lock-time這一原則,我給主線程和網(wǎng)絡(luò)線程各分配了一個(gè)完全一樣的容器,這個(gè)容器由各線程獨(dú)自享有,容納發(fā)送和接收的數(shù)據(jù)。
然后在特定的時(shí)候,lock主其中一個(gè)容器,進(jìn)行數(shù)據(jù)拷貝即可——將欲發(fā)送的數(shù)據(jù)從主線程容器拷貝到網(wǎng)絡(luò)線程的容器,將收到的數(shù)據(jù)從網(wǎng)絡(luò)線程的容器拷貝到主線程的容器。
這一lock只有拷貝工作,時(shí)間上應(yīng)該是十分短暫的。
第二個(gè)問題:由誰(shuí)負(fù)責(zé)這個(gè)拷貝,主線程還是網(wǎng)絡(luò)線程?負(fù)責(zé)拷貝的線程,必然去lock另一個(gè)線程的容器。
我選擇了主線程負(fù)責(zé)拷貝操作。在每幀的開始,鎖住網(wǎng)絡(luò)線程的容器,將它收到的數(shù)據(jù)拷出來,將要發(fā)送的數(shù)據(jù)拷進(jìn)去,解鎖,然后處理收到的消息。網(wǎng)絡(luò)線程則需要在操作自己的容器的時(shí)候加鎖。
好處是,主線程的 send_packet 操作不需要加鎖,并且收到的數(shù)據(jù)是拷出來就消耗掉。
順便也想想網(wǎng)絡(luò)線程負(fù)責(zé)拷貝的情形,在select之前,鎖住主線程的容器,將欲發(fā)送的數(shù)據(jù)拷進(jìn),解鎖;然后是select,recv,send;然后再次鎖住主線程的容器,將收到的數(shù)據(jù)拷出。相應(yīng)的,主線程需要在操作自己容器的時(shí)候加鎖。
看起來我并不想主線程在一幀內(nèi)有太多次的加鎖解鎖操作,因此就選擇了第一個(gè)方案。
至此,程序跑起來了。不過出現(xiàn)了一個(gè)單線程所沒有的新問題——CPU占用率太高了。
原因應(yīng)該是,select能掛起程序,所以單線程的時(shí)候,程序多多少少總會(huì)有掛起的機(jī)會(huì);但是多線程以后,主線程就跟while ( true )差不多,浪費(fèi)了太多的資源。
因此,讓主線程在每幀也睡一會(huì)就好了。游戲的主邏輯是限幀的,一般每秒25幀,稱邏輯幀。但是處理網(wǎng)絡(luò)消息不是限幀的,而是希望能盡可能快的處理他們,因此處理網(wǎng)絡(luò)消息是在實(shí)際幀中進(jìn)行的。
通常游戲主邏輯的一次tick并不能完全消耗掉一個(gè)邏輯幀的時(shí)間,因此讓主線程在邏輯幀剩下的時(shí)間里睡上一覺就好。
第三個(gè)問題是:如何讓主線程在剩下的邏輯幀時(shí)間里掛起,并在有網(wǎng)絡(luò)消息的時(shí)候立即激活?
信號(hào)/EVENT——主線程在進(jìn)行容器的數(shù)據(jù)拷貝之前,如果自己沒有欲發(fā)送的數(shù)據(jù),則等待信號(hào),等待的時(shí)間是上一個(gè)邏輯幀所剩余的時(shí)間。相應(yīng)的網(wǎng)絡(luò)線程中,如果收到新的數(shù)據(jù),則激活這個(gè)信號(hào),那么主線程會(huì)被立即喚醒。
等待超時(shí)或者被喚醒后,就會(huì)執(zhí)行數(shù)據(jù)拷貝和消息處理。這樣,既實(shí)現(xiàn)了sleep,又兼顧了即時(shí)反應(yīng)能力。
編譯運(yùn)行,程序看起來挺穩(wěn)定,CPU占用率為0.。。。。。。新項(xiàng)目,邏輯上幾乎啥都沒有呢。
posted on 2009-01-03 16:54
LOGOS 閱讀(7951)
評(píng)論(8) 編輯 收藏 引用