online游戲服務器架構--網絡架構

啟動:父進程啟動;子進程啟動;網絡架構。

每個父進程攜帶N個子進程,子進程負責處理業務邏輯和其它數據,而父進程只是將客戶端的請求路由到各個子進程,路由的策略非常簡單,父進程將請求包按照輪流的法則分發到這N個子進程。

子進程接收到請求包的時候,它便開始處理,處理完后再將結果反還給父進程。注意,子進程并不處理網絡連接,它并不知道請求包的源的信息,它只處理業務,相反地,父進程并不知道請求包的內容,它的任務就是處理連接。

父子進程之間通過共享內存進行通信,具體來講就是父進程將請求包放入和對應子進程共享的內存中,然后通過一個管道喚醒子進程,子進程探測到管道消息以后就從共享內存將請求拉出來然后進行處理,處理完畢后再將結果放回到共享內存,然后同樣喚醒父進程,父進程被喚醒之后便拉出子進程的回復數據,最后通過它自己保存的連接返回給客戶端。

這個服務器解除了接收數據和處理數據之間的耦合,便于進行任何一邊的擴展,不像那種消息映射服務器,直接在本進程內部通過分發回調函數來處理業務邏輯,或者用線程的方式進行處理,線程的方式雖然解決了吞吐量的問題,但是無法解決穩定性的問題,必須默認所有的數據都是安全的或者開發出繁復的處理邏輯來處理異常情況,額外增加了服務器的負擔。子進程的關于業務邏輯的處理方式非常類似于那種消息映射服務器,不同之處在于,典型的消息映射服務器是從網絡上將數據拉回,而該online服務器卻是從共享內存中將數據拉回,多了共享內存這么一個中間層。

關于業務邏輯的處理還有一個類似的層次,就是online子進程和數據庫之間的關系,它們通過一個數據庫代理(DBProxy)來將子進程的處理邏輯和數據庫之間的耦合解除,并且這數據庫代理還可以隱藏數據庫的訪問接口,只有代理知道后端連接了什么數據庫,而處理邏輯不必知道,它只需要將訪庫請求作為網絡請求發送給數據庫代理就好了,然后用消息映射服務器的方式處理數據庫代理的回復。數據庫只管保存數據,而不管這些數據之間的除了關系模型之外的額外事宜,比如有效性驗證之類的,所有的數據驗證和處理工作在online子進程那里進行。這樣處理的優點就是易于擴展新業務,缺點就是要來回幾次的進程訪庫,因為每次只取當次的數據,在業務處理過程中可能還需要別的數據…不過缺點可以通過高速網絡和高性能數據庫以及數據庫代理服務器來彌補。

for ( ; i != bc->bind_num; ++i ) {

bind_config_elem_t* bc_elem = &(bc->configs[i]);

shmq_create(bc_elem); //通過mmap的方式創建共享內存

… //錯誤處理

} else if (pid > 0) {

close_shmq_pipe(bc, i, 0);

do_add_conn(bc_elem->sendq.pipe_handles[0], PIPE_TYPE_FD, 0, bc_elem);

} else {

run_worker_process(bc, i, i + 1);

}

}

run_worker_process函數開始了子進程的歷程,可以看到最后這個函數調用了一個叫做net_loop的無限循環,這個函數在父進程初始化完畢后也最終調用,原型如下:

int net_loop(int timeout, int max_len, int is_conn);

該函數通過最后一個參數is_conn來區分是子進程還是父進程,函數內部實現也是通過該參數一分為二的,online的父進程負責網絡收發,主要是基于epoll的,epoll已經被證明擁有很高的性能,在linux平臺上的性能測試已經超越了原來的poll/select模型,甚至比windows的IO完成端口在大負載,高并發環境下表現更加出色。在net_loop中用epoll_wait等待有事件的文件描述符,然后判斷文件描述符的類型(套結字在創建之初就將描述符和類型等信息打包成一個數據結構了),如果是管道文件的事件,那么肯定是不需要處理數據的,僅僅察看事件類型以及判斷是否父進程就可以判斷發生了什么事了,由于子進程根本就不會將套結字描述符加入到epoll監控向量,因此子進程只能有管道類型的事件發生,注意這里不涉及online子進程和DB的通信。接下來的net_loop中關于epoll的處理流程就是父進程的事了,具體過程就是處理套結字類型的文件描述符了,就是從套結字接收數據,然后放到和一個子進程共享的內存區域中,最后往子進程管道里寫一個數據,告訴子進程現在該處理業務邏輯了,子進程在net_loop中監控到管道事件之后,最終調用net_loop最后的handle_recv_queue()函數,該函數開始處理業務邏輯:

if(!is_conn) {

#ifdef USE_CMD_QUEUE

handle_cmd_busy_sprite(); //handle the busy sprite list first

#endif

handle_recv_queue();

handle_timer();

}

以上是net_loop的大致流程,對于父進程如何將請求路由給子進程有兩種選擇,一種是父進程網絡服務器按照某種策略比如負載均衡采取輪換路由,另一種就是將選擇留給用戶,用戶登錄online之前首先登錄一個switch服務器,自行選擇online子進程,每個online子進程都有一個ID,用戶選擇后就用這個ID作為數據打包,另外switch服務器上的online子進程鏈表中包含了足夠的其對應于父進程的IP地址和端口信息,然后向online子進程對應的父進程發送LOGIN包,父進程在net_loop中最終調用net_recv,然后解出LOGIN包,由于該包中包含了其子進程的id,而這個id又和其與子進程的共享內存相關聯,一個數據結構最起碼關聯了父進程接收的套結字描述符,子進程ID,父子進程的共享內存緩沖區這三個元素。

關鍵數據結構:

typedef struct bind_config_elem {

int online_id;

char online_name[16];

char bind_ip[16]; //邦定的ip地址

in_port_t bind_port; //邦定的端口

char gameserv_ip[16]; //游戲服務器的ip

in_port_t gameserv_port;

char gameserv_test_ip[16];

in_port_t gameserv_test_port;

struct shm_queue sendq; //發送緩沖區,被分割成一個一個的塊,因此叫隊列

struct shm_queue recvq; //接收緩沖區,被分割成一個一個的塊,因此叫隊列

} bind_config_elem_t;

該結構描述了每一個傳輸套結字都應該擁有的一個結構,也就是每一個子進程一個這樣的結構

typedef struct bind_config {

int online_start_id;

int bind_num;

bind_config_elem_t configs[MAX_LISTEN_FDS];

} bind_config_t;

這個結構是上面結構的容器,main中的bind_config_elem_t* bc_elem = &(bc->configs[i]);體現了一切,所有的一切都是從配置文件中讀取的。

typedef struct shm_head {

volatile int head;

volatile int tail;

atomic_t blk_cnt;

} __attribute__ ((packed)) shm_head_t;

這個結構分割了一個緩沖區,將一個連續的緩沖區分割成了一個隊列

struct shm_queue {

shm_head_t* addr;

u_int length;

int pipe_handles[2];

};

這個結構代表了一個緩沖區,分割的過程在shm_head_t中體現。

struct epinfo {

struct fdinfo *fds;

struct epoll_event *evs;

struct list_head close_head;

struct list_head etin_head;

int epfd;

int maxfd;

int fdsize;

int count;

};

這個結構代表了epoll事件。

在LOGIN包被父進程解析到的時候:

if ((ntohl(proto->cmd) == PROTO_LOGIN) && (epi.fds[fd].bc_elem == 0) )為真,接著:

uint16_t online_id = ntohs(*(uint16_t*)(proto->body)); //得到用戶選擇的online_id

epi.fds[fd].bc_elem = &(bc->configs[online_id - bc->online_start_id]); //得到該id對應的config結構體。

得到了bind_config_elem_t結構體之后就可以將請求包轉發到從該結構體中取出的共享內存緩沖區了,然后將請求包放到這個內存中。所有的請求包中,LOGIN請求包是父進程直接處理的,后續的游戲邏輯請求包由子進程處理,另外子進程雖然不處理網絡連接,但是對于和數據庫代理服務器和switch中心跳服務器的連接還是要自己處理的,因此子進程中也有網絡相關的內容,在net_rcv中有以下片斷:

if (!is_conn) {

handle_process(epi.fds[fd].cb.recvptr, epi.fds[fd].cb.rcvprotlen, fd, 0);

}

這個就是直接處理數據庫代理以及心跳的處理過程。另外關于網絡架構中還有一點就是鏈表的使用,在net_rcv中首先調用do_read_conn讀取網絡數據,但是一旦當前積壓的未處理的數據達到了一個最大值的時候,后續的請求就要丟到鏈表中,然后在下一輪net_loop中接收新的數據前優先處理之;在net_loop中有一句:

if (is_conn) handle_send_queue();

該句的意思就是說,如果是父進程,那么首先處理發送隊列,這些發送隊列中的數據都是子進程放入的請求包的回復,父進程優先將這些回復返回給各個客戶端:

static inline void handle_send_queue()

{

struct shm_block *mb;

struct shm_queue *q;

int i = 0;

for ( ; i != bindconf.bind_num; ++i ) {

q = &(bindconf.configs[i].sendq);

while ( shmq_pop(q, &mb) == 0 ) {

schedule_output(mb);

}

}

}

雖然這個過程比較優先,但是更優先是前面說的過程,就是處理積壓鏈表,下面片斷在上面的之前調用:

