1. ข้อตกลง
WebSocket เป็นโปรโตคอลที่ใช้การสื่อสารแบบดูเพล็กซ์อย่างเต็มรูปแบบระหว่างลูกค้าและเซิร์ฟเวอร์บน TCP มันถูกกำหนดไว้ใน HTML5 และยังเป็นหนึ่งในข้อกำหนดพื้นฐานของ WebApps รุ่นใหม่
มันผ่านข้อ จำกัด ของ AJAX ก่อนหน้านี้คีย์เป็นแบบเรียลไทม์และเซิร์ฟเวอร์สามารถส่งเนื้อหาไปยังไคลเอนต์ได้อย่างแข็งขัน! แอปพลิเคชันที่เป็นไปได้รวมถึง: เกมออนไลน์ผู้เล่นหลายคน, แชทสด, การตรวจสอบแบบเรียลไทม์, เดสก์ท็อประยะไกล, เซิร์ฟเวอร์ข่าว ฯลฯ
สำหรับตัวฉันเองสิ่งที่ฉันต้องการลองมากที่สุดในขณะนี้คือสิ่งที่สามารถทำได้ด้วยการรวมกันของ Canvas+WebSocket
2. การรับรู้
เนื่องจากกระบวนการจับมือกันเป็นคำขอ HTTP มาตรฐานมีสองตัวเลือกสำหรับการใช้งาน WebSocket: 1) การใช้งานบน TCP; 2) การใช้งานบนซอฟต์แวร์ HTTP ที่มีอยู่ ข้อได้เปรียบหลังคือสามารถแชร์พอร์ตเซิร์ฟเวอร์ HTTP ที่มีอยู่และไม่จำเป็นต้องดำเนินการตรวจสอบฟังก์ชันการตรวจสอบและแยกวิเคราะห์คำขอ HTTP อีกครั้ง
โมดูล HTTP ของโหนดใช้ในตัวอย่างนี้ (ดูไฟล์แนบสำหรับเวอร์ชัน TCP และไฟล์ทั้งหมด)
1. รหัสฝั่งเซิร์ฟเวอร์:
var http = require ('http'); var url = ต้องการ ('url'); // var mime = ต้องการ ('mime'); var crypto = ต้องการ ('crypto'); var port = 4400; var server = http.createserver (); server.listen (พอร์ต, ฟังก์ชัน () {console.log ('เซิร์ฟเวอร์ทำงานบน localhost:', พอร์ต); เซิร์ฟเวอร์. on ('การเชื่อมต่อ', ฟังก์ชัน (s) {console.log ('on Connection', s);}) Object.keys (req), req.url, req ['อัพเกรด']); if (! req.upgrade) {// การเลือกคำขอที่ไม่ใช่การอัพเกรด: ขัดจังหวะหรือให้หน้าเว็บปกติ res.writehead (200, {'content-type': 'text/plain'}); Res.Write ('WebSocket Server ใช้งานได้!'); } res.end (); return;}; var onUpgrade = function (req, sock, head) {// console.log ('วิธี:', object.keys (ถุงเท้า)); if (req.headers.upgrade! == 'websocket') {console.warn ('การเชื่อมต่อที่ผิดกฎหมาย'); Sock.end (); กลับ; } bind_sock_event (ถุงเท้า); ลอง {handshake (req, sock, head); } catch (e) {console.error (e); Sock.end (); }}; // ห่อเฟรมที่จะส่ง var wrap = function (data) {var fa = 0x00, fe = 0xff, data = data.toString () len = 2+buffer.byTelength (data), buff = buffer ใหม่ (len); บัฟ [0] = fa; buff.write (ข้อมูล, 1); บัฟ [len-1] = fe; return buff;} // undrap เฟรมที่ได้รับ var unwrap = function (data) {return data.slice (1, data.length-1);} var bind_sock_event = ฟังก์ชั่น (ถุงเท้า) {sock .on ('data', ฟังก์ชั่น (บัฟเฟอร์) ส่ง ('hello html5,'+date.now ()) sock.emit ('ส่ง', ข้อมูล); sock.write (wrap (data), 'binary');})}; var get_part = ฟังก์ชั่น (คีย์) {var empty = '', spaces = key.replace (// s/g, ว่าง) if (! spaces) โยน {ข้อความ: 'คีย์ผิด:'+คีย์, ชื่อ: 'handshakeError'} return get_big_endian (ส่วน / ช่องว่าง);} var get_big_endian = function (n) {return string.fromcharcode.apply (null, [3,2,1,0] ฟังก์ชั่น (key1, key2, head) {var sum = get_part (key1) + get_part (key2) + head.toString ('ไบนารี'); ส่งคืน crypto.createhash ('md5'). อัปเดต (ผลรวม) .digest ('ไบนารี');} var handshake = function (req, sock, head) {var output = [], h = req.headers, br = '/r/n'; // ส่วนหัวเอาท์พุท Push ('HTTP/1.1 101 WebSocket Handshake', 'Upgrade: WebSocket', 'การเชื่อมต่อ: อัพเกรด', 'Sec-Websocket-Origin:' + H.origin, 'Sec-Websocket-Location: WS: //' my-custom-chat-protocol '+br); // body var c = ความท้าทาย (h ['sec-websocket-key1'], h ['sec-websocket-key2'], head); เอาท์พุท. พัช (c); sock.write (output.join (br), 'binary');}2. รหัสไคลเอนต์เบราว์เซอร์:
<Html> <head> <title> การสาธิต WebSocket </title> </head> <style type = "text/css"> textarea {width: 400px; ความสูง: 150px; display: block; overflow-y: scroll;} #OUTPUT {ความกว้าง: 600PX; .5em; สี:#000; เส้นขอบ: ไม่มี;} ปุ่ม {padding: .2em 1em;} </style> <link href = "layout.css" rel = "stylesheet" type = "text/css"/> <body> id = "ส่ง"> ส่ง </button> <script type = "text/javascript"> // localhostvar socket = ใหม่ websocket ('ws: //192.168.144.131: 4400/') socket.onopen = function (e) {log (e.type); socket.send ('hello node');} socket.onclose = function (e) {log (e.type);} socket.onmessage = function (e) {log ('รับ @'+วันที่ใหม่ (). tolocaletimestring ()+'/n'+e.data); output.scrolltop = output.scrollheight} socket.onclose = function (e) {log (e.type);} socket.addeventListener ('close', function () {log ('ตัวจัดการเหตุการณ์ปิดอีก id ('output'), input = id ('input'), send = id ('send'); var log = function (msg) {output.TextContent += '>' +msg +'/n'} send.addeventListener ('คลิก', ฟังก์ชัน () {socket.send (input.value);3. รายละเอียด
การใช้งานโปรโตคอล WebSocket เหนือโปรโตคอล HTTP มีเพียงสองขั้นตอน: การจับมือและส่งข้อมูล
1. จับมือกัน
กระบวนการจับมือกันเรียกว่าการตอบสนองการท้าทาย ขั้นแรกไคลเอนต์เริ่มต้นคำขอรับ HTTP ชื่ออัพเกรดเซิร์ฟเวอร์ตรวจสอบคำขอให้ตอบกลับ 101 เพื่อระบุว่าการอัพเกรดโปรโตคอลได้รับการยอมรับและการจับมือเสร็จสมบูรณ์
ข้อมูลการจับมือที่สวยงามโดยผู้ตรวจสอบ Chrome:
ขอ URL: WS: //192.168.144.131: 4400/PUB/แชท? Q = ฉัน
วิธีการขอ: รับ
รหัสสถานะ: 101 WebSocket Handshake
ขอส่วนหัว
การเชื่อมต่อ: อัพเกรด
โฮสต์: 192.168.144.131: 4400
Origin: http: // localhost: 800
Sec-Websocket-Key1: P2 G 947T 80 661 JAF2
sec-websocket-key2: z zq ^326 5 9 = 7S1 1 7H4
sec-websocket-protocol :: my-custom-chat-protocol
อัพเกรด: WebSocket
(key3): 7c: 44: 56: ca: 1f: 19: d2: 0a
ส่วนหัวการตอบสนอง
การเชื่อมต่อ: อัพเกรด
sec-websocket-location: ws: //192.168.144.131: 4400/pub/แชท? q = ฉัน
Sec-Websocket-Origin: http: // localhost: 800
Sec-Websocket-Protocol: My-Custom-Chat-Chat-Protocol
อัพเกรด: WebSocket
(การตอบกลับท้าทาย): 52: DF: 2C: F4: 50: C2: 8E: 98: 14: B7: 7d: 09: CF: C8: 33: 40
ขอส่วนหัว
โฮสต์: โฮสต์ WebSocket Server
การเชื่อมต่อ: ประเภทการเชื่อมต่อ
อัพเกรด: ประเภทการอัพเกรดโปรโตคอล
Origin: เยี่ยมชมแหล่งที่มา
Sec-Websocket-Protocol: ตัวเลือกชื่อย่อยชื่อย่อยที่กำหนดโดยแอปพลิเคชันเองและโปรโตคอลหลายรายการจะถูกหารด้วยช่องว่าง (*อีกหนึ่งตัวเลือกที่เหลือคือคุกกี้)
Sec-Websocket-KEY1: คีย์การตรวจสอบความปลอดภัยคำขอ XHR ไม่สามารถปลอมแปลงส่วนหัวคำขอเริ่มต้นด้วย 'Sec-'
sec-websocket-key2: เหมือนกับข้างต้น
Key3: การตอบสนองเนื้อหาของร่างกาย, การสุ่ม 8 ไบต์
ส่วนหัวการตอบสนอง
Sec-Websocket-Protocol: ต้องมีชื่อ subprotocol ที่ร้องขอ
Sec-Websocket-Origin: ต้องเท่ากับแหล่งที่มาของคำขอ
sec-websocket-location: ต้องเท่ากับที่อยู่ที่ร้องขอ
การตอบสนองต่อความท้าทาย: เนื้อหาของร่างกายที่ตอบสนองซึ่งคำนวณตามสามปุ่มในคำขอ 16 ไบต์
กระบวนการคำนวณสตริงการคำนวณ pseudocode:
part_1 = ตัวเลขทั้งหมดในคีย์ 1 / จำนวนช่องว่างใน key1 part_2 เหมือนกับข้างต้น sum = big_endian (part_1)+big_endian (part_2)+key3challenge_response = md5_digest (ผลรวม);
กลยุทธ์การคำนวณ Big_endian สำหรับจำนวนเต็ม 32 บิต:
# มันคล้ายกับการคำนวณสี RGBA มาก จากฟังก์ชั่นต่อไปนี้เราจะเห็นได้ว่ากระบวนการคำนวณ var big_endian = function (n) {return [3,2,1,0] .map (ฟังก์ชั่น (i) {return n >> 8*i & 0xff});} big_endian (0xcc77aaff); // -> [204, 119, 170,2. ส่งข้อมูล
WebSocket API ได้รับการออกแบบมาเพื่อประมวลผลข้อมูลโดยใช้กิจกรรม ลูกค้าสามารถรับข้อมูลที่สมบูรณ์ได้ตราบใดที่พวกเขาได้รับการแจ้งเตือนเหตุการณ์โดยไม่ต้องประมวลผลบัฟเฟอร์ด้วยตนเอง
ในกรณีนี้แต่ละข้อมูลเรียกว่าเฟรม ในข้อกำหนดข้อกำหนดหัวของมันจะต้องเริ่มต้นด้วย 0x00 และแอตทริบิวต์หางจะลงท้ายด้วย 0xff เพื่อให้การส่งข้อมูลแต่ละครั้งมีอย่างน้อยสองไบต์
ในการใช้งานเซิร์ฟเวอร์หัวและหางจะต้องถูกตัดออกเมื่อได้รับข้อมูล ในขณะที่ศีรษะและหางต้องบรรจุเมื่อส่งข้อมูล รูปแบบมีดังนี้:
# 'hello' การเป็นตัวแทนไบนารีดั้งเดิมส่วนหัวคำขอและนี่คือการเข้ารหัส UTF8
<Buffer E4 BD A0 E5 A5 BD>
# การเป็นตัวแทนไบนารีที่ห่อหุ้ม
<buffer 00 e4 bd a0 e5 a5 bd ff>
ข้างต้นเป็นเรื่องเกี่ยวกับบทความนี้ฉันหวังว่ามันจะเป็นประโยชน์กับการเรียนรู้ของทุกคน