五邑隱俠,本名關健昌,12年游戲生涯, 本教程以Go語言為例, 一、網路層 網路游戲客戶端除了全域登錄使用http請求外,一般通過socket長連接與服務端保持連接,go語言的net包提供網路socket長連接相關操作, 對于服務端,一般經歷 Listen、Accept兩個步驟實作與客戶端連接,
func main() {
l, err := net.Listen("tcp4", ":8080")
if err != nil {
return
}
for {
c, err := l.Accept()
if err != nil {
break
}
fmt.Println("accept connect: ", c.RemoteAddr())
}
}
客戶端通過 Dial 連接服務端
func main() {
c, err := net.DialTimeout("tcp4", "127.0.0.1:8080", time.Second*time.Duration(8))
if err != nil {
return
}
fmt.Println("connect with: ", c.LocalAddr())
}
連接 c(net.Conn型別)的主要方法是 Read、Write,
func Read(b []byte) (n int, err error) func Write(b []byte) (n int, err error)
一般連接建立后,每個連接分別創建讀和寫兩個 go rountine 進行讀回圈和寫回圈,
func (c *Conn) open() {
go c.readLoop()
go c.writeLoop()
}
雖然go底層是基于epoll邊緣觸發,但是并沒有暴露介面通知什么時候有可讀資料、可寫等,為了避免輪詢,一般在 go rountine 里阻塞的讀、寫,由于net.Conn只提供 Close() error,沒辦法只停止讀,等待寫結束再關閉連接,一般讀做超時處理,超時后如果有關閉標記,則不再嘗試讀,
func SetReadDeadline(t time.Time) error
連接層需要通知使用者連接的狀態,所以引入連接監聽 interface
type ConnListener interface {
OnConnOpen(c net.Conn) error
OnConnClose(c net.Conn) error
OnConnError(c net.Conn, err error)
OnConnRead(c net.Conn) error
OnConnWrite(c net.Conn) error
}
使用者只需要實作自己的監聽者監聽連接的各個生命周期,由讀寫的 go rountine 驅動業務邏輯執行,
二、P2P層 網路層提供簡單的連接打開、關閉和讀寫操作,為了建立服務端行程之間,以及服務端行程與客戶端之間通信的基礎,引入P2P層(端對端層), P2P層包含端資訊 P2pEnd、協議包P2pPack、P2P網路P2pNet 1、P2pEnd定義這個端的型別、編號(例如:游戲分服編號1,網關服編號3)、寫佇列,還包含一些防御資訊用于對連接進行監控type P2pEnd struct {
EndType uint8
EndNo uint16
QueWritePacks chan *P2pPack
}
2、P2pPack定義包資訊,包括源型別、編號,目標型別、編號,資料,這有利于包的路由,除此以外還有一些控制資訊做更精細的處理,
type P2pPack struct {
SrcEnd uint8
SrcNo uint16
DstEnd uint8
DstNo uint16
Payload []byte
}
這里的包格式是通用包格式,Payload里包含業務包包頭,根據業務需求定義自己的包格式,
3、P2pNet是一個端對端網路,維護該通信端所有的連接,作為一個通信端,它首先有自己的端型別、編號
type P2pNet struct {
endType uint8
endNo uint16
}
然后要記錄其他端與連接的互相映射
type P2pNet struct {
endType uint8
endNo uint16
mapConn2End map[net.Conn]*P2pEnd
mapId2Conn map[uint32]net.Conn
}
所有連接接收到的包放到一個chan里,方便做分發處理
type P2pNet struct {
endType uint8
endNo uint16
mapConn2End map[net.Conn]*P2pEnd
mapId2Conn map[uint32]net.Conn
queReadPacks chan *ReadPackWrap
}
還有一些其他的控制資訊,
在P2P層維護的是端與端之間的連接,所以需要提供注冊協議,用于向服務方告知自己的端型別和編號,func (r *P2pNet) Register(dstEnd uint8, dstNo uint16) error服務方在 OnConnOpen(c net.Conn) error(自動分配編號) 或者 OnConnRead(c net.Conn) error 得到的包是注冊包時(由協議指定編號)對連接進行系結,
除了注冊協議,底層的心跳 ping、pong 也在P2P層處理,還有一些防御相關的處理,對業務層透明,
這樣建立起一張端對端通信網,這張網的底層基于網路層做通信,通過實作 ConnListener 驅動連接讀寫,讀包放到 P2pNet.queReadPacks,寫包放到對應端的 P2pEnd.QueWritePacks, 為了驅動業務邏輯,類似網路層,在P2P層也引入監聽的 interface,使用者通過實作該interface來驅動業務邏輯type P2pListener interface {
OnP2pConn(p2p *P2pNet, endType uint8, endNo uint16)
OnP2pCall(p2p *P2pNet, pack *P2pPack)
OnP2pClose(p2p *P2pNet, endType uint8, endNo uint16)
OnP2pError(p2p *P2pNet, err error)
}
三、關于防御
連接層的防御一般就是檢測例外連接,把例外連接踢掉,避免占用socket資源, 1、最簡單的,通過心跳來判斷連接是否活躍,清除非活躍連接復用這部分socket,連接可以分為主動活躍和被動活躍兩種模式,主動活躍的連接,會主動發心跳包過來,通過頻率去檢測心跳包,如果超時都沒收到心跳包,可以踢掉,被動活躍連接,需要定時給它發心跳報活,避免被對方踢掉, 2、沒有業務包的連接,如果一個連接從連接開始,只發心跳包,限定時間內從來不發業務包,這個連接要踢掉, 基于1、2點,連接從連接開始必須在限定時間內發業務包,后續必須通過發業務包或者心跳包來維護連接, 3、限制 IP 關聯的連接數,一般同個局域網的玩家 IP 會一樣,但是也可能是服務器在被攻擊,現在有些游戲上線,會被模擬玩家連接撐滿服務,導致真實玩家無法進入游戲,通過加 IP 關聯的連接數限制來增加攻擊成本, 4、發包頻率檢測,例如我們設定最大15幀/s,每隔2分鐘檢測一次,如果請求包間隔平均時間小于66ms,可以踢掉, 5、限制最大的包大小,收到超過最大限制的包,則踢掉連接, 網路通信介紹到這里,接下來聊聊業務的服務機制和rpc機制,轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/300864.html
標籤:其他
上一篇:02-從四個角度分析時間復雜度
下一篇:如何自定義介面的cookie值
