Go กับ WebSocket: ทำแชทง่ายๆ ในไม่กี่นาที

เบื่อไหมครับกับการทำ Web App แล้วต้องคอย fetch ข้อมูลซ้ำๆ เพื่ออัปเดตสถานะ? หรือ long polling ที่โคตรเปลืองทรัพยากร? วันนี้เราจะมาลองของที่เขาเรียกว่า WebSocket กันดูครับ แล้วจะลองสร้าง Server ง่ายๆ ด้วยภาษา Go (Golang) ที่เหมาะกับงาน Concurrent แบบนี้มากๆ

WebSocket คืออะไร?

เอาง่ายๆ มันคือการสื่อสารแบบ สองทาง (Bi-directional) ที่เปิด Connection ค้างไว้ได้เลย คือ Server ส่งหา Client ได้ทันที Client ก็ส่งหา Server ได้ทันที ไม่ต้องรอ Request-Response เหมือน HTTP ทั่วไป นึกภาพ Chat Application, Game Online, หรือ Dashboard Real-time คือใช้เจ้านี่แหละ เหมาะสุดๆ

ทำไมต้อง Go ด้วยหรอ? ส่วนตัวผมมองว่า Go มันเกิดมาเพื่อสิ่งนี้ครับ ด้วย Goroutines และ Channels ที่ทำให้การเขียนโปรแกรมแบบ Concurrency (ทำงานหลายอย่างพร้อมกัน) มันโคตรง่ายกว่าภาษาอื่นเยอะมากๆ ไม่ต้องมานั่งจัดการ Thread ให้ปวดหัวเลย

มาเริ่มลงมือทำกันเลยดีกว่า!

ก่อนอื่นเลย ต้องมี Go ติดเครื่องก่อนนะ ถ้ายังไม่มีก็ไปโหลดจาก golang.org ได้เลย ส่วน Library ที่จะใช้ เราจะใช้ gorilla/websocket ครับ เพราะ Go Standard Library มันดิบไปนิดนึง เขียนเองเยอะเกินไป เสียเวลาครับ ตัว gorilla นี่แหละ พระเอกของเรา

go mod init mywebsocketapp
go get github.com/gorilla/websocket

จากนั้นก็สร้างไฟล์ main.go ขึ้นมาเลยครับ ลอกตามนี้ได้เลย

package main

import (
    "log"
    "net/http"

    "github.com/gorilla/websocket" // ตัวช่วยพระเอกของเรา
)

// ตั้งค่า Upgrader สำหรับ WebSocket
// ReadBufferSize กับ WriteBufferSize เอาไว้กำหนดขนาด Buffer ในการอ่าน/เขียนข้อมูล
// CheckOrigin สำคัญมาก! ปกติ Production ต้องเช็ค Origin ดีๆ เพื่อความปลอดภัย
// แต่อันนี้แค่ Demo ไง เลย Return true ไปก่อน ให้ใครเชื่อมก็ได้เลย
var upgrader = websocket.Upgrader{
    ReadBufferSize:  1024,
    WriteBufferSize: 1024,
    CheckOrigin: func(r *http.Request) bool {
        return true // ยอมให้ทุก Origin เชื่อมต่อได้ (สำหรับทดสอบเท่านั้น!)
    },
}

// ฟังก์ชันสำหรับจัดการ Connection ของ WebSocket
func handleConnections(w http.ResponseWriter, r *http.Request) {
    // อัปเกรด HTTP Connection เป็น WebSocket Connection
    ws, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Fatal("Upgrade error:", err) // ตรงนี้ถ้ามีปัญหาตอน Upgrade ก็จบเลย
    }
    defer ws.Close() // สำคัญมาก! ต้องปิด Connection เสมอเมื่อฟังก์ชันจบ หรือ Client หลุด

    log.Println("มี Client เชื่อมต่อเข้ามาแล้ว!")

    // Loop ไม่รู้จบ เพื่อรอรับข้อความจาก Client
    for {
        messageType, p, err := ws.ReadMessage() // อ่านข้อความที่ส่งมาจาก Client
        if err != nil {
            // เจอ Error เช่น Client ปิด Connection, Network หลุด
            log.Println("เกิดข้อผิดพลาดในการอ่านข้อความ:", err)
            // ผมเคยเจอ Error ประมาณ "websocket: close 1001 (going away)"
            // อันนี้แปลว่า Client มันปิดหน้าต่าง หรือปิด Connection ไปแล้วไง
            // ต้อง handle ดีๆ ไม่งั้น Server อาจจะค้างได้ หรือกิน CPU ฟรี
            break // ออกจาก Loop เพราะ Connection มีปัญหาแล้ว
        }
        log.Printf("ได้รับข้อความ: %s\n", p) // พิมพ์ข้อความที่ได้รับออก Console

        // ตัวอย่าง: Echo กลับไปหา Client เลย
        err = ws.WriteMessage(messageType, p)
        if err != nil {
            log.Println("เกิดข้อผิดพลาดในการส่งข้อความกลับ:", err)
            break // ออกจาก Loop
        }
    }
}

func main() {
    // กำหนด Endpoint /ws ให้ใช้ฟังก์ชัน handleConnections
    http.HandleFunc("/ws", handleConnections)

    log.Println("Server พร้อมทำงานที่พอร์ต :8080 แล้วนะ!")
    // เริ่ม Server HTTP
    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        log.Fatal("ListenAndServe error:", err) // ถ้า Server สตาร์ทไม่ขึ้นก็จบ
    }
}

มาลองทดสอบกับ Client กันดูบ้าง

สร้างไฟล์ index.html ขึ้นมา จะวางไว้ไหนก็ได้ แค่เปิดด้วย Browser ได้ก็พอ

<!DOCTYPE html>
<html>
<head>
    <title>ทดสอบ WebSocket Client</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        #messages { border: 1px solid #ccc; padding: 10px; min-height: 200px; overflow-y: scroll; margin-top: 10px; }
        .client-msg { color: blue; }
        .server-msg { color: green; }
        .status-msg { color: gray; font-style: italic; }
        .error-msg { color: red; font-weight: bold; }
    </style>
</head>
<body>
    <h1>WebSocket Client ง่ายๆ</h1>
    <input type="text" id="messageInput" placeholder="พิมพ์ข้อความ..." size="50">
    <button onclick="sendMessage()">ส่ง</button>
    <div id="messages"></div>

    <script>
        // เปลี่ยน localhost:8080 ถ้า Server ของคุณรันอยู่พอร์ตอื่น หรือ IP อื่น
        const ws = new WebSocket("ws://localhost:8080/ws");
        const messagesDiv = document.getElementById('messages');
        const messageInput = document.getElementById('messageInput');

        ws.onopen = () => {
            messagesDiv.innerHTML += '<p class="status-msg">เชื่อมต่อกับ Server แล้ว!</p>';
            console.log("Connected to WebSocket");
        };

        ws.onmessage = (event) => {
            // ข้อความที่ได้รับจาก Server
            messagesDiv.innerHTML += `<p class="server-msg">Server: ${event.data}</p>`;
            console.log("Message from server:", event.data);
        };

        ws.onclose = () => {
            messagesDiv.innerHTML += '<p class="status-msg error-msg">หลุดการเชื่อมต่อจาก Server!</p>';
            console.log("Disconnected from WebSocket");
        };

        ws.onerror = (error) => {
            messagesDiv.innerHTML += `<p class="error-msg">เกิดข้อผิดพลาด: ${error.message}</p>`;
            console.error("WebSocket Error:", error);
        };

        function sendMessage() {
            const message = messageInput.value;
            if (message) {
                ws.send(message); // ส่งข้อความไปที่ Server
                messagesDiv.innerHTML += `<p class="client-msg">คุณ: ${message}</p>`;
                messageInput.value = ''; // ล้างข้อความใน Input
            }
        }

        // ให้กด Enter แล้วส่งได้เลย
        messageInput.addEventListener('keypress', function(e) {
            if (e.key === 'Enter') {
                sendMessage();
            }
        });
    </script>
</body>
</html>

