1. 程式人生 > >railgun:通過程式碼來簡單說明

railgun:通過程式碼來簡單說明

在說明之前我先展示一下我的工程目錄層級,因為我連github有點慢,所以像protoc.exe這樣上了1M的檔案就不傳了。所以如果從github上獲取原始碼的同學可能還需要再去我的百度雲盤上獲取build.batprotoc.exe。百度雲盤連結:http://pan.baidu.com/s/1dFebn25密碼:oq2h,當然用linux的同學需要自己再另外獲取protoc和自己寫buildshell指令碼,這裡對linux同學造成的小小不便請諒解

不過在git.oschina上有傳protoc.exe


這裡build.batprotoc.exerailgun這個工程的是處於同一個目錄下的

Go/workspace就是我的GoPath這個環境變數的值

就是說railgun工程所處的目錄是$GoPath/src

Build.batprotoc.exe操作.proto檔案生成相應的.go原始檔的批處理命令,在linux下替換成相應.sh指令碼命令就行了(這裡要稍微注意點的是,如果你的GoPath環境變數有兩個以上的路徑,這個批處理命令是無法執行的,需要你把批處理裡面的%GOPATH%這個值替換成protoc-gen-go.exe所處的絕對路徑)


下面開始講解程式碼,以傳送LoginReq請求為例

執行GateApp.exeRouterApp.exe, LoginApp.exe

1.在工程裡寫了一個很粗糙的客戶端測試用例

ClientForTest.exe:

開始先建立與GateApp建立TCP連線

ch := make(chan proto.Message, 1000)
    session := DialManager.CreateClient("127.0.0.1:4101", ch)
    if session == nil {
        fmt.Println("連線gate失敗")
        return
    }

建立訊息接受協程

goReceiveMsg(ch)


將loginReq轉化為bs_tcp.TCPTransferMsg型別後gateApp傳送報文

loginReq := new(bs_client.LoginReq)
loginReq.LoginAccount = "yourname"
loginReq.LoginPassword = "E10ADC3949BA59ABBE56E057F20F883E" //123456的MD5加密
tcpMsg := ChangeCommonMsgToTCPTransferMsg(loginReq)
if tcpMsg != nil {
        session.MsgWriteCh <- tcpMsg //向gate傳送訊息
}

2.通過TCP傳輸到了GateApp.exe:

2.1.從main()入口函式開始分析GateApp的程式碼

func main() {
    //先建立需要的變數
    quit := make(chan int)
    var myAppId uint32 = 101
    pNetAgent := PoolAndAgent.CreateNetAgent("0.0.0.0:4101") //監聽4101埠
    pLogicPool := PoolAndAgent.CreateMsgPool(quit, uint32(bs_types.EnumAppType_Gate), myAppId)
    pRouterAgent := PoolAndAgent.CreateRouterAgent("127.0.0.1:2001") //連線127.0.0.1:2001地址
    pGateLogic := CreateGateLogicInstance()
    //將他們都與Pool繫結起來
    pLogicPool.AddLogicProcess(pGateLogic)
    pLogicPool.BindNetAgent(pNetAgent)
    pLogicPool.BindRouterAgent(pRouterAgent)
    //執行
    ok := pLogicPool.InitAndRun(nil)
    if ok {
        fmt.Println("初始化完畢")
        pInitMsg := &PrivateInitMsg{
            pNetAgent:    pNetAgent,
            pRouterAgent: pRouterAgent,
            myAppId:      myAppId}
        //在初始化完畢後向邏輯層傳送初始化報文,帶著一些初始化資訊,比如自己的APPID等
        pLogicPool.PushMsg(pInitMsg, 0)
    }

    //阻塞直到收到quit請求
    for {
        select {
        case v := <-quit:
            if v == 1 { //只有在收到1時才退出主執行緒
                return
            }
        }
    }
}

這裡quit是通知整個GateApp.exe程序結束的channel

pNetAgent是監聽2001listenManager

pLogicPool是一個訊息佇列管理池將會與主邏輯繫結

pRouterAgent是去撥號連線RouterAppDialManager

