Go Programming

WebSockets ve Go ile Gerçek Zamanlı Sohbet Uygulamaları Oluşturma

Gerçek zamanlı iletişim, anında mesajlaşma platformlarından işbirliği araçlarına ve canlı güncellemelere kadar modern web uygulamalarının temel taşları haline gelmiştir. Bu kapsamlı kılavuzda, Go'nun güçlü WebSocket uygulamasını ve modern web geliştirme uygulamalarını kullanarak sağlam, ölçeklenebilir gerçek zamanlı sohbet uygulamaları nasıl oluşturulacağını inceleyeceğiz.

WebSocket Temellerini Anlamak

WebSockets, istemci ve sunucu arasında tek, uzun süreli bir bağlantı üzerinden çift yönlü bir iletişim kanalı sağlar. Geleneksel HTTP isteklerinden farklı olarak, WebSockets her mesaj için yeni bağlantı kurma yükünü ortadan kaldırır ve bu nedenle gerçek zamanlı uygulamalar için idealdir.

WebSocket protokolü HTTP üzerine çalışır ve bağlantı HTTP'den WebSocket'e yükseltme ile başlatılan ilk el sıkışmayı gerektirir. Bu el sıkışma, Go'nun standart uygulaması dahil olmak üzere çoğu WebSocket kütüphanesi tarafından otomatik olarak işlenir.

Go Ortamını Ayarlama

Uygulamaya geçmeden önce Go 1.16+ yüklü olduğundan emin olun. Go için sağlam ve iyi test edilmiş bir WebSocket uygulaması sağlayan gorilla/websocket paketini kullanacağız.

go get github.com/gorilla/websocket

Çekirdek Sohbet Sunucusu Uygulaması

Çoklu istemcileri ve mesaj yayını yeteneklerini destekleyen temel bir sohbet sunucusu oluşturalım:

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)
}

İstemci İletişimi İşleme

İstemci yapısı, WebSocket bağlantısı üzerinden okuma ve yazma işlemlerini yönetir:

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
            }
        }
    }
}

Ön Yüz Entegrasyonu

Ön yüz JavaScript uygulaması basittir ve WebSocket bağlantısını kurar ve mesajları işler:

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 = '';
        });
    }
}

İleti Kalıcılığı ile Geliştirme

Üretim uygulamaları için mesajları bir veritabanına kaydetmek isteyeceksiniz. İşte sohbet sunucusunu Redis ile mesaj depolama için nasıl genişletebileceğiniz:

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)
}

Ölçeklenebilirlik Düşünceleri

Yüksek trafiğe sahip uygulamalar için, sabit oturumlarla bir yük dengeleyici uygulamayı veya sunucular arasında iletişim için NATS gibi bir mesaj aracısı kullanmayı düşünün. Tek sunucu yaklaşımı küçük uygulamalar için iyi çalışır, ancak dağıtık sistemler daha sofistike desenlere ihtiyaç duyar.

Güvenlik En İyi Uygulamaları

Kimlik doğrulama belirteçleri uygulayın, gelen tüm verileri doğrulayın ve üretimde güvenli WebSocket protokollerini (wss://) kullanın. Aşırı kullanım önlemek için oran sınırlaması düşünün ve bağlantı kararlılığını korumak için uygun hata işleme uygulayın.

Sonuç

Go ve WebSockets ile gerçek zamanlı sohbet uygulamaları oluşturmak, performans, güvenilirlik ve geliştirici üretkenliği açısından güçlü bir kombinasyon sunar. Uygun mimari planlama ve ölçeklenebilirlik konusunda dikkatli olunursa, WebSocket tabanlı sohbet sistemleri binlerce eşzamanlı bağlantı ile verimli bir şekilde başa çıkabilir. Bu kılavuzda gösterilen modüler yaklaşım, daha karmaşık gerçek zamanlı uygulamalar oluşturmak için sağlam bir temel sağlarken, Go'nun arka uç servisleri için mükemmel bir seçim yapmasını sağlayan performans özelliklerini korur.

Üretim dağıtımında çeşitli ağ koşullarıyla kapsamlı test etmeyi ve uygun hata işleme uygulamayı unutmayın. Go ekosistemi, WebSocket geliştirme işlemini hem basit hem de sağlam hale getiren mükemmel araçlar ve kütüphaneler sağlar.

Share: