ปัญหา: การเชื่อมต่อระหว่างไคลเอ็นต์กับเซิร์ฟเวอร์และเซิร์ฟเวอร์กับไคลเอ็นต์ที่มีเวลาในการตอบสนองต่ำ
เว็บส่วนใหญ่สร้างขึ้นโดยใช้กระบวนทัศน์คำขอ/การตอบกลับของ HTTP ไคลเอ็นต์โหลดหน้าเว็บขึ้นมา จากนั้นจะไม่มีอะไรเกิดขึ้นจนกว่าผู้ใช้จะคลิกไปยังหน้าถัดไป ประมาณปี 2005 AJAX เริ่มทำให้เว็บดูมีไดนามิกมากขึ้น อย่างไรก็ตาม การสื่อสาร HTTP ทั้งหมดได้รับการควบคุมโดยไคลเอ็นต์ ซึ่งต้องมีการโต้ตอบของผู้ใช้หรือการสำรวจเป็นระยะเพื่อโหลดข้อมูลใหม่จากเซิร์ฟเวอร์
เทคโนโลยีที่ช่วยให้เซิร์ฟเวอร์ส่งข้อมูลไปยังไคลเอ็นต์ได้ทันทีที่ทราบว่ามีข้อมูลใหม่นั้นมีมานานแล้ว โดยมีชื่อเรียกต่างๆ เช่น "Push" หรือ "Comet" หนึ่งในแฮ็กที่พบบ่อยที่สุดในการสร้างภาพลวงตาของการเชื่อมต่อที่เซิร์ฟเวอร์เริ่มต้นเรียกว่า Long Polling เมื่อใช้ Long Polling ไคลเอ็นต์จะเปิดการเชื่อมต่อ HTTP กับเซิร์ฟเวอร์ซึ่งจะเปิดไว้จนกว่าจะส่งการตอบกลับ เมื่อใดก็ตามที่เซิร์ฟเวอร์มีข้อมูลใหม่ เซิร์ฟเวอร์จะส่งการตอบกลับ (เทคนิคอื่นๆ เกี่ยวข้องกับ Flash, คำขอ XHR แบบหลายส่วน และสิ่งที่เรียกว่า htmlfiles) Long Polling และเทคนิคอื่นๆ ทำงานได้ค่อนข้างดี คุณใช้แอปพลิเคชันเหล่านี้ทุกวัน เช่น แชทใน Gmail
อย่างไรก็ตาม วิธีแก้ปัญหาทั้งหมดนี้มีปัญหาเดียวกันคือ มีค่าใช้จ่ายเพิ่มเติมของ HTTP ซึ่งทำให้ไม่เหมาะกับแอปพลิเคชันที่มีเวลาในการตอบสนองต่ำ ลองนึกถึงเกมยิงมุมมองบุคคลที่หนึ่งแบบผู้เล่นหลายคนในเบราว์เซอร์หรือเกมออนไลน์อื่นๆ ที่มีคอมโพเนนต์แบบเรียลไทม์
ขอแนะนำ WebSocket: การนำซ็อกเก็ตมาสู่เว็บ
ข้อกำหนดเฉพาะของ WebSocket จะกำหนด API ที่สร้างการเชื่อมต่อ "ซ็อกเก็ต" ระหว่างเว็บเบราว์เซอร์กับเซิร์ฟเวอร์ พูดง่ายๆ ก็คือ มีการเชื่อมต่อแบบถาวรระหว่างไคลเอ็นต์กับเซิร์ฟเวอร์ และทั้ง 2 ฝ่ายสามารถเริ่มส่งข้อมูลได้ทุกเมื่อ
เริ่มต้นใช้งาน
คุณเปิดการเชื่อมต่อ WebSocket ได้ง่ายๆ โดยเรียกใช้คอนสตรัคเตอร์ WebSocket ดังนี้
var connection = new WebSocket('ws://html5rocks.websocket.org/echo', ['soap', 'xmpp']);
สังเกตws: นี่คือสคีมา URL ใหม่สำหรับการเชื่อมต่อ WebSocket นอกจากนี้ยังมี wss: สำหรับการเชื่อมต่อ WebSocket ที่ปลอดภัยในลักษณะเดียวกับที่ใช้ https: สำหรับการเชื่อมต่อ HTTP ที่ปลอดภัย
การแนบเครื่องจัดการเหตุการณ์บางอย่างเข้ากับการเชื่อมต่อทันทีจะช่วยให้คุณทราบเมื่อมีการเปิดการเชื่อมต่อ ได้รับข้อความขาเข้า หรือมีข้อผิดพลาด
อาร์กิวเมนต์ที่ 2 ยอมรับโปรโตคอลย่อยที่ไม่บังคับ โดยอาจเป็นสตริงหรืออาร์เรย์ของสตริงก็ได้ สตริงแต่ละรายการควรแสดงชื่อโปรโตคอลย่อย และเซิร์ฟเวอร์จะยอมรับโปรโตคอลย่อยที่ส่งมาเพียงรายการเดียวในอาร์เรย์ คุณสามารถระบุโปรโตคอลย่อยที่ยอมรับได้โดยการเข้าถึงพร็อพเพอร์ตี้ protocol ของออบเจ็กต์ WebSocket
ชื่อโปรโตคอลย่อยต้องเป็นชื่อโปรโตคอลย่อยที่ลงทะเบียนไว้ในรีจิสทรี IANA ปัจจุบันมีชื่อโปรโตคอลย่อย (soap) เพียงชื่อเดียวที่ลงทะเบียนไว้ ณ เดือนกุมภาพันธ์ 2012
// When the connection is open, send some data to the server
connection.onopen = function () {
connection.send('Ping'); // Send the message 'Ping' to the server
};
// Log errors
connection.onerror = function (error) {
console.log('WebSocket Error ' + error);
};
// Log messages from the server
connection.onmessage = function (e) {
console.log('Server: ' + e.data);
};
กำลังสื่อสารกับเซิร์ฟเวอร์
ทันทีที่เราเชื่อมต่อกับเซิร์ฟเวอร์ (เมื่อเหตุการณ์ open เริ่มทำงาน) เราจะเริ่มส่งข้อมูลไปยังเซิร์ฟเวอร์ได้โดยใช้วิธี send('your message') ในออบเจ็กต์การเชื่อมต่อ โดยก่อนหน้านี้รองรับเฉพาะสตริง แต่ในข้อกำหนดล่าสุดสามารถส่งข้อความไบนารีได้ด้วย หากต้องการส่งข้อมูลไบนารี คุณสามารถใช้ออบเจ็กต์ Blob หรือ ArrayBuffer ก็ได้
// Sending String
connection.send('your message');
// Sending canvas ImageData as ArrayBuffer
var img = canvas_context.getImageData(0, 0, 400, 320);
var binary = new Uint8Array(img.data.length);
for (var i = 0; i < img.data.length; i++) {
binary[i] = img.data[i];
}
connection.send(binary.buffer);
// Sending file as Blob
var file = document.querySelector('input[type="file"]').files[0];
connection.send(file);
ในทำนองเดียวกัน เซิร์ฟเวอร์อาจส่งข้อความถึงเราได้ทุกเมื่อ ทุกครั้งที่เกิดเหตุการณ์นี้ขึ้น ระบบจะเรียกใช้ Callback onmessage Callback จะได้รับออบเจ็กต์เหตุการณ์ และข้อความจริงจะเข้าถึงได้ผ่านพร็อพเพอร์ตี้ data
WebSocket ยังรับข้อความไบนารีในข้อกำหนดล่าสุดได้ด้วย โดยสามารถรับเฟรมไบนารีในรูปแบบ Blob หรือ ArrayBuffer หากต้องการระบุรูปแบบของไบนารีที่ได้รับ ให้ตั้งค่าพร็อพเพอร์ตี้ binaryType ของออบเจ็กต์ WebSocket เป็น "blob" หรือ "arraybuffer" รูปแบบเริ่มต้นคือ "blob" (คุณไม่จำเป็นต้องจัดแนวพารามิเตอร์ binaryType ในการส่ง)
// Setting binaryType to accept received binary as either 'blob' or 'arraybuffer'
connection.binaryType = 'arraybuffer';
connection.onmessage = function(e) {
console.log(e.data.byteLength); // ArrayBuffer object if binary
};
ฟีเจอร์ที่เพิ่มเข้ามาใหม่ของ WebSocket คือส่วนขยาย การใช้ส่วนขยายจะช่วยให้ส่งเฟรมที่บีบอัด มัลติเพล็กซ์ ฯลฯ ได้ คุณสามารถค้นหาส่วนขยายที่เซิร์ฟเวอร์ยอมรับได้โดยตรวจสอบพร็อพเพอร์ตี้ส่วนขยายของออบเจ็กต์ WebSocket หลังจากเหตุการณ์เปิด ยังไม่มีข้อกำหนดของส่วนขยายที่เผยแพร่อย่างเป็นทางการ ณ เดือนกุมภาพันธ์ 2012
// Determining accepted extensions
console.log(connection.extensions);
การสื่อสารแบบข้ามต้นทาง
เนื่องจากเป็นโปรโตคอลสมัยใหม่ การสื่อสารข้ามต้นทางจึงรวมอยู่ใน WebSocket โดยตรง แม้ว่าคุณควรตรวจสอบให้แน่ใจว่าได้สื่อสารกับไคลเอ็นต์และเซิร์ฟเวอร์ที่คุณเชื่อถือเท่านั้น แต่ WebSocket ก็ช่วยให้การสื่อสารระหว่างฝ่ายต่างๆ ในโดเมนใดก็ได้เป็นไปได้ เซิร์ฟเวอร์จะตัดสินใจว่าจะให้บริการแก่ไคลเอ็นต์ทั้งหมดหรือเฉพาะไคลเอ็นต์ที่อยู่ในชุดโดเมนที่กำหนดไว้อย่างชัดเจน
พร็อกซีเซิร์ฟเวอร์
เทคโนโลยีใหม่ทุกอย่างมาพร้อมกับปัญหาชุดใหม่ ในกรณีของ WebSocket จะเป็นความเข้ากันได้กับพร็อกซีเซิร์ฟเวอร์ซึ่งเป็นสื่อกลางในการเชื่อมต่อ HTTP ในเครือข่ายบริษัทส่วนใหญ่ โปรโตคอล WebSocket ใช้ระบบอัปเกรด HTTP (ซึ่งปกติใช้สำหรับ HTTP/SSL) เพื่อ "อัปเกรด" การเชื่อมต่อ HTTP เป็นการเชื่อมต่อ WebSocket พร็อกซีเซิร์ฟเวอร์บางรายการไม่รองรับการดำเนินการนี้และจะยกเลิกการเชื่อมต่อ ดังนั้น แม้ว่าไคลเอ็นต์หนึ่งๆ จะใช้โปรโตคอล WebSocket ก็อาจสร้างการเชื่อมต่อไม่ได้ ซึ่งทำให้ส่วนถัดไปมีความสำคัญมากยิ่งขึ้น :)
ใช้ WebSockets เลย
WebSocket ยังคงเป็นเทคโนโลยีใหม่และยังไม่ได้ใช้งานอย่างเต็มรูปแบบในเบราว์เซอร์ทั้งหมด อย่างไรก็ตาม คุณสามารถใช้ WebSocket ได้ในปัจจุบันด้วยไลบรารีที่ใช้ทางเลือกใดทางเลือกหนึ่งที่กล่าวถึงข้างต้นเมื่อใดก็ตามที่ WebSocket ไม่พร้อมใช้งาน ไลบรารีที่ได้รับความนิยมอย่างมากในโดเมนนี้คือ socket.io ซึ่งมาพร้อมกับการใช้งานไคลเอ็นต์และเซิร์ฟเวอร์ของโปรโตคอล และรวมถึงการสำรอง (socket.io ยังไม่รองรับการรับส่งข้อความแบบไบนารี ณ เดือนกุมภาพันธ์ 2012) นอกจากนี้ยังมีโซลูชันเชิงพาณิชย์ เช่น PusherApp ซึ่งผสานรวมเข้ากับสภาพแวดล้อมเว็บใดก็ได้โดยง่ายด้วยการจัดเตรียม HTTP API เพื่อส่งข้อความ WebSocket ไปยังไคลเอ็นต์ เนื่องจากคำขอ HTTP เพิ่มเติมจะมีค่าใช้จ่ายเพิ่มเติมเสมอเมื่อเทียบกับ WebSocket แบบเพียวๆ
ฝั่งเซิร์ฟเวอร์
การใช้ WebSocket จะสร้างรูปแบบการใช้งานใหม่ทั้งหมดสำหรับแอปพลิเคชันฝั่งเซิร์ฟเวอร์ แม้ว่าสแต็กเซิร์ฟเวอร์แบบดั้งเดิม เช่น LAMP จะได้รับการออกแบบมาให้ทำงานตามวงจรคำขอ/การตอบกลับ HTTP แต่ก็มักจะจัดการกับการเชื่อมต่อ WebSocket ที่เปิดอยู่จำนวนมากได้ไม่ดีนัก การเปิดการเชื่อมต่อจำนวนมากพร้อมกันต้องใช้สถาปัตยกรรมที่รองรับการทำงานพร้อมกันสูงโดยใช้ต้นทุนด้านประสิทธิภาพต่ำ โดยปกติแล้ว สถาปัตยกรรมดังกล่าวจะออกแบบมาเพื่อการทำเธรดหรือที่เรียกว่า Non-blocking IO
การติดตั้งใช้งานฝั่งเซิร์ฟเวอร์
- Node.js
- Java
- Ruby
- Python
- Erlang
- C++
- .NET
เวอร์ชันของโปรโตคอล
โปรโตคอลแบบใช้สาย (แฮนด์เชคและการโอนข้อมูลระหว่างไคลเอ็นต์และเซิร์ฟเวอร์) สำหรับ WebSocket คือ RFC6455 Chrome และ Chrome สำหรับ Android เวอร์ชันล่าสุดเข้ากันได้กับ RFC6455 อย่างสมบูรณ์ รวมถึงการรับส่งข้อความแบบไบนารี นอกจากนี้ Firefox จะเข้ากันได้กับเวอร์ชัน 11 และ Internet Explorer จะเข้ากันได้กับเวอร์ชัน 10 คุณยังคงใช้โปรโตคอลเวอร์ชันเก่าได้ แต่เราไม่แนะนำเนื่องจากเป็นที่ทราบกันดีว่ามีความเสี่ยง หากคุณมีการติดตั้งใช้งานเซิร์ฟเวอร์สำหรับโปรโตคอล WebSocket เวอร์ชันเก่า เราขอแนะนำให้อัปเกรดเป็นเวอร์ชันล่าสุด
กรณีการใช้งาน
ใช้ WebSocket เมื่อใดก็ตามที่คุณต้องการการเชื่อมต่อที่มีเวลาในการตอบสนองต่ำอย่างแท้จริงและเกือบเรียลไทม์ระหว่างไคลเอ็นต์และเซิร์ฟเวอร์ โปรดทราบว่าการดำเนินการนี้อาจเกี่ยวข้องกับการคิดใหม่เกี่ยวกับวิธีสร้างแอปพลิเคชันฝั่งเซิร์ฟเวอร์โดยมุ่งเน้นที่เทคโนโลยีใหม่ๆ เช่น คิวเหตุการณ์ ตัวอย่างกรณีการใช้งานมีดังนี้
- เกมออนไลน์แบบผู้เล่นหลายคน
- แอปพลิเคชัน Chat
- แถบแสดงข้อมูลการถ่ายทอดสดกีฬา
- สตรีมโซเชียลที่อัปเดตแบบเรียลไทม์
การสาธิต
- Plink
- Paint With Me
- Pixelatr
- เส้นประ
- เกมปริศนาอักษรไขว้แบบออนไลน์ที่มีผู้เล่นจำนวนมาก
- เซิร์ฟเวอร์ Ping (ใช้ในตัวอย่างด้านบน)
- ตัวอย่าง HTML5demos