pGateLogic是業務邏輯GateLogic的一個單例,GateLogic實現了agent.go裡的typeILogicProcessinterface這個介面,GateLogic.go裡定義和實現了GateLogic

然後和這些變數都和pLogicPool繫結後,pLogicPool.InitAndRun來啟動執行,如果執行成功後向pLogicPool push私有的初始化用途報文PrivateInitMsg,把自己的APPId之類的資訊傳遞給主邏輯層。


2.2.下一步分析package PoolAndAgent的MsgPool.go的SingleMsgPool.InitAndRun:

這裡主要分3部分

1.判斷有沒有bindingNetAgent,就是是否需要建立網路監聽例項,如果需要那麼就建立新的協程,用於一直select case阻塞讀取NetToLogicChannel這個channel

2.判斷有沒有RouterToLogicChannel,就是是否需要建立去連線RouterApp的網路撥號例項(除了RouterApp自身都需要)

3.遍歷bindingLogicProcesses這個slice,如果是主邏輯業務的ILogicProcess建議只需要一個就夠了,因為主邏輯業務會不可避免的要儲存各種資料,那麼如果是多協程可能就會涉及到全域性資料加鎖的情況,反覆加鎖解鎖說效率不一定比單協程要高。(當然如果你要主邏輯多協程我也不反對,只是按照我的經驗來說不建議這麼做)。像資料庫操作就這種IO速度較慢不可避免需要多協程的來處理的情況,那麼實際上操作資料庫的協程之間是沒有相互通訊的。都是在獲取好資料後直接傳給主邏輯協程來處理,這樣程式結構上顯得清楚點。後面講到LoginApp.exe時會舉例。

在遍歷迴圈中為每個bindingLogicProcesses建立RunLogicProcess(pLogicILogicProcess,pDataBase*CADODatabase,InitMsgproto.Message)

協程pLogic就是業務邏輯的單例,pDataBase是資料庫,如果不需要操作資料庫傳nilInitMsg是需要傳入的初始化報文,不需要也傳nil

RunLogicProcess()函式裡每當收到一個報文就呼叫pLogic.ProcessReq來處理這個報文,和定時呼叫pLogic.OnPulse(nMs)函式,每次OnPulse的時間間隔ONPULSE_INTERVAL由程式猿根據實際需求調整

OK,這裡當上文中來自ClientForTest.exe的TCPTransferMsg報文由網路層收到以後就丟給SingleMsgPool,SingleMsgPool收到後再丟給自己的成員變PoolToLogicChannel

,然後RunLogicProcess()協程一直在阻塞讀取PoolToLogicChannel在收到TCPTransferMsg報文後呼叫pLogic.ProcessReq函式來處理這個報文


2.3.下一步分析GateLogic.go裡的ProcessReq:

開始需要呼叫Gate_CreateCommonMsgByTCPTransferMsg函式來過濾掉不需要的報文,並轉化成自己需要的報文。ClientForTest.exe發來的報文到這裡變為了bs_gate.GateTransferData,然後Gate_GateTransferData()函式裡把GateTransferData轉為RouterTransferData發往RouterApp.exe



3.GateApp.exe通過TCP傳輸到了RouterApp.exe:

Main()SingleMsgPool.InitAndRun()GateApp.exe部分大同小異,區別部分就是GateApp.exe是有RouterAgent模組的,RouterApp.exe是沒有RouterAgent模組的

講一下RouterLogic.go檔案的ProcessReq():

