WebSocket على Go: إنشاء تطبيقات الوقت الفعلي المتقدمة

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

WebSocket على Go: تقنيات متقدمة لتطبيقات الوقت الفعلي

\n\n

تطوير تطبيقات الوقت الفعلي على Go هي إحدى أقوى جوانب اللغة. الدعم المدمج للتزامن من خلال goroutines والقنوات يجعل Go خيارًا مثاليًا لخوادم WebSocket. ومع ذلك، عندما يتعلق الأمر بأنظمة الإنتاج التي تحتوي على آلاف الاتصالات المتزامنة، فإن المثال البسيط "Hello, WebSocket" يتوقف عن العمل. في هذه المقالة، سنناقش التقنيات المتقدمة: من تحسين تجمع الاتصالات إلى التوسع عبر وسطاء الرسائل.

\n\n

1. بنية تجمع الاتصالات: إدارة آلاف العملاء

\n\n

في المثال الأساسي، نقوم بإنشاء goroutine منفصلة لكل اتصال. هذا جيد لـ 100-200 عميل، ولكن مع 10,000+ اتصال، يصبح استهلاك الذاكرة والتكاليف الإضافية لتبديل السياق حرجة. النهج المتقدم هو استخدام تجمع اتصالات بعدد ثابت من العمال والقنوات.

\n\n
// Pool — تجمع اتصالات مع تحديد goroutines\ntype Pool struct {\n    connections map[*websocket.Conn]bool\n    broadcast   chan []byte\n    register    chan *websocket.Conn\n    unregister  chan *websocket.Conn\n}\n\nfunc NewPool(maxGoroutines int) *Pool {\n    return &Pool{\n        connections: make(map[*websocket.Conn]bool),\n        broadcast:   make(chan []byte, 256),\n        register:    make(chan *websocket.Conn),\n        unregister:  make(chan *websocket.Conn),\n    }\n}\n\nfunc (pool *Pool) Start() {\n    for {\n        select {\n        case conn := <-pool.register:\n            pool.connections[conn] = true\n        case conn := <-pool.unregister:\n            if _, ok := pool.connections[conn]; ok {\n                delete(pool.connections, conn)\n                conn.Close()\n            }\n        case message := <-pool.broadcast:\n            for conn := range pool.connections {\n                go func(c *websocket.Conn) {\n                    if err := c.WriteMessage(websocket.TextMessage, message); err != nil {\n                        pool.unregister <- c\n                    }\n                }(conn)\n            }\n        }\n    }\n}
\n\n

لماذا يعمل هذا؟ تعمل القنوات كمخازن مؤقتة، مما يمنع الحظر. يدير select واحد جميع الأحداث، ويتم إنشاء goroutines للكتابة فقط عند الحاجة. هذا يقلل من الحمل الأقصى على GC (جامع القمامة).

\n\n

2. الإغلاق اللطيف وإدارة حالة الاتصالات

\n\n

عند إعادة تشغيل الخادم، من المهم إغلاق جميع اتصالات WebSocket بشكل صحيح، مع إرسال رسالة إنهاء للعملاء. تجاهل هذه الخطوة يؤدي إلى اتصالات معلقة وتسرب للذاكرة.

\n\n
// Server — خادم WebSocket مع إغلاق لطيف\ntype Server struct {\n    pool    *Pool\n    httpSrv *http.Server\n}\n\nfunc (s *Server) Shutdown(ctx context.Context) error {\n    // 1. إغلاق خادم HTTP (لا يتم قبول اتصالات جديدة)\n    if err := s.httpSrv.Shutdown(ctx); err != nil {\n        return err\n    }\n\n    // 2. إرسال رسالة إنهاء لجميع العملاء\n    shutdownMsg := []byte(`{"type":"shutdown","message":"server is going down"}`)\n    s.pool.broadcast <- shutdownMsg\n\n    // 3. إغلاق جميع الاتصالات في التجمع\n    for conn := range s.pool.connections {\n        conn.WriteMessage(websocket.CloseMessage, \n            websocket.FormatCloseMessage(websocket.CloseNormalClosure, "server shutdown"))\n        conn.Close()\n    }\n\n    return nil\n}\n\n// الاستخدام في main\nfunc main() {\n    pool := NewPool(1000)\n    go pool.Start()\n\n    srv := &Server{pool: pool}\n    srv.httpSrv = &http.Server{Addr: ":8080"}\n\n    // معالجة الإشارات\n    go func() {\n        sigint := make(chan os.Signal, 1)\n        signal.Notify(sigint, os.Interrupt, syscall.SIGTERM)\n        <-sigint\n        ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\n        defer cancel()\n        srv.Shutdown(ctx)\n    }()\n\n    log.Fatal(srv.httpSrv.ListenAndServ                

المدونات

توصيات الكتب