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>
วิธีใช้งาน:
- เปิด Terminal ไปที่ Folder ที่มี
main.goอยู่ แล้วรันgo run main.go - เปิดไฟล์
index.htmlใน Browser (Chrome, Firefox, อะไรก็ได้) - ลองพิมพ์ข้อความในช่อง 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 หลายๆ ตัวอีกเยอะเลย แต่แค่นี้ก็น่าจะพอเห็นภาพและเอาไปต่อยอดได้แล้วล่ะครับ โค้ดดิ้งมันก็สนุกตรงนี้แหละ!