
สารบัญ
- ทำไมต้อง Push Notification แบบ Realtime?
- รู้จัก SSE และ WebSocket
- สแตกเทคโนโลยีที่ใช้
- เขียน SSE Backend (Spring WebFlux)
- เขียน WebSocket Backend (Spring WebSocket)
- โค้ด Frontend รับข้อความ
- การ Deploy & Scaling
- เปรียบเทียบ SSE กับ WebSocket
- Security & Best Practices
- Monitoring & Benchmark
- FAQ
- สรุป
1. ทำไมต้อง Push Notification แบบ Realtime?
ประสบการณ์ผู้ใช้ยุคปัจจุบันคาดหวังข้อมูล “เด้ง” มาทันทีไม่ต้องรีเฟรช ไม่ว่าจะเป็น ออเดอร์ใหม่, ข้อความแชท, ราคาหุ้น หรือ สถานะงาน หากยังใช้วิธี polling (ไถวนถามเซิร์ฟเวอร์ทุก X วินาที) นอกจากสิ้นเปลืองแบนด์วิธยังเพิ่มโหลด CPU แบบไร้สาระ Server-Sent Events (SSE) และ WebSocket จึงกลายเป็นรากฐานของแอป Realtime สมัยใหม่

2. รู้จัก SSE และ WebSocket
2.1 Server-Sent Events (SSE)

- เชื่อมต่อ HTTP ที่ฝั่ง Client เปิดค้างไว้ (long-lived connection)
- เหมาะกับ “one-way stream” – server ➜ client
- เบา (ใช้ text/event-stream) ไม่ต้องทำ handshake พิเศษ
- Reconnect อัตโนมัติด้วย header
Last-Event-ID
2.2 WebSocket

- เปลี่ยน HTTP/1.1 เป็น
wss://
บน TCP ซึ่งรับ-ส่งสองทาง - Frame binary/text ส่งได้ทั้ง server➜client และ client➜server
- เหมาะกับแอปแชท เกมออนไลน์ หรือ collaborative editing
- ต้องมี load balancer ที่รองรับ sticky-session / L4
3. สแตกเทคโนโลยีที่ใช้
- Java 17+ + Spring Boot 3.5.x
- Spring WebFlux (Reactive) สำหรับ SSE
- Spring WebSocket + STOMP สำหรับ WebSocket
- Frontend สาธิตด้วย Vanilla JS & Vite
- Database ทดลอง: Redis Stream เป็น event-source
4. เขียน SSE Backend (Spring WebFlux)
4.1 Controller + Event Flux
@RestController @RequestMapping("/api") @RequiredArgsConstructor public class SseController { private final Sinks.Many<ServerSentEvent<String>> sink = Sinks.many().multicast().onBackpressureBuffer(); @GetMapping(path = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<ServerSentEvent<String>> stream() { return sink.asFlux(); } @PostMapping("/notify") public Mono<Void> send(@RequestBody String message) { sink.tryEmitNext(ServerSentEvent.builder(message).build()); return Mono.empty(); } }
4.2 ทดสอบส่ง cURL
curl -N http://localhost:8080/api/stream # แท็บใหม่ curl -XPOST http://localhost:8080/api/notify \ -H "Content-Type:text/plain" -d "Hello SSE!"
5. เขียน WebSocket Backend (Spring WebSocket + STOMP)
5.1 WebSocket Config
@Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/ws").setAllowedOrigins("*").withSockJS(); } @Override public void configureMessageBroker(MessageBrokerRegistry registry) { registry.enableSimpleBroker("/topic"); registry.setApplicationDestinationPrefixes("/app"); } }
5.2 Controller ส่งข้อความ
@Controller @RequiredArgsConstructor public class WsController { private final SimpMessagingTemplate template; @MessageMapping("/chat") public void onChat(String body) { template.convertAndSend("/topic/messages", body); } }
6. โค้ด Frontend รับข้อความ
6.1 Client SSE
const evtSource = new EventSource("/api/stream"); evtSource.onmessage = (e) => { const li = document.createElement("li"); li.textContent = "📩 " + e.data; document.querySelector("#messages").append(li); };
6.2 Client WebSocket ผ่าน STOMP
const socket = new SockJS("/ws"); const stomp = Stomp.over(socket); stomp.connect({}, () => { stomp.subscribe("/topic/messages", (msg) => { const li = document.createElement("li"); li.textContent = "💬 " + msg.body; document.querySelector("#messages").append(li); }); // ส่งข้อความ document.querySelector("#send").onclick = () => { const txt = document.querySelector("#txt").value; stomp.send("/app/chat", {}, txt); }; });
7. การ Deploy & Scaling
- เปิด
server.tomcat.max-threads
สูงไว้สำหรับ WebSocket blocking-IO - SSE สามารถใช้ Netty & Reactive Pool ขนาดเล็กรับได้หมื่น connection
- ถ้า Kubernetes ➜ ใช้ NGINX Ingress v1.9+ เปิด
use-http2
- เพิ่ม
proxy_read_timeout
ใหญ่กว่า idle timeout ของ Browser
8. เปรียบเทียบ SSE กับ WebSocket

คุณสมบัติ | SSE | WebSocket |
---|---|---|
ทิศทางข้อมูล | Server ➜ Client | Full-Duplex |
โปรโตคอล | HTTP text/event-stream | ws/wss Binary or Text |
Browser Support | Chrome, Edge, Firefox, Safari | เหมือนกัน |
ฟีเจอร์ Reconnect | ในตัว | ต้องเขียนเอง |
ใช้ Proxy/LB ทั่วไป | ✓ | อาจต้อง sticky session |
เหมาะกับ | Event feed, IoT metrics | Chat, Game, Collab |
9. Security & Best Practices
- ส่ง JWT ใน
Authorization
Header สำหรับ SSE / WebSocket Handshake - ใส่ CSRF-token ซ่อนใน query param เพื่อป้องกัน WS Hijack
- จำกัดขนาดข้อความ (
maxFrameSize
) เพื่อกัน big payload attack - แยก Topic ตาม user-id เพื่อกันข้อมูลรั่ว
10. Monitoring & Benchmark
Spring Boot Actuator มี metric เช่น:
reactor.netty.connection.active
จำนวน Connection SSEtomcat.sessions.active
สำหรับ WS in-memory
ทดสอบด้วย wrk
ส่ง 50 kpps SSE พบ CPU ใช้เพียง 25 %
11. FAQ
Q: Safari บน iOS ไม่รับ SSE?
A: ใช้ Fetch + ReadableStream Polyfill หรือ Upgrade เป็น iOS 13+
Q: WebSocket บน AWS ALB ตั้งอย่างไร?
A: เปิด idle-timeout = 4000 s และใช้ protocol: HTTP2
12. สรุป
ทั้ง SSE และ WebSocket ทำให้แอปของคุณ พูดคุย กับผู้ใช้แบบเรียลไทม์ เลือกให้เหมาะ: ถ้าเพียง “ส่งแจ้งเตือน” ใช้ SSE ง่าย เบา เสถียร, ถ้า “โต้ตอบสองทาง” เช่น Chat หรือ Game ให้ไป WebSocket อย่าลืมวัดผล & ปรับแต่ง timeout , buffer และ security ให้รอบด้าน!
โค้ดตัวอย่างเต็ม ดาวน์โหลดได้ที่ GitHub: realtime-demo
© 2025 poolsawat.com • ขอบคุณที่ติดตาม หากบทความนี้เป็นประโยชน์ฝาก Share ต่อให้เพื่อน 👍