Go Programming

ساخت برنامه‌های چت بلادرنگ با WebSockets و Go

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

درک مبانی WebSocket

WebSocket‌ها کانال ارتباطی دوطرفه را با استفاده از یک اتصال طولانی و مداوم بین کلاینت و سرور فراهم می‌کنند. برخلاف درخواست‌های HTTP سنتی، WebSocket‌ها اضافه‌بار ایجاد اتصال‌های جدید برای هر پیام را حذف می‌کنند که آن‌ها را برای برنامه‌های بلادرنگ مناسب می‌سازد.

پروتکل WebSocket بر روی HTTP عمل می‌کند و نیازمند یک handshake اولیه است که اتصال را از HTTP به WebSocket تغییر می‌دهد. این handshake به‌صورت خودکار توسط بیشتر کتابخانه‌های WebSocket، از جمله پیاده‌سازی استاندارد Go، مدیریت می‌شود.

راه‌اندازی محیط Go

قبل از ورود به پیاده‌سازی، مطمئن شوید Go 1.16+ نصب شده است. ما از بسته gorilla/websocket استفاده خواهیم کرد که پیاده‌سازی قوی و تست‌شده‌ای از WebSocket برای Go فراهم می‌کند.

go get github.com/gorilla/websocket

پیاده‌سازی پایه سرور چت

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

package main

import (
    "fmt"
    "log"
    "net/http"
    "sync"
    
    "github.com/gorilla/websocket"
)

type Client struct {
    conn *websocket.Conn
    send chan []byte
    id   string
}

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

var upgrader = websocket.Upgrader{
    CheckOrigin: func(r *http.Request) bool {
        return true
    },
}

func NewChatServer() *ChatServer {
    return &ChatServer{
        clients:    make(map[*Client]bool),
        broadcast:  make(chan []byte),
        register:   make(chan *Client),
        unregister: make(chan *Client),
    }
}

func (server *ChatServer) run() {
    for {
        select {
        case client := <-server.register:
            server.mutex.Lock()
            server.clients[client] = true
            server.mutex.Unlock()
            log.Printf("Client connected: %s", client.id)
            
        case client := <-server.unregister:
            if _, ok := server.clients[client]; ok {
                server.mutex.Lock()
                delete(server.clients, client)
                server.mutex.Unlock()
                close(client.send)
                log.Printf("Client disconnected: %s", client.id)
            }
            
        case message := <-server.broadcast:
            server.mutex.RLock()
            for client := range server.clients {
                select {
                case client.send <- message:
                default:
                    close(client.send)
                    delete(server.clients, client)
                }
            }
            server.mutex.RUnlock()
        }
    }
}

func (server *ChatServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Print("upgrade error:", err)
        return
    }
    
    client := &Client{
        conn: conn,
        send: make(chan []byte, 256),
        id:   fmt.Sprintf("client-%d", time.Now().Unix()),
    }
    
    server.register <- client
    
    go client.writePump()
    go client.readPump(server)
}

مدیریت ارتباط کلاینت

ساختار کلاینت هم خواندن و هم نوشتن از روی اتصال WebSocket را مدیریت می‌کند:

func (client *Client) readPump(server *ChatServer) {
    defer func() {
        server.unregister <- client
        client.conn.Close()
    }()
    
    for {
        _, message, err := client.conn.ReadMessage()
        if err != nil {
            if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
                log.Printf("error: %v", err)
            }
            break
        }
        
        message = bytes.TrimSpace(message)
        server.broadcast <- message
    }
}

func (client *Client) writePump() {
    defer func() {
        client.conn.Close()
    }()
    
    for {
        select {
        case message, ok := <-client.send:
            if !ok {
                client.conn.WriteMessage(websocket.CloseMessage, []byte{})
                return
            }
            
            w, err := client.conn.NextWriter(websocket.TextMessage)
            if err != nil {
                return
            }
            w.Write(message)
            
            if err := w.Close(); err != nil {
                return
            }
        }
    }
}

ادغام با فرانت‌اند

پیاده‌سازی جاوااسکریپت فرانت‌اند ساده است، اتصال WebSocket را برقرار می‌کند و پیام‌ها را مدیریت می‌کند:

class ChatClient {
    constructor() {
        this.ws = new WebSocket('ws://localhost:8080');
        this.messages = document.getElementById('messages');
        this.messageForm = document.getElementById('message-form');
        this.messageInput = document.getElementById('message-input');
        
        this.ws.onopen = () => {
            console.log('Connected to chat server');
        };
        
        this.ws.onmessage = (event) => {
            const message = document.createElement('div');
            message.textContent = event.data;
            this.messages.appendChild(message);
            this.messages.scrollTop = this.messages.scrollHeight;
        };
        
        this.messageForm.addEventListener('submit', (e) => {
            e.preventDefault();
            const message = this.messageInput.value;
            this.ws.send(message);
            this.messageInput.value = '';
        });
    }
}

بهبود با ذخیره‌سازی پیام‌ها

برای برنامه‌های تولیدی، می‌خواهید پیام‌ها را در یک پایگاه داده ذخیره کنید. در اینجا نحوه گسترش سرور چت با Redis برای ذخیره‌سازی پیام‌ها آمده است:

import (
    "github.com/go-redis/redis/v8"
    "context"
)

type Message struct {
    ID      string `json:"id"`
    Content string `json:"content"`
    Time    int64  `json:"time"`
    User    string `json:"user"`
}

func (server *ChatServer) storeMessage(message string, user string) {
    msg := Message{
        ID:      uuid.New().String(),
        Content: message,
        Time:    time.Now().Unix(),
        User:    user,
    }
    
    jsonMsg, _ := json.Marshal(msg)
    server.redisClient.LPush(context.Background(), "chat_messages", jsonMsg)
}

ملاحظات مقیاس‌پذیری

برای برنامه‌های با ترافیک بالا، می‌توانید از یک بارگذاری بار (load balancer) با جلسات چسبان یا استفاده از یک بروکر پیام مانند NATS برای ارتباط بین سرورها استفاده کنید. رویکرد یک سرور برای برنامه‌های کوچک مناسب است، اما سیستم‌های توزیع‌شده نیازمند الگوهای پیشرفته‌تری هستند.

روش‌های امنیتی بهتر

استفاده از توکن‌های احراز هویت، اعتبارسنجی تمام داده‌های ورودی و استفاده از پروتکل‌های امن WebSocket (wss://) در محیط تولید. در نظر گرفتن محدودیت نرخ برای جلوگیری از سوءاستفاده و پیاده‌سازی مدیریت صحیح خطا برای حفظ پایداری اتصال.

نتیجه‌گیری

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

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

Share: