مقدمة في WebSocket على Go: لماذا الوقت الفعلي؟
تتطلب تطبيقات الويب الحديثة بشكل متزايد توصيل البيانات الفوري. لم يعد طلب/استجابة HTTP التقليدي كافياً لمهام الدردشات، الألعاب عبر الإنترنت، أسعار البورصة ولوحات المراقبة. هنا يأتي دور WebSocket — البروتوكول الذي يوفر اتصالاً ثنائي الاتجاه مستمراً بين العميل والخادم. في هذه المقالة، سنشرح كيفية إنشاء تطبيقات الوقت الفعلي على Go (Golang) باستخدام WebSocket، من التثبيت إلى حلول الإنتاج.
Go مثالي لـ WebSocket بفضل نموذج التزامن الخفيف (الغوروتينات) والأداء العالي. سننتقل من خادم الصدى البسيط إلى دردشة كاملة ونظام مراقبة.
أساسيات WebSocket وإعداد البيئة
ما هو WebSocket؟
WebSocket هو بروتوكول يعمل فوق TCP. يبدأ بمصافحة HTTP (ترقية)، ثم يتحول الاتصال إلى قناة ثنائية الاتجاه دائمة. على عكس HTTP، يمكن للخادم إرسال البيانات إلى العميل دون طلب — وهذا هو أساس الوقت الفعلي.
التثبيت والمكتبات
لا توجد مكتبة قياسية لـ WebSocket في Go، ولكن هناك مكتبة شائعة gorilla/websocket. سنقوم بتثبيتها:
go get github.com/gorilla/websocketيمكنك أيضاً استخدام nhooyr.io/websocket (بديل أكثر حداثة)، لكن في هذه المقالة سنركز على gorilla باعتبارها الأكثر انتشاراً.
أول خادم WebSocket على Go
لنكتب خادم صدى بسيطاً يستقبل الرسالة ويرسلها مرة أخرى:
package main
import ( "log" "net/http" "github.com/gorilla/websocket")
var upgrader = websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true // للتطوير },}
func echoHandler(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if err != nil { log.Print("Upgrade:", err) return } defer conn.Close()
for { mt, message, err := conn.ReadMessage() if err != nil { log.Println("Read:", err) break } log.Printf("Received: %s", message) err = conn.WriteMessage(mt, message) if err != nil { log.Println("Write:", err) break } }}
func main() { http.HandleFunc("/echo", echoHandler) log.Println("Server started on :8080") log.Fatal(http.ListenAndServe(":8080", nil))}هذا مثال عملي بسيط. لاحظ Upgrader — فهو يتحقق من رؤوس HTTP ويحول الاتصال.
بنية تطبيق الوقت الفعلي: دردشة على Go
إدارة الاتصالات
للدردشة، نحتاج إلى إدارة العديد من العملاء. لننشئ هيكل Hub الذي سيخزن جميع الاتصالات النشطة ويوزع الرسائل:
type Client struct { hub *Hub conn *websocket.Conn send chan []byte}
type Hub struct { clients map[*Client]bool broadcast chan []byte register chan *Client unregister chan *Client}
func newHub() *Hub { return &Hub{ clients: make(map[*Client]bool), broadcast: make(chan []byte), register: make(chan *Client), unregister: make(chan *Client), }}
func (h *Hub) run() { for { select { case client := <-h.register: h.clients[client] = true case client := <-h.unregister: if _, ok := h.clients[client]; ok { delete(h.clients, client) close(client.send) } case message := <-h.broadcast: for client := range h.clients { select { case client.send <- message: default: