const http = require('http');
const fs = require('fs');
const app = http.createServer((req, res) => {
// 設(shè)置響應(yīng)頭
res.setHeader('Content-type', 'application/json; charset=utf-8');
res.setHeader('Cache-Control', 'max-age=0'); // 沒有緩存
let num = 0;
// 地柜返回
const send = () => {
if (num > 20) {
res.end();
return;
}
num++;
const data = Math.random() + '';
res.write(data, 'utf8');
setTimeout(send, 1000);
}
send();
});
app.listen(8081, () => {
console.log('127.0.0.1:8081');
})
前端監(jiān)聽的事件,每當(dāng)服務(wù)器返回一段數(shù)據(jù)都會(huì)觸發(fā)一次事件,可以從中得到當(dāng)前獲取到的全部數(shù)據(jù)。
var xhr = new XMLHttpRequest();
xhr.open('GET', '/api');
xhr.timeout = 30000;
xhr.responseType = 'text';
xhr.onreadystatechange = function () {
if (this.readyState == 3) { // 分段獲取服務(wù)端返回的數(shù)據(jù)
console.log(this.responseText);
}
if (this.readyState == 4) {
if (this.status >= 200 && this.status < 300 || this.status == 304) {
// this.response
} else {
// this.statusText
}
}
}
xhr.send()
2.
具有三個(gè)優(yōu)點(diǎn),雙向通信,自動(dòng)跨域,性能高。最主要的是可以傳輸多種格式的數(shù)據(jù)。 協(xié)議在2008年誕生,2011年成為國(guó)際標(biāo)準(zhǔn)。所有瀏覽器都已經(jīng)支持了,也是應(yīng)用很廣泛的一種即時(shí)通信協(xié)議。
是HTML5新增的API,屬于瀏覽器或者前端的內(nèi)容。后端用的是socket,socket協(xié)議的歷史相當(dāng)古老基本四十年前就已經(jīng)存在了。在H5中自帶一些安全的措施,而原生的socket就沒什么安全性可言了。
客戶端瀏覽器通過實(shí)例化,傳入服務(wù)地址創(chuàng)建鏈接,message中會(huì)接收到服務(wù)端推送的數(shù)據(jù),也可通過send方法向服務(wù)端發(fā)送數(shù)據(jù)。
const ws = new Websocket('ws://127.0.0.1:8080/api');
// 原生沒有emit,自己封裝一個(gè)
ws.emit = function(name, ...args) {
ws.send(JSON.stringify({
name,
data: [...args]
}))
}
ws.onopen = function() {
console.log('鏈接上了');
// ws.send('dadadadadasda'); // 發(fā)送數(shù)據(jù),只有一個(gè)參數(shù)一個(gè)大字符串
ws.emit('msg', 12, 5, 8);
}; // 已經(jīng)鏈接
ws.onmessage = function() {
console.log('接收到消息了')
}; // 收到數(shù)據(jù)
ws.onclose = function() {
console.log('斷開鏈接了')
}; // 斷開了
在node中想要實(shí)現(xiàn)socket可以借助node原生的net模塊,這是一個(gè)相對(duì)底層的網(wǎng)絡(luò)模塊,是一個(gè)tcp的庫(kù)。net是http的底層,很多東西都需要自己去實(shí)現(xiàn),比如這里可以使用net.來創(chuàng)建服務(wù)。
也是給予http的,先通過http請(qǐng)求到服務(wù),會(huì)攜帶一個(gè)upgrade為的請(qǐng)求頭,表示希望升級(jí)為,這個(gè)時(shí)候服務(wù)可以返回101狀態(tài)碼,表示進(jìn)行服務(wù)可以升級(jí)。
const http = require('http');
const net = require('net'); // TCP的庫(kù),可以理解為原生的Socket
const crypto = require('crypto'); // 借助加密庫(kù)實(shí)現(xiàn)一些安全性
const server = net.createServer(sock=> {
console.log('鏈接上了');
sock.on('end', () => {
console.log('客戶端斷開了')
}); // 斷開
sock.once('data', (data) => {
console.log('hand shake start...');
// 最先過來的是http頭
const str = data.toString();
// 將http頭用\r\n切開
let lines = str.split('\r\n');
// 刪除第一行和最后一行,因?yàn)闆]啥用
lines = lines.slice(1, lines.length - 2);
// 將所有請(qǐng)求頭通過'分號(hào)空格'切開
const headers = {};
lines.forEach(line => {
const [key, value ] = line.split(': ');
// 將請(qǐng)求頭變成小寫
headers[key.toLowerCase()] = val;
})
// http協(xié)議轉(zhuǎn)websocket會(huì)傳入upgrade為websocket
if (headers['upgrade'] != 'websocket') {
console.log('其他協(xié)議,暫不支持');
sock.end();
} else if (headers['sec-websocket-version'] != 13) {
console.log('不兼容不是13的版本');
sock.end();
} else {
const key = headers['sec-websocket-key'];
// 13版本的源碼是258E,可以百度的到
const mask = '258EAFA5-47DA-95CA-C5AB0DC85B11';
// 需要把key和mask加在一起,然后用sha1加密,再變成base64,還給客戶端
// sha1(key + mask) -> base64 -> client;
const hash = crypto.createHash('sha1');
hash.update(key + mask);
const tokey = hash.digest('base64');
// 數(shù)據(jù)以HTTP發(fā)回客戶端,因?yàn)轵?yàn)證的過程還是http階段, 狀態(tài)值為101(正在切換協(xié)議,協(xié)議升級(jí) Switching Protocols)
sock.write('HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: ' + tokey + '\r\n'); // Upgrade: websocket告訴瀏覽器升級(jí)為websocket,冒號(hào)要有空格
// 至此,握手已經(jīng)結(jié)束了。因?yàn)槲帐值倪^程只有一次,所以不要用on處理,用once處理
// 從這里開始,才是真正的數(shù)據(jù),以后所有的數(shù)據(jù)都走這里,所以用on處理
sock.on('data', data => {
// 獲取到的數(shù)據(jù)
// 不過數(shù)據(jù)是一個(gè)buffer的數(shù)據(jù)包,解析起來比較麻煩。
console.log(data);
})
}
}); // 有數(shù)據(jù)過來
}).listen(8080);
上面介紹的是的一個(gè)實(shí)現(xiàn)原理,項(xiàng)目中可以直接使用socket.io這個(gè)庫(kù)。
前端代碼如下:
const sock = io.connect('ws://127.0.0.1:8080/api');
sock.on('connect', () => {
console.log('已鏈接');
sock.emit('aaa', 12, 5,8);
sock.on('time', (ts) => {
console.loh(ts);
})
});
sock.on('disconnect', () => {
console.log('已斷開');
});
服務(wù)端代碼如下:
const http = require('http');
const io = require('socket.io');
// 創(chuàng)建http服務(wù),開啟8080端口號(hào)
const httpServer = http.createServer().listen(8080);
// socket監(jiān)聽http服務(wù)
const wsServer = io.listen(httpServer);
// 當(dāng)有鏈接的時(shí)候
wsServer.on('connection', sock => {
// 發(fā)送
// sock.emit
sock.emit('time', Date.now());
// 接收
sock.on('aaa', (a, b, c) => {
console.loh(a, b, c);
})
})
3. SSE
SSE全稱是Server-Sent Events,指的是網(wǎng)頁(yè)自動(dòng)獲取來自服務(wù)器的更新,也就是自動(dòng)獲取服務(wù)端推送至網(wǎng)頁(yè)的數(shù)據(jù),這是一個(gè)H5的屬性,除了IE,其它標(biāo)準(zhǔn)瀏覽器基本都兼容。
實(shí)現(xiàn)方式和第二種有一些像,服務(wù)器向客戶端聲明要發(fā)送流信息,然后連續(xù)不斷地發(fā)送過來。這時(shí)客戶端是不會(huì)關(guān)閉連接的,會(huì)一直等著服務(wù)器發(fā)過來的新的數(shù)據(jù)流。比如音視頻的媒體流就是這種機(jī)制。
SSE 只能服務(wù)器向?yàn)g覽器發(fā)送數(shù)據(jù),這點(diǎn)和第二種方式很像,能力上都不如,優(yōu)點(diǎn)是SSE使用更加簡(jiǎn)單,并且基于http協(xié)議,兼容性還可以(當(dāng)然2022年了,沒有啥是兼容性不可以的了)。
H5端使用對(duì)象,填入要請(qǐng)求的url地址就可以了。
var source = new EventSource('/api', {
withCredentials: true
});
source.onopen = function () {
console.log('鏈接已建立', this.readyState);
}
source.onmessage = function (event) {
console.log('實(shí)時(shí)獲取的數(shù)據(jù)', event.data);
}
source.onerror = function () {
console.log('發(fā)生錯(cuò)誤');
}
// 關(guān)閉
// source.close();
服務(wù)器向?yàn)g覽器發(fā)送的 SSE 數(shù)據(jù),首先必須設(shè)置響應(yīng)頭的Content-type為text/event-stream,且編碼格式為utf-8。返回的數(shù)據(jù)格式必須為data: xxxx\n\n。除了data還有event,id,以及retry,可以參考Server--mdn。
服務(wù)端代碼如下:
const http = require('http');
const fs = require('fs');
const app = http.createServer((req, res) => {
res.setHeader('Content-type', 'text/event-stream; charset=utf-8');
res.setHeader('Cache-Control', 'max-age=0'); // 清楚緩存
res.setHeader('Access-Control-Allow-Origin', 'http:127.0.0.1/');
let num = 0;
const send = () => {
if (num > 20) {
res.end();
return;
}
num++;
const data = Math.random() + '';
res.write(`data: ${data}\n\n`, 'utf8');
setTimeout(send, 1000);
}
send();
});
app.listen(8081, () => {
console.log('127.0.0.1:8081');
})
4. ajax
ajax輪詢,這個(gè)沒啥好說的,是個(gè)人都想的到,就不介紹了。
沒錯(cuò),這條就是用來湊數(shù)的。
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。