WebSocket على Go: تقنيات متقدمة لتطبيقات الوقت الفعلي
\n\nتطوير تطبيقات الوقت الفعلي على Go هي إحدى أقوى جوانب اللغة. الدعم المدمج للتزامن من خلال goroutines والقنوات يجعل Go خيارًا مثاليًا لخوادم WebSocket. ومع ذلك، عندما يتعلق الأمر بأنظمة الإنتاج التي تحتوي على آلاف الاتصالات المتزامنة، فإن المثال البسيط "Hello, WebSocket" يتوقف عن العمل. في هذه المقالة، سنناقش التقنيات المتقدمة: من تحسين تجمع الاتصالات إلى التوسع عبر وسطاء الرسائل.
\n\n1. بنية تجمع الاتصالات: إدارة آلاف العملاء
\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\n2. الإغلاق اللطيف وإدارة حالة الاتصالات
\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