golang微服務(wù)框架kratos實(shí)現(xiàn)Socket.IO服務(wù)的方法
Socket.IO 是一個(gè)面向?qū)崟r(shí) web 應(yīng)用的 實(shí)時(shí)通訊庫。它使得服務(wù)器和客戶端之間實(shí)時(shí)雙向的通信成為可能。底層使用EngineIO。SocketIO的的客戶端使用Engine.IO-Client,服務(wù)端使用Engine.IO實(shí)現(xiàn)。
Socket.IO 主要使用WebSocket協(xié)議。但是如果需要的話,Socket.IO 可以回退到幾種其它方法,例如Adobe Flash Sockets,JSONP拉取,或是傳統(tǒng)的AJAX拉取,并且在同時(shí)提供完全相同的接口。盡管它可以被用作WebSocket的包裝庫,它還是提供了許多其它功能,比如廣播至多個(gè)套接字,存儲(chǔ)與不同客戶有關(guān)的數(shù)據(jù),和異步IO操作。
Socket.IO如何工作
客戶端
EIO Socket 通過一個(gè) XHR (XMLHttpRequest) 握手。前端發(fā)送一個(gè) XHR,告訴服務(wù)端我要開始 XHR 長(zhǎng)輪詢了。后端返回的數(shù)據(jù)里面包括一個(gè) open 標(biāo)志(數(shù)字 0 表示), 以及sid 和 upgrades 字段,ping時(shí)間間隔,ping超時(shí)時(shí)間。
{ "sid": "8b7ab1ae-fbcf-4d23-8192-3c14a2a90721", "upgrades": [ "websocket" ], "pingInterval": 10000, "pingTimeout": 60000 }
sid 是本次 EIO Socket 的會(huì)話 ID,因?yàn)橐淮?EIO Socket 包含了多個(gè)請(qǐng)求,而后端又會(huì)同時(shí)連接多個(gè) EIO Socket,sid 的作用就相當(dāng)于 SESSION ID。
另一個(gè)字段 upgrades,正常情況下是 ['websocket'],表示可以把連接方式從長(zhǎng)輪詢升級(jí)到 WebSocket。
前端在發(fā)送第一個(gè) XHR 的時(shí)候就開始了 XHR 長(zhǎng)輪詢,這個(gè)時(shí)候如果有收發(fā)數(shù)據(jù)的需求,是通過長(zhǎng)輪詢實(shí)現(xiàn)的。所謂長(zhǎng)輪詢,是指前端發(fā)送一個(gè) request,服務(wù)端會(huì)等到有數(shù)據(jù)需要返回時(shí)再 response. 前端收到 response 后馬上發(fā)送下一次 request。這樣就可以實(shí)現(xiàn)雙向通信。
前端收到握手的 upgrades 后,EIO 會(huì)檢測(cè)瀏覽器是否支持 WebSocket,如果支持,就會(huì)啟動(dòng)一個(gè) WebSocket 連接,然后通過這個(gè) WebSocket 往服務(wù)器發(fā)一條內(nèi)容為 probe, 類型為 ping 的數(shù)據(jù)。如果這時(shí)服務(wù)器返回了內(nèi)容為 probe, 類型為 pong 的數(shù)據(jù),前端就會(huì)把前面建立的 HTTP 長(zhǎng)輪詢停掉,后面只使用 WebSocket 通道進(jìn)行收發(fā)數(shù)據(jù)
EIO Socket 生命周期內(nèi),會(huì)間隔一段時(shí)間 ping - pong 一次,用來測(cè)試網(wǎng)絡(luò)是否正常。
這是 WebSocket 幀的結(jié)構(gòu),綠色是發(fā)送,白色是接收。前面的數(shù)字是數(shù)據(jù)包類型,2 是 ping, 3 是 pong, 42是 message
服務(wù)端
服務(wù)端使用 ws 庫實(shí)現(xiàn) WebSocket 協(xié)議。http://socket.io 服務(wù)啟動(dòng)時(shí),會(huì)先啟動(dòng)一個(gè) ws 服務(wù)。http://socket.io 會(huì)監(jiān)聽 HTTP 服務(wù)器的 upgrade 和 request 事件。當(dāng) upgrade 事件觸發(fā)時(shí),說明可能是 WebSocket 握手,先簡(jiǎn)單校驗(yàn)下,然后把請(qǐng)求交給 ws 服務(wù)進(jìn)行處理,拿到 WebSocket 對(duì)象。當(dāng) request 事件觸發(fā)時(shí),根據(jù) url 路徑判斷是不是 http://socket.io 的 XHR 請(qǐng)求,拿到 res 和 res 對(duì)象。這樣就可以正確接收和返回客戶端數(shù)據(jù)了,具體處理過程和前端部分是對(duì)應(yīng)的。
Socket.IO的限制
與所有技術(shù)一樣,選擇正確的一種意味著明確您對(duì)產(chǎn)品未來的期望。與您自己創(chuàng)建Socket鏈接相比,SocketIO確實(shí)使許多事情變得更容易,但是除了上面提到的擴(kuò)展問題之外,還有局限性和缺點(diǎn)。
首先是初始連接比WebSockets更長(zhǎng)。這是因?yàn)樗紫仁褂瞄L(zhǎng)輪詢和XHRPolling建立連接,然后升級(jí)到WebSocket(如果可用)。如果您不需要支持較舊的瀏覽器并且不擔(dān)心不支持WebSockets的客戶端環(huán)境,則可能不需要SocketIO的額外開銷。您可以通過指定僅與WebSockets連接來最大程度地減少這種影響。這將更改與WebSocket的初始連接,但是會(huì)關(guān)閉備選方案。
在代碼最小化的情況下,客戶端仍將需要下載61.2 KB的數(shù)據(jù)。
對(duì)于其他繁重的數(shù)據(jù)傳輸(例如,視頻流傳輸),Socket不是好的解決方案。如果要在此級(jí)別上支持?jǐn)?shù)據(jù)交換,則更好的解決方案是webRTC或流數(shù)據(jù)傳輸服務(wù)商,Ably是其中之一。
Kratos服務(wù)端
首先安裝庫:
go get -u github.com/tx7do/kratos-transport/transport/socketio
然后實(shí)現(xiàn)一個(gè)簡(jiǎn)單的服務(wù)端:
package main import ( ?? ?"context" ?? ?"os" ?? ?"os/signal" ?? ?"syscall" ?? ?"github.com/go-kratos/kratos/v2/log" ? ? transportSocketIO "github.com/tx7do/kratos-transport/transport/socketio" ?? ?socketio "github.com/googollee/go-socket.io" ) func main() { ?? ?interrupt := make(chan os.Signal, 1) ?? ?signal.Notify(interrupt, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) ?? ?ctx := context.Background() ?? ?srv := transportSocketIO.NewServer( ?? ??? ?transportSocketIO.WithAddress(":8000"), ?? ??? ?transportSocketIO.WithCodec("json"), ?? ??? ?transportSocketIO.WithPath("/socket.io/"), ?? ?) ?? ?srv.RegisterConnectHandler("/", func(s socketio.Conn) error { ?? ??? ?s.SetContext("") ?? ??? ?log.Info("connected:", s.ID()) ?? ??? ?return nil ?? ?}) ?? ?srv.RegisterEventHandler("/", "notice", func(s socketio.Conn, msg string) { ?? ??? ?log.Info("notice:", msg) ?? ??? ?s.Emit("reply", "have "+msg) ?? ?}) ?? ?srv.RegisterEventHandler("/chat", "msg", func(s socketio.Conn, msg string) string { ?? ??? ?s.SetContext(msg) ?? ??? ?return "recv " + msg ?? ?}) ?? ?srv.RegisterEventHandler("/", "bye", func(s socketio.Conn) string { ?? ??? ?last := s.Context().(string) ?? ??? ?s.Emit("bye", last) ?? ??? ?_ = s.Close() ?? ??? ?return last ?? ?}) ?? ?srv.RegisterErrorHandler("/", func(s socketio.Conn, e error) { ?? ??? ?log.Info("meet error:", e) ?? ?}) ?? ?srv.RegisterDisconnectHandler("/", func(s socketio.Conn, reason string) { ?? ??? ?log.Info("closed", reason) ?? ?}) ?? ?if err := srv.Start(ctx); err != nil { ?? ??? ?panic(err) ?? ?} ?? ?defer func() { ?? ??? ?if err := srv.Stop(ctx); err != nil { ?? ??? ??? ?t.Errorf("expected nil got %v", err) ?? ??? ?} ?? ?}() ?? ?<-interrupt }
JS客戶端
<!DOCTYPE html> <html lang="en"> <head> ? ? <title>Socket.IO chat</title> ? ? <meta charset='utf-8'> ? ? <meta http-equiv='X-UA-Compatible' content='IE=edge'> ? ? <meta name='viewport' content='width=device-width, initial-scale=1'> ? ? <style> ? ? ? ? * { ? ? ? ? ? ? margin: 0; ? ? ? ? ? ? padding: 0; ? ? ? ? ? ? box-sizing: border-box; ? ? ? ? } ? ? ? ? body { ? ? ? ? ? ? font: 13px Helvetica, Arial; ? ? ? ? } ? ? ? ? form { ? ? ? ? ? ? background: #000; ? ? ? ? ? ? padding: 3px; ? ? ? ? ? ? position: fixed; ? ? ? ? ? ? bottom: 0; ? ? ? ? ? ? width: 100%; ? ? ? ? } ? ? ? ? form input { ? ? ? ? ? ? border: 0; ? ? ? ? ? ? padding: 10px; ? ? ? ? ? ? width: 90%; ? ? ? ? ? ? margin-right: .5%; ? ? ? ? } ? ? ? ? form button { ? ? ? ? ? ? width: 9%; ? ? ? ? ? ? background: rgb(130, 224, 255); ? ? ? ? ? ? border: none; ? ? ? ? ? ? padding: 10px; ? ? ? ? } ? ? ? ? #messages { ? ? ? ? ? ? list-style-type: none; ? ? ? ? ? ? margin: 0; ? ? ? ? ? ? padding: 0; ? ? ? ? } ? ? ? ? #messages li { ? ? ? ? ? ? padding: 5px 10px; ? ? ? ? } ? ? ? ? #messages li:nth-child(odd) { ? ? ? ? ? ? background: #eee; ? ? ? ? } ? ? </style> </head> <body> <ul id="messages"></ul> <form action=""> ? ? <input id="m" autocomplete="off"/> ? ? <button>Send</button> </form> <!--<script src="https://cdn.socket.io/4.6.0/socket.io.min.js" integrity="sha384-c79GN5VsunZvi+Q/WObgk2in0CbZsHnjEqvFxC5DxHn9lTfNce2WW6h2pH6u/kF+" crossorigin="anonymous"></script>--> <script src="https://cdn.socket.io/socket.io-1.2.0.js"></script> <script src="https://code.jquery.com/jquery-1.11.1.js"></script> <script> ? ? var socket = io("ws://localhost:8000"); ? ? // var socket2 = io("http://localhost:8000/chat/"); ? ? socket.on('reply', function (msg) { ? ? ? ? $('#messages').append($('<li>').text(msg)); ? ? }); ? ? $('form').submit(function () { ? ? ? ? // socket2.emit('msg', $('#m').val(), function (data) { ? ? ? ? // ? ? $('#messages').append($('<li>').text('ACK CALLBACK: ' + data)); ? ? ? ? // }); ? ? ? ? socket.emit('notice', $('#m').val()); ? ? ? ? $('#m').val(''); ? ? ? ? return false; ? ? }); </script> </body> </html>
參考資料 (Reference)
go-socket.io
Socket.IO
什么是socketIO?
到此這篇關(guān)于golang微服務(wù)框架kratos實(shí)現(xiàn)Socket.IO服務(wù)的方法的文章就介紹到這了,更多相關(guān)golang Socket.IO內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang連接MongoDB數(shù)據(jù)庫及數(shù)據(jù)庫操作指南
MongoDB是Nosql中常用的一種數(shù)據(jù)庫,下面這篇文章主要給大家介紹了關(guān)于golang連接MongoDB數(shù)據(jù)庫及數(shù)據(jù)庫操作的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-09-09Golang中的信號(hào)(Signal)機(jī)制詳解
Signal 是一種操作系統(tǒng)級(jí)別的事件通知機(jī)制,進(jìn)程可以響應(yīng)特定的系統(tǒng)信號(hào),這些信號(hào)用于指示進(jìn)程執(zhí)行特定的操作,如程序終止、掛起、恢復(fù)等,Golang 的標(biāo)準(zhǔn)庫 os/signal 提供了對(duì)信號(hào)處理的支持,本文將詳細(xì)講解 Golang 是如何處理和響應(yīng)系統(tǒng)信號(hào)的,需要的朋友可以參考下2024-01-01golang內(nèi)存對(duì)齊的項(xiàng)目實(shí)踐
本文主要介紹了golang內(nèi)存對(duì)齊的項(xiàng)目實(shí)踐,內(nèi)存對(duì)齊不僅有助于提高內(nèi)存訪問效率,還確保了與硬件接口的兼容性,是Go語言編程中不可忽視的重要優(yōu)化手段,下面就來介紹一下2025-02-02Go?Gin框架優(yōu)雅重啟和停止實(shí)現(xiàn)方法示例
Web應(yīng)用程序中,有時(shí)需要重啟或停止服務(wù)器,無論是因?yàn)楦麓a還是進(jìn)行例行維護(hù),這時(shí)需要保證應(yīng)用程序的可用性和數(shù)據(jù)的一致性,就需要優(yōu)雅地關(guān)閉和重啟應(yīng)用程序,即不丟失正在處理的請(qǐng)求和不拒絕新的請(qǐng)求,本文將詳解如何在Go語言中使用Gin這個(gè)框架實(shí)現(xiàn)優(yōu)雅的重啟停止2024-01-01Go語言基于viper的conf庫進(jìn)行配置文件解析
在現(xiàn)代軟件開發(fā)中,配置文件是不可或缺的一部分,如何高效地將這些格式解析到 Go 結(jié)構(gòu)體中,一直是開發(fā)者的痛點(diǎn),下面我們來看看如何使用conf進(jìn)行配置文件解析吧2025-03-03VS Code配置Go語言開發(fā)環(huán)境的詳細(xì)教程
這篇文章主要介紹了VS Code配置Go語言開發(fā)環(huán)境的詳細(xì)教程,本文通過實(shí)例代碼圖文相結(jié)合的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05