วิธีใช้งาน:

  1. เปิด Terminal ไปที่ Folder ที่มี main.go อยู่ แล้วรัน go run main.go
  2. เปิดไฟล์ index.html ใน Browser (Chrome, Firefox, อะไรก็ได้)
  3. ลองพิมพ์ข้อความในช่อง Input แล้วกด Enter หรือกดปุ่ม 'ส่ง' คุณจะเห็นข้อความไปโผล่ที่ Server แล้ว Server ก็ Echo ข้อความนั้นกลับมาหา Client อีกทีนึง

สรุปท้ายบท:

จะเห็นว่าการทำ WebSocket Server ด้วย Go นี่ง่ายมากๆ เลยใช่ไหมครับ? ด้วยความสามารถในการจัดการ Concurrency ที่ดีของ Go ทำให้เราเขียนโค้ดสำหรับ Real-time App ได้อย่างมีประสิทธิภาพ แล้ว gorilla/websocket ก็ช่วยให้ชีวิตเราง่ายขึ้นไปอีกเยอะเลยนะ

บทความนี้แค่เบสิกๆ นะครับ จริงๆ ใน Production ต้องคิดเรื่องการจัดการ Connection หลายๆ ตัว, การ Broadcast ข้อความหาทุก Client, Authentication, หรือการใช้ Redis Pub/Sub มาช่วยกระจายข้อความระหว่าง Server หลายๆ ตัวอีกเยอะเลย แต่แค่นี้ก็น่าจะพอเห็นภาพและเอาไปต่อยอดได้แล้วล่ะครับ โค้ดดิ้งมันก็สนุกตรงนี้แหละ!

Read more

ไอลีน กู: ตำนานนักสกีฟรีสไตล์ผู้พลิกโฉมวงการและความหมายของชัยชนะ

ไอลีน กู: ตำนานนักสกีฟรีสไตล์ผู้พลิกโฉมวงการและความหมายของชัยชนะ

เจาะลึกเรื่องราวของ Eileen Gu นักสกีฟรีสไตล์ผู้สร้างประวัติศาสตร์ในโอลิมปิก 2026 สถิติที่ไม่เคยมีมาก่อน ประเด็นถกเถียง และความแข็งแกร่งส่วนตัวที่ทำให้เธอก้าวสู่ระดับโลก

By ทีมงาน devdog
วันพระ: คู่มือฉบับสมบูรณ์สำหรับพุทธศาสนิกชนและผู้สนใจยุคใหม่

วันพระ: คู่มือฉบับสมบูรณ์สำหรับพุทธศาสนิกชนและผู้สนใจยุคใหม่

เจาะลึกวันพระและความสำคัญของวันมาฆบูชา 2569 ทั้งวันหยุดราชการ ธนาคาร กิจกรรมเวียนเทียนต้นไม้ และผลกระทบต่อบริการขนส่ง เตรียมตัววางแผนทำบุญและพักผ่อน

By ทีมงาน devdog
ถอดรหัสรักแท้: "บังมัดคลองตันต้นข้าว" เรื่องราวที่สะท้อนการให้อภัยและการเริ่มต้นใหม่

ถอดรหัสรักแท้: "บังมัดคลองตันต้นข้าว" เรื่องราวที่สะท้อนการให้อภัยและการเริ่มต้นใหม่

เจาะลึกงานวิวาห์ "บังมัดคลองตัน" กับ "ต้นข้าว มิสแกรนด์" พร้อมเหตุผลจากใจเจ้าสาวที่เลือกความรักเหนือกาลเวลาและคำวิจารณ์ สู่การเริ่มต้นชีวิตคู่ที่สะท้อนการให้อภัย

By ทีมงาน devdog
ไฮไลท์บอลไทยลีก 2: มหาสารคาม เอสบีที เอฟซี กับฟอร์มร้อนแรงสู่เส้นทางเพลย์ออฟ

ไฮไลท์บอลไทยลีก 2: มหาสารคาม เอสบีที เอฟซี กับฟอร์มร้อนแรงสู่เส้นทางเพลย์ออฟ

เจาะลึกไฮไลท์บอลไทยลีก 2 ของมหาสารคาม เอสบีที เอฟซี กับฟอร์มร้อนแรง ชัยชนะสำคัญจาก ชิตชนก และบทบาทโค้ชดุสิต สู่เส้นทางเพลย์ออฟที่น่าจับตา!

By ทีมงาน devdog