ข้ามไปยังเนื้อหา

Sending and Receiving

เมื่อการเชื่อมต่อเป็น OPEN แล้ว งานประจำวันมีแค่สองอย่าง: ผลักข้อความออกไปด้วย send() และตอบสนองต่อข้อความที่เข้ามาผ่าน message event บทเรียนนี้ตอกย้ำทั้งสองทิศทาง บวกกับสองกฎที่ทำให้คนสะดุดมากที่สุด — ส่งได้ เมื่อไหร่ และสิ่งต่าง ๆ มาถึง ตามลำดับใด

ในการส่ง คุณเรียก send() พร้อม payload ของคุณ:

ws.send('hello, server');
ws.send(JSON.stringify({ type: 'chat', text: 'hi' }));

send() รับ string หรือ binary data (เรื่อง binary อยู่ในบทถัดไป) ไม่ คืนค่าอะไรและไม่มีการตอบรับ — แค่ยื่น message ให้การเชื่อมต่อเท่านั้น รูปแบบที่พบบ่อยคือส่งข้อมูลที่มีโครงสร้างโดยแปลง object เป็น JSON แล้ว parse ที่อีกฝั่ง

ข้อความที่เข้ามาจะมาถึงในรูปของ message event และ payload จะอยู่บน event.data เสมอ:

ws.onmessage = (event) => {
const data = event.data; // a string (or binary, next lesson)
const msg = JSON.parse(data); // if the peer sent JSON
console.log(msg.type, msg.text);
};

event.data คือ ที่เดียว ที่ payload อยู่ ถ้าส่ง JSON มาก็ parse ตรงนี้ ถ้าส่ง text ธรรมดามาก็ใช้ได้เลย handler ทำงานหนึ่งครั้งต่อหนึ่ง message และยิงบ่อยเท่าที่ server มีเรื่องจะพูด

นี่คือกฎเดียวที่ควรสักไว้ในความทรงจำ: send() ใช้ได้เฉพาะขณะที่ readyState เป็น OPEN เท่านั้น

  • เรียก send() ขณะยังเป็น CONNECTING จะ throw InvalidStateError — การเชื่อมต่อยังไม่พร้อม
  • เรียก send() หลัง CLOSING หรือ CLOSED ข้อมูลจะถูกทิ้งเงียบ ๆ ไม่มีวันถึง peer

ดังนั้นรูปแบบที่ปลอดภัยคือส่งจากภายใน onopen หรือใส่ guard ให้ทุกการเรียก:

function safeSend(ws: WebSocket, data: string) {
if (ws.readyState === WebSocket.OPEN) {
ws.send(data);
} else {
console.warn('not open — dropping or queuing message');
}
}

WebSocket ทำงานบนการเชื่อมต่อ TCP เดียว และนั่นมอบการรับประกันที่หนักแน่นและเรียบง่ายให้คุณ: ข้อความถูกส่งถึงตามลำดับที่คุณส่งออกไป ถ้าคุณเรียก send('a') แล้ว send('b') แล้ว send('c') peer จะได้รับ a, b, c — ไม่มีวันสลับลำดับ ไม่มีวันซ้ำ เช่นเดียวกันกับข้อความที่ server ส่งมาหาคุณ

sequenceDiagram
  participant App as Your code
  participant WS as WebSocket
  participant Srv as Server
  Note over WS: readyState = OPEN
  App->>WS: send("a")
  App->>WS: send("b")
  WS->>Srv: a
  WS->>Srv: b
  Srv-->>WS: reply to a
  WS-->>App: message event (data = reply to a)
  Srv-->>WS: reply to b
  WS-->>App: message event (data = reply to b)
การส่งและ message event ที่ตามมา เรียงตามลำดับ

สิ่งที่ลำดับ ไม่ รับประกันคือการจับคู่ request/response WebSocket ไม่มีแนวคิดในตัวว่า “การตอบกลับนี้เป็นของการส่งนั้น” — ถ้าคุณต้องการแบบนั้น คุณต้องเพิ่ม correlation id ของคุณเองไว้ในข้อความ ลำดับ ใช่; การจับคู่ นั่นเป็นหน้าที่ของคุณ

เดโมใช้ echo socket ในหน้าเว็บที่มี WebSocket API จริง จึงรันได้ทุกที่โดยไม่ต้องมี server เดโมส่งสาม message ติดกันจากใน onopen แล้วพิมพ์ echo แต่ละตัวตอนกลับมา — เรียงตามลำดับ ในแอปจริงมีแค่บรรทัดแรกที่เปลี่ยนเป็น new WebSocket('wss://your-server')

JavaScript

สังเกตว่าความพยายามแรก — too early — ถูกบล็อกเพราะ socket ยังเป็น CONNECTING อยู่ จากนั้นพอเปิดแล้ว สาม message ที่มีหมายเลขก็กลับมาเป็น 1, 2, 3 ตรงตามลำดับที่ส่งออกไปเป๊ะ นั่นคือ send(), message event, กฎ OPEN-only และลำดับ ทั้งหมดในการรันครั้งเดียว

Data Typeส่งด้วยรับเป็นเหมาะกับ
Stringws.send("text")event.data เป็น stringJSON message, text protocol
ArrayBufferws.send(buffer)event.data เป็น ArrayBufferbinary data, fixed structure
Blobws.send(blob)event.data เป็น Blobfile, image, audio

Parse JSON ทุก Message โดยไม่มี Error Handling อาการ:

  • JSON.parse(event.data) โดยไม่ try/catch
  • server ส่ง malformed JSON → app crash
  • wrap ใน try/catch: try { const msg = JSON.parse(event.data); } catch (e) { console.error('Invalid message', e) }

ส่ง Message ขนาดใหญ่ใน Loop โดยไม่ Check bufferedAmount อาการ:

  • loop ส่ง frame ติดกัน 1000 ครั้ง — buffer ล้น
  • ws.bufferedAmount สูงมาก — memory spike
  • check ws.bufferedAmount < threshold ก่อนส่ง message ถัดไป

💡 ตัวอย่างจากของจริง

Slack:

  • รับ message เป็น JSON: { "type": "message", "text": "Hello", "channel": "C1234" }
  • dispatch ตาม type field — event-driven message handling

Google Docs:

  • ส่ง operation เป็น binary (OT operations) — compact และเร็ว
  • รับ update จาก server และ apply ต่อ document state local
payload ของข้อความที่เข้ามาอยู่ที่ไหน?
เกิดอะไรขึ้นถ้าคุณเรียก send() ขณะที่ socket ยังเป็น CONNECTING?
WebSocket ให้การรับประกันแบบใดสำหรับข้อความ?