list_for_each_safe (p, l, &epi.close_head) { //優先便利需要關閉的套結字,第一時間關閉連接

fi = list_entry (p, struct fdinfo, list);

if (fi->cb.sendlen > 0)

do_write_conn (fi->sockfd);

do_del_conn (fi->sockfd, 0);

}

list_for_each_safe (p, l, &epi.etin_head) { //優先處理積壓隊列,提高響應速度

fi = list_entry (p, struct fdinfo, list);

if (net_recv(fi->sockfd, max_len, is_conn) == -1)

do_del_conn(fi->sockfd, is_conn);

}

該服務器中大量運用了鏈表,此鏈表的定義就是list_head,是從linux內核中抽取出來的。

接收新連接的時候,在net_loop中:

if (epi.fds[fd].type == LISTEN_TYPE_FD) {

while (do_open_conn(fd, is_conn) > 0);

接收了新的連接,并且加入了一個列表,將新連接的套結字描述符和一個空的bind_config_elem_t相關聯,注意此時并沒有初始化這個bind_config_elem_t,因為在LOGIN包到來之前還不知道和哪一個bind_config_elem_t相關聯,該函數僅僅初始化了一個epi結構。

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/444985.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/444985.shtml
英文地址,請注明出處:http://en.pswp.cn/news/444985.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

online游戲服務器架構—用戶登錄數據組織 .

sprite_t類型的數據結構是核心數據結構,每一個登錄用戶對應一個,它的初始化在用戶登錄的時候,此后一直到用戶退出或者離線一直保存在系統內存當中,在此過程中該sprite_t數據結構被保存在兩個哈希表當中,一個是以用戶的…

leetcode300 最長上升子序列

經典題&#xff0c;不解釋&#xff0c;可以看我之前文章。 普通dp public class Solution {public int lengthOfLIS(int[] nums) {if (nums.length 0) {return 0;}int[] dp new int[nums.length];dp[0] 1;int maxans 1;for (int i 1; i < dp.length; i) {int maxval …

Github(5)-開源開發-常見錯誤

使用github 托管代碼簡單使用教程--開源開發-常見錯誤5. github開源開發6. 報錯log6.1 fatal: remote origin already exists.6.2 There is no tracking information for the current branch.6.標簽管理廖雪峰老師博文學習筆記&#xff1a;https://www.liaoxuefeng.com/wiki/89…

online游戲服務器架構--數據庫及事件相關 .

Online服務器的第三部分就是數據層&#xff0c;send_request_to_db開始了數據層的處理邏輯&#xff1a; int send_request_to_db(int cmd, sprite_t* p, int body_len, const void* body_buf, uint32_t id)&#xff1b; 在該函數里首先以懶惰的方式連接數據庫服務器&#xff…

leecode5 最長回文子串

給定一個字符串 s&#xff0c;找到 s 中最長的回文子串。你可以假設 s 的最大長度為 1000。 示例 1&#xff1a; 輸入: "babad" 輸出: "bab" 注意: "aba" 也是一個有效答案。 示例 2&#xff1a; 輸入: "cbbd" 輸出: "bb"…

libxml解析xml文件的一些總結

libxml -- 解析 XML 文檔XML 介紹&#xff1a;XML 和 DOMlibxml 介紹 數據類型 — xmlChar數據結構 創建 XML 文檔解析 XML 文檔修改 xml 文檔Xpath — 處理大型 XML 文檔libxml2 庫函數要注意的函數讀取 xml 文件xml 操作基本結構及其指針類型根節點相關函數 創建子節點相關函…

Linux(7)-正則表達式

正則表達式demo1:在某個文件中尋找命令seddemo2:尋找8位電話號碼正則表達式&#xff1a;用來描述或者匹配某一系列符合某個句法隊則的字符串或者單個字符串。最初正則表達式&#xff0c;出現在自動控制理論和形式化語言理論中。 Linux 中 find grep sed ls命令都支持正則表達式…

服務器端開發的一些建議

摘要: 本文作為游戲服務器端開發的基本大綱&#xff0c;是游戲實踐開發中的總結。第一部分專業基礎&#xff0c;用于指導招聘和實習考核&#xff0c; 第二部分游戲入門&#xff0c;講述游戲服務器端開發的基本要點&#xff0c;第三部分服務端架構&#xff0c;介紹架構設計中的一…

leetcode63 不同路徑II

一個機器人位于一個 m x n 網格的左上角 &#xff08;起始點在下圖中標記為“Start” &#xff09;。 機器人每次只能向下或者向右移動一步。機器人試圖達到網格的右下角&#xff08;在下圖中標記為“Finish”&#xff09;。 現在考慮網格中有障礙物。那么從左上角到右下角將…

小談Online-game服務器端設計(1、2)

談這個話題之前&#xff0c;首先要讓大家知道&#xff0c;什么是服務器。在網絡游戲中&#xff0c;服務器所扮演的角色是同步&#xff0c;廣播和服務器主動的一些行為&#xff0c;比如說天氣&#xff0c;NPC AI之類的&#xff0c;之所以現在的很多網絡游戲服務器都需要負擔一些…

Linux(8)-Linux下的編程開發-C/C++、PHP、JAVA概述

Linux下的編程開發1.C/C語言開發環境的搭建2.PHP開發環境搭建3.JAVA開發環境搭建1.C/C語言開發環境的搭建 方式1:文本編輯器編譯器&#xff08;gcc/g&#xff09; Ubuntu 下常用的文本編輯器&#xff1a; Gedit–語法高亮Vim–vi(無比強大無比難用)的改進。字符界面/圖形界面…

leetcode55 跳躍游戲 秒殺所有答案

給定一個非負整數數組&#xff0c;你最初位于數組的第一個位置。 數組中的每個元素代表你在該位置可以跳躍的最大長度。 判斷你是否能夠到達最后一個位置。 示例 1: 輸入: [2,3,1,1,4] 輸出: true 解釋: 我們可以先跳 1 步&#xff0c;從位置 0 到達 位置 1, 然后再從位置 …

小談Online-game服務器端設計(3)

下面我想來談談關于服務器上NPC的設計以及NPC智能等一些方面涉及到的問題。首先&#xff0c;我們需要知道什么是NPC&#xff0c;NPC需要做什么。NPC的全稱是&#xff08;Non-Player Character&#xff09;&#xff0c;很顯然&#xff0c;他是一個character&#xff0c;但不是玩…

小談Online-game服務器端設計(4)

在這一章節&#xff0c;我想談談關于服務器端的腳本的相關設計。因為在上一章節里面&#xff0c;談NPC智能相關的時候已經接觸到一些腳本相關的東東了。還是先來談談腳本的作用吧。   在基于編譯的服務器端程序中&#xff0c;是無法在程序的運行過程中構建一些東西的&#xf…

leetcode45 跳躍游戲II 秒殺所有答案

給定一個非負整數數組&#xff0c;你最初位于數組的第一個位置。 數組中的每個元素代表你在該位置可以跳躍的最大長度。 你的目標是使用最少的跳躍次數到達數組的最后一個位置。 示例: 輸入: [2,3,1,1,4] 輸出: 2 解釋: 跳到最后一個位置的最小跳躍數是 2。 從下標為 …

MachineLearning(7)-決策樹基礎+sklearn.DecisionTreeClassifier簡單實踐

sklearn.DecisionTreeClassifier決策樹簡單使用1.決策樹算法基礎2.sklearn.DecisionTreeClassifier簡單實踐2.1 決策樹類2.3 決策樹構建2.3.1全數據集擬合&#xff0c;決策樹可視化2.3.2交叉驗證實驗2.3.3超參數搜索2.3.4模型保存與導入2.3.5固定隨機數種子參考資料1.決策樹算法…

游戲服務器體系結構

本文描述了一個我所設計的游戲服務器體系結構,其目的是實現游戲服務器的動態負載平衡,將對象從繁忙的服務器轉移到相對空閑的服務器中.設計并沒有經過具體的測試與驗證,僅僅是將自己目前的一些想法記錄下來.隨著新構思的出現,可能會有所變化. 以下是服務器的邏輯視圖,其中忽略…

游戲服務器架構探討

要描述一項技術或是一個行業&#xff0c;一般都會從其最古老的歷史開始說起&#xff0c;我本也想按著這個套路走&#xff0c;無奈本人乃一八零后小輩&#xff0c;沒有經歷過那些苦澀的卻令人羨慕的單機游戲開發&#xff0c;也沒有響當當的拿的出手的優秀作品&#xff0c;所以也…

leetcode72 編輯距離

給定兩個單詞 word1 和 word2&#xff0c;計算出將 word1 轉換成 word2 所使用的最少操作數 。 你可以對一個單詞進行如下三種操作&#xff1a; 插入一個字符 刪除一個字符 替換一個字符 示例 1: 輸入: word1 "horse", word2 "ros" 輸出: 3 解釋: ho…

即時通訊系統架構

有過幾款IM系統開發經歷&#xff0c;目前有一款還在線上跑著。準備簡單地介紹一下大型商業應用的IM系統的架構。設計這種架構比較重要的一點是低耦合&#xff0c;把整個系統設計成多個相互分離的子系統。我把整個系統分成下面幾個部分&#xff1a;&#xff08;1&#xff09;狀態…