曾雨陽 趙建喆
摘要:隨著網(wǎng)絡(luò)的迅速發(fā)展,多人在線游戲備受歡迎。選取抗擊生物病毒的主題,通過多線程等技術(shù)開發(fā)了游戲,多個(gè)客戶端可以通過socket連接服務(wù)器,實(shí)現(xiàn)通信與交互。旨在借助多人在線游戲的特點(diǎn),傳遞抗擊生物病毒的精神力量。
關(guān)鍵詞:多進(jìn)程;多線程;多人在線游戲
中圖分類號:TP311? ? ? 文獻(xiàn)標(biāo)識碼:A
文章編號:1009-3044(2021)14-0047-02
Abstract: With the rapid development of the Internet, multiplayer online games are very popular. Fighting biological virus is selected as the topic of the game. Through multi-threading technology, the game is developed. To achieve communication and interaction, multiple clients can be connected to the server via socket. The spirit of fighting biological virus is transferred using the characteristics of multiplayer online games.
Key words: multi-process; multi-threading; multiplayer online game
1 背景
本文設(shè)計(jì)并實(shí)現(xiàn)了一個(gè)基于多進(jìn)程、多人在線操作的抗擊生物病毒游戲。
玩家可以自由控制自己的“醫(yī)生”角色,當(dāng)與對面來的病毒相遇時(shí),玩家醫(yī)生血量減少;當(dāng)與對面來的疫苗或口罩道具相遇時(shí),玩家會(huì)獲得積分。
游戲?qū)崿F(xiàn)了以下功能:上下左右按鍵自由控制“醫(yī)生”角色的移動(dòng)。隨機(jī)生成病毒、疫苗和口罩道具,這些障礙物會(huì)以不同的速度勻速自上而下移動(dòng)。玩家血量減少、玩家積分、失敗和重新開始等。界面顯示生命值和得分。
游戲具有多人聯(lián)機(jī)協(xié)同的功能。客戶端使用Java與Swing實(shí)現(xiàn),服務(wù)端使用C/C++混合編程,實(shí)現(xiàn)了多進(jìn)程、多線程、進(jìn)程間通信和多路復(fù)用等技術(shù)[1-2]。
2 系統(tǒng)設(shè)計(jì)
2.1 架構(gòu)設(shè)計(jì)
游戲客戶端使用Java Swing作為前端GUI,使用Java作為客戶端邏輯,服務(wù)端使用C/C++混合編程實(shí)現(xiàn),客戶端與服務(wù)端使用Socket TCP協(xié)議連接[3]。架構(gòu)設(shè)計(jì)如圖1所示。
客戶端不負(fù)責(zé)進(jìn)行關(guān)鍵邏輯判斷,只負(fù)責(zé)接收消息并將處理后的消息展示到GUI界面,服務(wù)端負(fù)責(zé)實(shí)現(xiàn)病毒、口罩和疫苗道具等障礙物對象的生成、相遇判斷等,有效地避免了客戶端可能發(fā)生的作弊現(xiàn)象,保證了游戲的公平性與安全性。
服務(wù)端使用多進(jìn)程、多線程、進(jìn)程間通信和多路復(fù)用技術(shù),實(shí)現(xiàn)思路如圖2所示。
子進(jìn)程用于運(yùn)行游戲,異常退出等對于主進(jìn)程的影響小。父子進(jìn)程之間使用信號機(jī)制進(jìn)行通信,子進(jìn)程使用多線程同時(shí)處理數(shù)據(jù),使用多路復(fù)用輪詢所有客戶端[4]。
2.2 server服務(wù)器端的邏輯
2.2.1 main()主運(yùn)行進(jìn)程
初始化內(nèi)存,給服務(wù)器網(wǎng)絡(luò)地址結(jié)構(gòu)體sockaddr_in設(shè)置TCP協(xié)議,設(shè)置為可以連接任意的IP地址,服務(wù)器端口號設(shè)置為8888。按照socket()、bind()、listen()和accept()的順序監(jiān)聽端口。
socket()獲取服務(wù)器端socket的文件描述符,采用IPv4和TCP協(xié)議。bind()將服務(wù)器端socket綁定到服務(wù)器的地址和端口上(服務(wù)器網(wǎng)絡(luò)地址結(jié)構(gòu)體sockaddr_in)。listen()監(jiān)聽8888端口號上的連接請求,進(jìn)入的連接請求在使用系統(tǒng)調(diào)用accept()應(yīng)答之前要在進(jìn)入隊(duì)列中等待。signal(SIGUSR1, father_listen)設(shè)置信號的處理函數(shù)。
2.2.2 調(diào)用father_listen()函數(shù)
父進(jìn)程監(jiān)控為father_listen()。accept()處理連接8888端口的請求,獲取客戶端的socket文件描述符(用來調(diào)用send()和recv()),并放入玩家列表player_list中。對該客戶端連接,fork()創(chuàng)建子進(jìn)程,子進(jìn)程中啟動(dòng)主客戶端進(jìn)程main_client。掛起父進(jìn)程,直到收到SIGUSR1的信號。
2.2.3 main_client()主客戶端進(jìn)程
創(chuàng)建兩個(gè)線程,分別用于調(diào)整障礙物移動(dòng)obstacle_run和獲取新客戶端get_new_client。初始化障礙物列表,包括其種類和初始位置。服務(wù)器主進(jìn)程通過多路復(fù)用以監(jiān)聽客戶端。多路分離函數(shù)select()實(shí)現(xiàn)多路復(fù)用,在同一個(gè)線程內(nèi)同時(shí)處理多個(gè)請求,不斷輪詢所負(fù)責(zé)的socket。監(jiān)視fd文件描述符是否可讀取,是否有數(shù)據(jù)到來。當(dāng)可讀取時(shí),用recv()函數(shù)從TCP連接的另一端接收數(shù)據(jù),存放在長度為1024的buff緩沖區(qū)中。以逗號為分界,切割客戶端發(fā)來的數(shù)據(jù)字符串,處理數(shù)據(jù)。根據(jù)這些數(shù)據(jù),在服務(wù)器中更新對應(yīng)ID的玩家數(shù)據(jù)。連接成功后為客戶端創(chuàng)建玩家控制對象,初始坐標(biāo)隨機(jī)。并用sendData()將數(shù)據(jù)發(fā)回客戶端,通知連接成功。
3 關(guān)鍵部分程序?qū)崿F(xiàn)
3.1 Socket通信
本游戲采用了Java Swing作為客戶端,利用Socket在兩種語言間發(fā)送消息[5]。在連接client和server的過程中,Java與C發(fā)送接收數(shù)據(jù)時(shí)應(yīng)該將String類型的字符串中包含的字符轉(zhuǎn)換成byte類型,存入到一個(gè)byte[]數(shù)組中,且應(yīng)該指定編碼形式,否則會(huì)出現(xiàn)亂碼的現(xiàn)象(Java默認(rèn)使用UTF-8,C++默認(rèn)使用GBK),例如,使用語句toServer.write((ID + ",connected,").getBytes("GB2312"))。
在服務(wù)端,獲取客戶端的socket文件描述符作為客戶端新的連接,其中包含客戶端ip和port,具體語句如下:
client_socket = accept(server_socket, (struct sockaddr *)&remote_addr, (socklen_t *)&client_len);
服務(wù)端將這個(gè)請求的新文件描述符放到player_list中,視為一個(gè)新的玩家對象。如果客戶端沒有新的連接,則關(guān)閉客戶端socket,并在父進(jìn)程中,把調(diào)用進(jìn)程掛起pause(),直至捕獲到一個(gè)SIGUSR1的信號。
在客戶端,通過while(true)循環(huán)讀取服務(wù)器傳來的數(shù)據(jù),通過String(readBuff).trim()去除數(shù)據(jù)中的空白符,處理并判斷服務(wù)器返回的數(shù)據(jù),如果判斷游戲結(jié)束,則顯示“再來一次”按鈕,并結(jié)束游戲,否則繼續(xù)顯示游戲操作提示信息和玩家當(dāng)前坐標(biāo)。
3.2 多線程
通過pthread_create()創(chuàng)建服務(wù)器全局障礙物調(diào)整的線程,如果創(chuàng)建成功,該函數(shù)返回值為零。同樣方法創(chuàng)建第二個(gè)用于監(jiān)聽是否有新的客戶端傳入的線程。多線程以實(shí)現(xiàn)病毒、疫苗和口罩道具的勻速自上而下移動(dòng),以及多人在線操作的功能。
3.3 多路復(fù)用
使用select()函數(shù)實(shí)現(xiàn)多路復(fù)用,以監(jiān)視fd文件描述符是否可以讀取。select()函數(shù)會(huì)不斷輪詢所負(fù)責(zé)的socket,監(jiān)測是否有數(shù)據(jù)到來。用recv()函數(shù)從TCP連接的另一端接收數(shù)據(jù),存放在長度為1024的buf緩沖區(qū)中。具體實(shí)現(xiàn)方法如下:
ret_val = select( nfds: player_list[client_num - 1] +1, &rfds, writefds: NULL, exceptfds: NULL, stv);
recv(player_list[i], buf, n:1024, flags: 0)。
3.4 病毒、口罩和疫苗道具等障礙物的位置移動(dòng)
使用obstacle_run()函數(shù)實(shí)現(xiàn)障礙物的移動(dòng)。通過隨機(jī)數(shù)發(fā)生器的初始化函數(shù)srand(),使用系統(tǒng)時(shí)間作為種子,以隨機(jī)產(chǎn)生障礙物的種類。不同種類障礙物設(shè)置不同的移動(dòng)步長,產(chǎn)生不同的移動(dòng)速度效果。
遍歷所有的障礙物對象,將障礙物所有服務(wù)端處理后的數(shù)據(jù)(如ID、位置、種類等)發(fā)送給玩家列表里所有的玩家客戶端,以實(shí)現(xiàn)所有病毒、口罩和疫苗道具等障礙物的位置移動(dòng)。
3.5 相遇檢測
假設(shè)兩個(gè)移動(dòng)對象中心點(diǎn)的距離為變量a,兩個(gè)移動(dòng)對象的邊緣矩形中心點(diǎn)對角線的距離為變量b,只要a小于b,那么就判定兩者相遇。
4 總結(jié)
利用Java Swing構(gòu)建界面,利用Java和C兩種語言分別搭建客戶端與服務(wù)端并進(jìn)行交互,趣味性強(qiáng)。游戲?yàn)閷?shí)時(shí)網(wǎng)絡(luò)游戲,多個(gè)玩家連接服務(wù)器進(jìn)行游戲,合理有效地使用了多進(jìn)程、多線程、信號機(jī)制和多路復(fù)用等技術(shù),數(shù)據(jù)交互具有較好的實(shí)時(shí)性和準(zhǔn)確性。游戲的業(yè)務(wù)部分都在服務(wù)器端處理,而客戶端主要用于接收用戶的輸入及顯示,保證了游戲的公平性和安全性。
服務(wù)端具有一定的健壯性,具有較好的性能,游戲邏輯較為復(fù)雜(隨機(jī)生成障礙物移動(dòng)對象、鍵盤控制玩家對象、障礙物相遇檢測等)。選擇抗疫題材,玩家通過此游戲可以獲得積極向上,共克時(shí)艱的昂揚(yáng)精神力量,這樣的游戲可以為抗擊疫情貢獻(xiàn)一份精神力量。
參考文獻(xiàn):
[1] 呂佳歡,蔣富,金易.基于Java的炸彈人游戲設(shè)計(jì)[J].電腦知識與技術(shù),2020,16(25):97-98.
[2] Fang W J,Wang C L,Lau F C M.On the design of global object space for efficient multi-threading Java computing on clusters[J].Parallel Computing,2003,29(11/12):1563-1587.
[3] 傅玥,蔡興富.Socket網(wǎng)絡(luò)編程-基于TCP協(xié)議或UDP協(xié)議[J].中國新通信,2020,22(8):57-58.
[4] Himang M M,Himang C M,Ceniza A M,et al.Using an extended technology acceptance model for online strategic video games[J].International Journal of Technology and Human Interaction,2021,17(1):32-58.
[5] 劉賢梅,劉俊,賈迪.Unity引擎下多人在線網(wǎng)絡(luò)游戲的設(shè)計(jì)與開發(fā)[J].計(jì)算機(jī)系統(tǒng)應(yīng)用,2020,29(5):103-109.
【通聯(lián)編輯:謝媛媛】