Go Programming

ساخت برنامه‌های چت در زمان واقعی با Go و WebSockets: راهنمای کامل

ارتباط در زمان واقعی پایه‌ی برنامه‌های وب مدرن است و سیستم‌های چت یکی از جالب‌ترین موارد استفاده برای قابلیت‌های در زمان واقعی هستند. در این راهنمای جامع، نحوه ساخت برنامه‌های چت مقاوم و مقیاس‌پذیر در زمان واقعی با استفاده از Go و WebSockets را بررسی خواهیم کرد.

مقدمه‌ای بر چت در زمان واقعی با Go

مدل هم‌زمانی، ویژگی‌های عملکردی و سادگی Go، انتخاب عالی‌ای برای ساخت برنامه‌های در زمان واقعی محسوب می‌شود. وقتی با WebSockets ترکیب شود، Go یک پایه قدرتمند برای ایجاد سیستم‌های چت واکنش‌گویی فراهم می‌کند که می‌تواند هزاران اتصال همزمان را به‌طور کارآمد مدیریت کند.

WebSockets ارتباط دوطرفه بین کلاینت و سرور را فراهم می‌کنند و اضافه‌بار HTTP polling را حذف کرده و تعامل واقعی در زمان واقعی را فعال می‌سازند. در این آموزش، یک برنامه چت کامل ایجاد خواهیم کرد که نشان می‌دهد چگونه از بهترین شیوه‌ها برای پیاده‌سازی WebSocket در Go استفاده کنیم.

راه‌اندازی پایه

اول، ساختار پروژه خود را و بسته‌های لازم را وارد کنید:

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "sync"
    "time"

    "github.com/gorilla/websocket"
)

type Message struct {
    Username string `json:"username"`
    Text     string `json:"text"`
    Time     string `json:"time"`
}

type Client struct {
    conn *websocket.Conn
    send chan Message
    name string
}

type ChatRoom struct {
    clients    map[*Client]bool
    broadcast  chan Message
    register   chan *Client
    unregister chan *Client
    mutex      sync.RWMutex
}

پیاده‌سازی سرور WebSocket

سرور چت ما باید اتصالات را مدیریت کند، پیام‌ها را پخش کند و ثبت کاربران را مدیریت کند. این‌جوری می‌توانیم عملکرد اصلی را پیاده‌سازی کنیم:

func NewChatRoom() *ChatRoom {
    return &ChatRoom{
        clients:    make(map[*Client]bool),
        broadcast:  make(chan Message),
        register:   make(chan *Client),
        unregister: make(chan *Client),
    }
}

func (room *ChatRoom) Run() {
    for {
        select {
        case client := <-room.register:
            room.mutex.Lock()
            room.clients[client] = true
            room.mutex.Unlock()
            log.Printf("Client connected: %s", client.name)

        case client := <-room.unregister:
            if _, ok := room.clients[client]; ok {
                room.mutex.Lock()
                delete(room.clients, client)
                room.mutex.Unlock()
                close(client.send)
                log.Printf("Client disconnected: %s", client.name)
            }

        case message := <-room.broadcast:
            room.mutex.RLock()
            for client := range room.clients {
                select {
                case client.send <- message:
                default:
                    close(client.send)
                    room.mutex.Lock()
                    delete(room.clients, client)
                    room.mutex.Unlock()
                }
            }
            room.mutex.RUnlock()
        }
    }
}

مدیریت اتصالات کلاینت

فرآیند ارتقاء WebSocket نیازمند مدیریت دقیق برای اطمینان از برقراری اتصال صحیح و مدیریت خطاها است:

var upgrader = websocket.Upgrader{
    CheckOrigin: func(r *http.Request) bool {
        // در محیط تولید، منبع را بر اساس دامنه‌های مجاز اعتبارسنجی کنید
        return true
    },
}

func (room *ChatRoom) handleConnections(w http.ResponseWriter, r *http.Request) {
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Printf("WebSocket upgrade error: %v", err)
        return
    }
    defer conn.Close()

    // گرفتن نام کلاینت از پارامتر کوئری
    name := r.URL.Query().Get("name")
    if name == "" {
        name = "Anonymous"
    }

    client := &Client{
        conn: conn,
        send: make(chan Message, 256),
        name: name,
    }

    // ثبت کلاینت
    room.register <- client
    defer func() {
        room.unregister <- client
    }()

    // شروع خواندن پیام‌ها
    go client.readPump(room)
    
    // شروع ارسال پیام‌ها
    client.writePump()
}