在收到RouterTransferData報文後,根據dest_apptypedest_appid的值向目標APP傳送(其實這裡就是LoginApp

4.RouterApp.exe通過TCP傳輸到了LoginApp.exe:

4.1 這裡要說一下Main():

func main() {
    //先建立需要的變數
    quit := make(chan int)
    var myAppId uint32 = 30
    pLogicPool := PoolAndAgent.CreateMsgPool(quit, uint32(bs_types.EnumAppType_Login), myAppId)
    pRouterAgent := PoolAndAgent.CreateRouterAgent("127.0.0.1:2001") //連線127.0.0.1:2001地址
    pMainLoginLogic := CreateLoginLogicInstance()
    //將他們都與Pool繫結起來
    pLogicPool.AddLogicProcess(pMainLoginLogic)
    pLogicPool.BindRouterAgent(pRouterAgent)
    //建立資料庫協程,只有主執行緒pLogicPool需要繫結RouterAgent,資料庫協程是不需要的,所以退出quit也不需要建立,因為程式退出又邏輯主執行緒來控制
    //先建立需要的變數
    pDBPool := PoolAndAgent.CreateMsgPool(nil, uint32(bs_types.EnumAppType_Login), myAppId)
    for i := 0; i < 10; i++ {
        //建立10個數據庫協程,因為資料庫IO速度比較慢會存在阻塞時間,所以要多開幾個,
        //資料庫邏輯協程間最好不要有資料通訊,都應該通過主邏輯POOL與主邏輯進行通訊
        //相對的主邏輯最好只用一個協程,這樣寫業務邏輯也好寫,
        pDBLogic := CreateLoginDBInstance()
        pDBProcess := PoolAndAgent.CreateADODatabase("root:[email protected](localhost:3306)/gotest?charset=utf8")
        //將他們都與Pool繫結起來
        pDBPool.AddLogicProcess(pDBLogic)
        pDBPool.AddDataBaseProcess(pDBProcess)
    }

    //執行主邏輯執行緒pool
    ok := pLogicPool.InitAndRun(nil)
    if ok {
        fmt.Println("主邏輯POOL初始化完畢")
        //這裡要在初始化完畢後把DBPool和MainPool的指標傳過去,這樣才能兩個Pool之間才可以相互傳遞資料
        pInitMsg := &PrivateInitMsg{
            pNetAgent:    nil,
            pRouterAgent: pRouterAgent,
            myAppId:      myAppId,
            pMainPool:    pLogicPool,
            pDBPool:      pDBPool}
        //在初始化完畢後向邏輯層傳送初始化報文,帶著一些初始化資訊,比如自己的APPID等
        pLogicPool.PushMsg(pInitMsg, 0)
    } else {
        return
    }
    //執行資料庫協程pool
    pInitMsg := &PrivateInitMsg{
        pNetAgent:    nil,
        pRouterAgent: pRouterAgent,
        myAppId:      myAppId,
        pMainPool:    pLogicPool,
        pDBPool:      pDBPool}
    ok = pDBPool.InitAndRun(pInitMsg)
    if ok {
        fmt.Println("資料庫邏輯POOL初始化完畢")
    } else {
        return
    }
    //阻塞直到收到quit請求
    for {
        select {
        case v := <-quit:
            if v == 1 { //只有在收到1時才退出主執行緒
                return
            }
        }
    }
}

main()函式裡建立了兩個SingleMsgPool,一個是繫結主邏輯業務協程的Pool,另一個是繫結多個數據庫邏輯業務協程的Pool

而且在繫結資料庫協程的pDBPool.InitAndRun(pInitMsg)的時候引數不是nil而是pInitMsg。為什麼呢?因為如果不在InitAndRun的時候就傳進去,在初始化完成後再向pDBPool傳送,那麼多個數據庫業務邏輯協程實際上是對PoolToLogicChannel是處於共同讀取狀態,如果哪個協程空閒或者說正處於管道讀取阻塞狀態,就會去處理這個報文。就是說就算髮送多個初始化報文仍然有可能存在有些資料庫邏輯沒有收到初始化報文的情況,造成初始化不完全的情況。

綁定了資料庫邏輯業務協程的Pool實際上是沒有繫結RouterAgent的,只有主邏輯Pool才綁定了RouterAgent,就是說LoginAppRouterApp通訊是通過主邏輯Pool,資料庫的pDBPool並不直接與RouterApp通訊

4.2.LoginMainLogic.go裡的ProcessReq:

在收到TCPTransferMsg時,RouterAgent會將其轉為RouterTransferData再讓ProcessReq來處理。ProcessReq先將RouterTransferData轉為LoginReq,

Client_OnLoginReq()函式中列印後就呼叫this.PushToDBPool(req) push給資料庫的DBPool。

DBPool收到後就讓空閒的資料庫業務協程來處理

4.3.LoginDBLogic.go裡的ProcessReq:

LoginDBLogic收到的直接就是LoginReq,直接呼叫Client_OnDBLoginReq來處理

這裡我對資料庫sql.DB做了簡單的封裝,如果你不喜歡我的封裝直接操作CADODatabase

.TheDB也是可以的,或者重新封裝也沒問題。

在從資料庫裡獲取了資料後new了一個bs_client.LoginRsp登入回覆報文丟給主邏輯業務Pool

接下來LoginRspLoginApp最終返回到客戶端,就是上述過程的逆過程,這裡就不再贅述了。需要簡單注意一下的是,在GateApp收到LoginRsp時,如果是登入成功則會記錄下這個userId並和對應的會話關聯起來,以後其他APP向某個使用者客戶端傳送報文時就可以用userId作為標識傳送了,不一定要gateConnId,因為用userId通常比較方便,因為gateConnId使用者客戶端每當重新連線時都會變更。

5.說明和小結:每當根據業務需求新寫一個業務App時,需要手寫的原始檔有

package mainmain.goPrivateMsg.go(這個如果沒有需要新增私有報文就直接複製過來就行了)、XXXMsgFilter.goXXXMainLogic.goXXXDBLogic.go(如果需要操作資料庫的話)

Package bs_protoSetBaseInfo.go中的SetBaseKindAndSubId函式,要根據proto資料型別對其new Base並對Base.KindId和SubId賦值




我的郵箱:[email protected]

相關推薦

railgun通過程式碼簡單說明

在說明之前我先展示一下我的工程目錄層級,因為我連github有點慢,所以像protoc.exe這樣上了1M的檔案就不傳了。所以如果從github上獲取原始碼的同學可能還需要再去我的百度雲盤上獲取build.bat和protoc.exe。百度雲盤連結:http://pan.b

MongoDB 通過ReadConcern 處理備庫一致讀的問題

問題描述 MongoDB的寫請求寫入Primary, secondary從Primary自動獲取並且應用oplog來保持和主庫的同步, MongoDB 允許使用者從Primary 或者 secondary 讀取資料(由客戶端ReadPreference 決定)。但讀資料可能存在以下問題: 使用者從s

程式設計師常用網站程式碼簡單

說起程式設計師,總有那麼幾個網站是程式設計師不可或缺的,小編辦公室的程式設計師就常常用這幾款網站,你用過麼? Stack Overflow 這個網站,就算不常用,每次搜尋程式設計相關都能看見它。當你遇到任何程式設計問題,搜尋它,就對了。 w3school 線上教程 如果你剛剛學

OC中UITableView之自定義cell的使用(2)通過程式碼建立

在使用UITableView做開發時,常常會遇到 系統提供的樣式無法滿足專案需求的情況,這時就需要根據需求來自定義cell。 自定義cell有兩種方式:   · 通過xib自定義cell(適用於cell中子控制元件個數固定、cell樣式統一的結構,例如:商品的列表頁面)

[caffe筆記005]通過程式碼理解faster-RCNN中的RPN

https://blog.csdn.net/happyflyy/article/details/54917514 [caffe筆記005]:通過程式碼理解faster-RCNN中的RPN 注意:整個RPN完全是筆者自己的理解,可能會有一些理解錯誤的地方。 1. RPN簡介 RPN是reg

Android通過程式碼繪製UI介面

在Android中,我們可以像java Swing中那樣完全通過程式碼控制UI介面。所有的UI元件都是new出來的。 然後將這些UI元件新增到佈局管理器中,來實現UI介面。 在程式碼中生產介面一般是三個步驟: 1、建立一個佈局管理器,五大布局根據你的需

JQuery手動觸發事件API之通過程式碼看清trigger與triggerHandler的差別

本文只討論JQuery如何手動觸發DOM上繫結的事件處理函式,至於如何給DOM繫結事件處理函式,可以參考這篇文章。測試環境是IE11/FF17/Chrome39,JQuery版本是1.11.1和2.1

Elastcisearch.Nest 7.x 系列`偽`官方翻譯通過 NEST 快捷試用 Elasticsearch

本系列已經全部完成,完整版可見 :https://blog.zhuliang.ltd/categories/Elasticsearch/ 本系列博文是“偽”官方文件翻譯(更加本土化),並非完全將官方文件進行翻譯,而是在查閱、測試原始文件並轉換為自己真知灼見後的“準”翻譯。有不同見解 / 說明不周的地方,還

C++通過C++程式碼簡單理解程序間的通訊機制共享記憶體

下面用共享對映檔案的方式實現程序間通訊,程式碼可以執行。 一、淺理解 每個程序有自己獨立的空間,一個程序無法訪問其他程序的資料。就好像兩個是互不干涉的個體,想讓它們進行通訊(交換資料),就必須有一段它們都可以訪問到的空間,作為中間介質。在計算機中,可以存放資料的地方分為記憶體和硬

程式碼實踐|通過簡單程式碼回顧卷積塊的歷史(Bottleneck,Inception,Residual,ResNeXt,Dense,Squeeze-and-Excitation,NASNet等)

轉自:https://mp.weixin.qq.com/s/itfrg597sVB0sa6auF2l_Q 作者:Paul-Louis Pröve          我試著定期閱讀ML和AI的論文,這是保持不掉隊的唯一的方法。作為一個電腦科學家

黑馬基礎階段測試題通過字符輸入流讀取info.txt中的所有內容,每次讀取一行,將每一行的第一個文字截取出並打印在控制臺上。

print swift red amr ack pub flush app args package com.swift; import java.io.BufferedReader; import java.io.BufferedWriter; import java

(譯)綜合指南通過Ubuntu 16.04上從Source構建安裝支持GPU的Caffe2

疑問 選項 靈活性 vid 克隆 .profile rop prope 特定 (譯)綜合指南:通過Ubuntu 16.04上從Source構建來安裝支持GPU的Caffe2 譯者註: 原文來自:https://tech.amikelive.com/node-706/comp

spring-boot 之Lombok的使用,通過註解省略一些常用程式碼,set get 日誌等

如果使用IDEA要先安裝lombok外掛 三、注意:如果註解@Slf4j注入後找不到變數log,那就給IDE安裝lombok外掛,、 下面以idea為例 1、File  → settings →  Plugins,  然後點選“Browse repositori

快速傅立葉變換FFT的學習筆記一C語言程式碼簡單實現

快速傅立葉變換FFT的學習筆記一:C語言程式碼的簡單實現 fft.c #include "math.h" #include "fft.h" void conjugate_complex(int n,complex in[],complex out[]) { int i = 0

java8學習通過行為引數化傳遞程式碼

如下一段程式碼請看 @Test public void test() throws Exception { List<Apple> list = new ArrayList<>(); for (Apple apple : list) { if (apple.getWeight(

微服務 使用eclipse 搭建一個簡單的微服務

下載地址:https://download.csdn.net/download/qq_18430613/10642372 1.先看看整體專案的一個架構,parent 專案父依賴包,用於管理我們的依賴,provider 服務提供方,consumer 消費者。 2.建立parent 專案

springboot配置通過工具類獲取spring容器中的bean

**由於公司電腦限制,完全手敲,有單字錯誤望理解** @component @SuppressWarnings("static-access") public class AppContext implements ApplicationContextAware{   &n

python3教程(八)使用文字編寫程式碼

我們之前一隻在IDLE環境裡程式設計,IDLE裡我們可以看到每一行都直接返回結果,這樣方便我們在前期執行程式碼,發現BUG源頭。 但是,IDLE也有很不好的地方,比如:寫的程式碼不能儲存,IDLE對於某些模組可能支援不好等等,這時候我們就需要使用文字來寫程式碼。 python會自帶一個文

SprinvAction學習一、裝配bean通過Java程式碼裝配bean

具體步驟 (1)普通的介面 (2)實現介面的類,該類為需要裝配到spring bean中 (3)配置類,由該類定義bean 示例程式碼: 介面1:(普通的介面) package com.sp

spring-boot 之Lombok的使用,通過註解省略一些常用程式碼,set get 日誌等

如果使用IDEA要先安裝lombok外掛 三、注意:如果註解@Slf4j注入後找不到變數log,那就給IDE安裝lombok外掛,、 下面以idea為例 1、File  → settings →  Plugins,  然後點選“Browse repositories” 如