مدیریت پیام‌ها و پخش آن‌ها

پیاده‌سازی کلاینت ما باید هم خواندن پیام‌های ورودی و هم نوشتن پیام‌های خروجی را مدیریت کند:

func (c *Client) readPump(room *ChatRoom) {
    defer c.conn.Close()
    
    for {
        var msg Message
        err := c.conn.ReadJSON(&msg)
        if err != nil {
            if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
                log.Printf("WebSocket error: %v", err)
            }
            break
        }

        // تنظیم زمان
        msg.Time = time.Now().Format("15:04:05")
        
        // پخش پیام به تمام کلاینت‌ها
        room.broadcast <- msg
    }
}

func (c *Client) writePump() {
    defer c.conn.Close()
    
    for {
        select {
        case message, ok := <-c.send:
            if !ok {
                c.conn.WriteMessage(websocket.CloseMessage, []byte{})
                return
            }

            err := c.conn.WriteJSON(message)
            if err != nil {
                log.Printf("Error sending message: %v", err)
                break
            }
        }
    }
}

پیاده‌سازی فرانت‌اند

این یک فرانت‌اند ساده HTML/JavaScript برای تست سرور چت WebSocket ما است:

<!DOCTYPE html>
<html>
<head>
    <title>Go Chat</title>
</head>
<body>
    <div id="chat">
        <div id="messages"></div>
        <input type="text" id="messageInput" placeholder="Type your message..." />
        <button onclick="sendMessage()">Send</button>
    </div>

    <script>
        const ws = new WebSocket('ws://localhost:8080/ws?name=GoUser');
        const messages = document.getElementById('messages');
        const input = document.getElementById('messageInput');

        ws.onmessage = function(event) {
            const msg = JSON.parse(event.data);
            const messageElement = document.createElement('div');
            messageElement.textContent = `${msg.username}: ${msg.text} (${msg.time})`;
            messages.appendChild(messageElement);
            messages.scrollTop = messages.scrollHeight;
        };

        function sendMessage() {
            const text = input.value;
            if (text.trim()) {
                ws.send(JSON.stringify({
                    username: 'GoUser',
                    text: text,
                    time: new Date().toLocaleTimeString()
                }));
                input.value = '';
            }
        }

        input.addEventListener('keypress', function(e) {
            if (e.key === 'Enter') {
                sendMessage();
            }
        });
    </script>
</body>
</html>

اجرای برنامه

این تابع اصلی برای اتصال همه چیز است:

func main() {
    room := NewChatRoom()
    
    // شروع goroutine چت روم
    go room.Run()

    // HTTP handlers
    http.HandleFunc("/ws", room.handleConnections)
    
    log.Println("Chat server starting on :8080")
    if err := http.ListenAndServe(":8080", nil); err != nil {
        log.Fatal("Server failed to start:", err)
    }
}

ملاحظات تولید

برای استقرار در محیط تولید، موارد زیر را در نظر بگیرید:

  • پایداری پیام با استفاده از Redis یا ذخیره‌سازی دیتابیس
  • مکانیزم‌های احراز هویت و مجوز
  • محدود کردن نرخ برای جلوگیری از اسپم
  • رمزگذاری پیام برای امنیت
  • تعادل بار برای مقیاس‌پذیری افقی

نتیجه‌گیری

ساخت برنامه‌های چت در زمان واقعی با Go و WebSockets یک پایه عالی برای سیستم‌های ارتباطی مقیاس‌پذیر و با عملکرد بالا فراهم می‌کند. ویژگی‌های هم‌زمانی داخلی Go، ترکیب با کتابخانه Gorilla WebSocket، یک پلتفرم قوی برای برنامه‌های در زمان واقعی ایجاد می‌کند.

این پیاده‌سازی مفاهیم اصلی را نشان می‌دهد که می‌توانند با ویژگی‌های اضافی مانند احراز هویت کاربران، پایداری پیام، پیام‌های خصوصی و مدیریت اتاق‌های پیشرفته گسترش یابند. طراحی ماژولار اجازه افزودن ویژگی‌ها را بدون کاهش عملکرد و مقیاس‌پذیری فراهم می‌کند.

با رعایت این الگوها و شیوه‌های بهتر، شما به‌خوبی برای ساخت برنامه‌های چت در زمان واقعی آماده‌اید که می‌توانند حمله کاربران زیادی را تحمل کنند و همچنان عملکرد واکنش‌گویی داشته باشند.

Share: