構(gòu)建TCP服務(wù)
TCP是面向連接的協(xié)議,其顯著的特征是在傳輸之前需要3次握手形成會話先嬉,只有會話形成之后泵肄,服務(wù)端和客戶端之間才能互相發(fā)送數(shù)據(jù),在創(chuàng)建會話的過程中焕蹄,服務(wù)端和客戶端分別提供一個套接字逾雄,這兩個套接字共同形成一個連接,服務(wù)端與客戶端則通過套接字實現(xiàn)兩者之間連接的操作腻脏。
創(chuàng)建TCP服務(wù)器端
var net = require('net');
var server = net.createServer(function (socket) {
// 新的連接
socket.on('data', function (data) {
socket.write("你好");
});
socket.on('end', function () {
console.log('連接斷開');
});
socket.write("歡迎光臨深入淺出Node.js");
});
server.listen(8124, function () {
console.log('server bound');
});
然后可以通過net模塊自行構(gòu)造客戶端進(jìn)行會話鸦泳,測試上面構(gòu)建的TCP服務(wù)的代碼。
// client.js
var net = require('net');
var client = net.connect({port: 8124}, function () { //'connect' listener
console.log('client connected');
client.write('world!\r\n');
});
client.on('data', function (data) {
console.log(data.toString());
client.end(); // 主動關(guān)閉連接
});
client.on('end', function () {
console.log('client disconnected');
});
$ node client.js
client connected
歡迎光臨深入淺出Node.js
你好
client disconnected
TCP服務(wù)的事件
服務(wù)器事件
對于通過net.createServer()創(chuàng)建的服務(wù)器而言永品,它是一個EventEmitter實例辽故,它的自定義事件有如下幾種:
listening
: 在調(diào)用server.listen()綁定端口觸發(fā),對這個事件的監(jiān)聽腐碱,簡潔寫法:可以當(dāng)做server.listen()的第二個參數(shù)傳入誊垢,server.listen(port,listeningListener)connection
: 每個客戶端套接字連接到服務(wù)器端時觸發(fā)掉弛,同樣這個事件的監(jiān)聽也有簡潔寫法,當(dāng)做net.createServer()最后一個參數(shù)傳遞喂走。close
: 當(dāng)服務(wù)器關(guān)閉時觸發(fā)殃饿,在調(diào)用sever.close()后,服務(wù)器將停止接受新的套接字連接芋肠,但保持當(dāng)前存在的連接乎芳,等待所有連接都斷開后,會觸發(fā)該事件帖池。error
: 當(dāng)服務(wù)器發(fā)生異常時奈惑,將會觸發(fā)該事件,比如偵聽一個使用中的端口睡汹,將會觸發(fā)一個異常肴甸,如果不偵聽error事件,服務(wù)器將會拋出異常囚巴。
連接事件
服務(wù)器可以同時與多個客戶端保持連接原在,對于每個連接而言是典型的可寫可讀Stream對象,就是上面代碼示例中的socket彤叉,用于服務(wù)端和客戶端之間的通信庶柿。它的事件有:
data
: 當(dāng)一端調(diào)用write()事件發(fā)送數(shù)據(jù)時,另一端接收到數(shù)據(jù)就會觸發(fā)data事件秽浇,傳遞的數(shù)據(jù)就是write()發(fā)送的浮庐。end
: 當(dāng)連接中的任意一端發(fā)送了FIN數(shù)據(jù)時,將會觸發(fā)該事件柬焕。connect
: 該事件用于客戶端审残,當(dāng)套接字與服務(wù)器端連接成功時會被觸發(fā),就是上面示例代碼client.js中的'connect' listener击喂,也是一種簡寫的方式维苔。drain
: 當(dāng)任意一端調(diào)用write()發(fā)送時,當(dāng)前這段會觸發(fā)該事件懂昂。error
: 當(dāng)異常發(fā)生時介时,觸發(fā)該事件。close
: 當(dāng)套接字完全關(guān)閉時凌彬,觸發(fā)該事件沸柔。timeout
: 當(dāng)一定時間后連接不再活躍時,該事件將會被觸發(fā)铲敛,通知用戶當(dāng)前該連接已經(jīng)被閑置了褐澎。
{{% notice info %}}
值得注意的是,TCP針對網(wǎng)絡(luò)中的小數(shù)據(jù)包有一定的優(yōu)化策略:Nagle算法伐蒋。TCP/IP協(xié)議中工三,無論發(fā)送多少數(shù)據(jù)迁酸,總要在數(shù)據(jù)前面加上協(xié)議頭,同時俭正,對方接到數(shù)據(jù)奸鬓,也需要發(fā)送ACK表示確認(rèn),為了盡可能的利用網(wǎng)絡(luò)帶寬掸读,TCP總是希望盡可能的發(fā)送足夠大的數(shù)據(jù)(一個連接會設(shè)置MSS參數(shù)串远,因此,TCP/IP希望每次能夠以MSS尺寸的數(shù)據(jù)塊來發(fā)送數(shù)據(jù))儿惫,Nagle算法就是為了盡可能發(fā)送大塊數(shù)據(jù)澡罚,避免網(wǎng)絡(luò)中充斥著許多小數(shù)據(jù)塊。
如果每次只發(fā)送1字節(jié)的數(shù)據(jù)肾请,會在傳輸上造成41字節(jié)的包留搔,其中包括1字節(jié)的有用信息和40字節(jié)的首部數(shù)據(jù),這種情況轉(zhuǎn)變成了4000%的消耗筐喳,對于輕負(fù)載的網(wǎng)絡(luò)還是可以接受的催式,但是重負(fù)載的就受不了了函喉。Nagle算法通常會在未確認(rèn)數(shù)據(jù)發(fā)送的時候讓發(fā)送器把數(shù)據(jù)送到緩存里避归,任何數(shù)據(jù)隨后繼續(xù)直到得到明顯的數(shù)據(jù)確認(rèn)或者直到攢到了一定數(shù)量的數(shù)據(jù)了再發(fā)包。
{{% /notice %}}
在Node中管呵,TCP默認(rèn)開啟Nagle算法梳毙,可調(diào)用socket.setNoDelay(true)去掉Nagle算法,使得write()可以立即發(fā)送數(shù)據(jù)到網(wǎng)絡(luò)中捐下。
另一個需要注意的是账锹,盡管在網(wǎng)絡(luò)的一段調(diào)用writre()會觸發(fā)另一端得到data事件,但是并不意味著每次write()都會觸發(fā)一次data事件坷襟,在關(guān)閉掉Nagle算法后奸柬,接受端可能會將接受到的多個小數(shù)據(jù)包合并,然后只觸發(fā)一次data事件婴程。
構(gòu)建UDP服務(wù)
UDP又稱用戶數(shù)據(jù)包協(xié)議廓奕,與TCP一樣同屬于網(wǎng)絡(luò)傳輸層,UDP和TCP最大的同是UDP不是面向連接的档叔,在UDP中桌粉,一個套接字可以與多個UDP服務(wù)通信,它雖然提供面向事務(wù)的簡單不可靠信息傳輸服務(wù)衙四,在網(wǎng)絡(luò)差的情況下存在丟到嚴(yán)重的情況铃肯,但是它無須連接、資源消耗低传蹈、處理快速且靈活押逼,所以常常應(yīng)用在那種偶爾丟一兩個數(shù)據(jù)包也不會產(chǎn)生重大影響的場景步藕,比如音頻、視頻等挑格,DNS服務(wù)也是基于它實現(xiàn)的漱抓。
創(chuàng)建UDP套接字
UDP套接字一旦創(chuàng)建,既可以作為客戶端發(fā)送數(shù)據(jù)恕齐,也可以作為服務(wù)端接受數(shù)據(jù)乞娄,
var dgram = require('dgram');
var socket = dgram.createSocket("udp4"); // 套接字
創(chuàng)建UDP服務(wù)端
var dgram = require('dgram');
var server = dgram.createSocket("udp4"); // 套接字
// 套接字事件
// 當(dāng)UDP套接字偵聽網(wǎng)卡端口后,接受到消息后觸發(fā)显歧,觸發(fā)攜帶的數(shù)據(jù)為消息的Buffer對象和一個遠(yuǎn)程地址信息
server.on("message", function (msg, rinfo) {
console.log("server got: " + msg + " from " + rinfo.address + ":" + rinfo.port);
});
// 當(dāng)UDP套接字開始偵聽時觸發(fā)
server.on("listening", function () {
var address = server.address();
console.log("server listening " + address.address + ":" + address.port);
});
// 還有close事件 和error事件
// close:調(diào)用close()方式時觸發(fā)該事件仪或,并不再觸發(fā)message事件,如需繼續(xù)觸發(fā)message 重新bind即可
// error:當(dāng)異常發(fā)生時觸發(fā)該事件士骤,如果不偵聽范删,異常將直接拋出,使進(jìn)程退出
server.bind(41234); // 接受網(wǎng)卡上所有41234端口上的消息拷肌,綁定完成后到旦,觸發(fā)listening事件
創(chuàng)建UDP客戶端
var dgram = require('dgram');
var message = new Buffer("深入淺出Node.js");
var client = dgram.createSocket("udp4");
client.send(message, 0, message.length, 41234, "localhost", function(err, bytes) {
client.close();
});
當(dāng)套接字對象用在客戶端時,可以調(diào)用send方法發(fā)送消息到網(wǎng)絡(luò)中巨缘,send的參數(shù)如下socket.send(buf, offset, length, port, address, [callback])
分別為要發(fā)送的Buffer添忘、Buffer的偏移、Buffer的長度若锁、目標(biāo)端口搁骑、目標(biāo)地址、發(fā)送完成后的回調(diào)又固。雖然參數(shù)列表相對復(fù)雜仲器,但是它更靈活的地方在于可以隨意發(fā)送數(shù)據(jù)到網(wǎng)絡(luò)中的服務(wù)器端,而TCP如果要發(fā)送數(shù)據(jù)給另一個服務(wù)器端仰冠,則需要重新通過套接字構(gòu)造新的連接乏冀。
構(gòu)建HTTP服務(wù)
HTTP是應(yīng)用層協(xié)議,Node提供了http和https模塊用于HTTP和HTTPS的封裝洋只。
HTTP協(xié)議構(gòu)建在請求和響應(yīng)的概念上辆沦,對應(yīng)在Node.js中就是由http.ServerRequest和http.ServerResponse這兩個構(gòu)造器構(gòu)造出來的對象。
當(dāng)用戶瀏覽一個網(wǎng)站時木张,用戶代理(瀏覽器)會創(chuàng)建一個請求众辨,該請求通過TCP發(fā)送給Web服務(wù)器,隨后服務(wù)器會給出響應(yīng)舷礼。
在構(gòu)建TCP服務(wù)器時鹃彻,createServer()
中接受的回調(diào)的參數(shù)是一個連接對象(connection)對象,而在HTTP服務(wù)器中則是請求和響應(yīng)對象妻献。
盡管我們可以通過req.connection獲取TCP連接對象蛛株,但大多數(shù)情況下你還是與請求和響應(yīng)的抽象打交道团赁,默認(rèn)情況下,Node會告訴瀏覽器始終保持連接(請求頭:connection: keep-alive)谨履,通過它發(fā)送更多的請求欢摄,這是為了提高性能,因為不想浪費時間去重新建立和關(guān)閉TCP連接笋粟,當(dāng)然我們可以使用writeHead()
傳遞一個不同的值怀挠,如Close,將連接關(guān)閉害捕。
HTTP
HTTP全稱是超文本傳輸協(xié)議(HyperText Transfer Protocol)绿淋,在其兩端是服務(wù)器和瀏覽器,即B/S模式尝盼,Web即是HTTP應(yīng)用吞滞。
HTTP是基于請求響應(yīng)式的,以一問一答的方式實現(xiàn)服務(wù)盾沫,雖然基于TCP會話裁赠,但是本身并無會話的特點,從協(xié)議的的角度來說赴精,瀏覽器其實是一個HTTP的代理佩捞,用戶的行為會通過它轉(zhuǎn)化為HTTP請求報文發(fā)送給服務(wù)端,服務(wù)器端在處理請求后祖娘,發(fā)送響應(yīng)報文給代理失尖,代理在解析報文后啊奄,將用戶需要的內(nèi)容呈現(xiàn)在界面上渐苏。簡而言之。HTTP服務(wù)只做兩件事菇夸,處理HTTP請求發(fā)送和發(fā)送HTTP響應(yīng)琼富。
無論是請求報文還是響應(yīng)報文,報文內(nèi)容都包含兩個部分報文頭和報文體庄新,但是GET的請求報文中沒有包含報文體鞠眉,傳遞的消息包含在報文頭中。
http模塊
在Node中,HTTP服務(wù)繼承自TCP服務(wù)器(net模塊)择诈,它能夠與多個客戶端保持連接械蹋,由于其采用事件驅(qū)動的形式,并不為每一個連接創(chuàng)建額外的線程或進(jìn)程羞芍,保持很低的內(nèi)存占用哗戈,所以能實現(xiàn)高并發(fā)。
HTTP服務(wù)于TCP服務(wù)模型有區(qū)別的地方在于荷科,在開啟keepalive后唯咬,一個TCP會話可以用于多次請求和響應(yīng)纱注,TCP服務(wù)以connection為單位進(jìn)行服務(wù),HTTP服務(wù)以request進(jìn)行服務(wù)胆胰。
http模塊將連接所用套接字的讀寫抽象為ServerRequest和ServerResponse對象狞贱,它們分別對應(yīng)請求和響應(yīng)操作(http服務(wù)回調(diào)中常用的req, res),在請求產(chǎn)生的過程中,http模塊拿到連接中傳來的數(shù)據(jù),調(diào)用二進(jìn)制模塊http_parser進(jìn)行解析敦姻,在解析完請求報文的報頭后质况,觸發(fā)request事件,調(diào)用用戶的業(yè)務(wù)邏輯奸绷。
http請求
對于TCP連接的讀操作,http模塊將其封裝為ServerRequest對象,報文頭通過http_parser進(jìn)行解析挑胸。
報文頭解析為如下屬性。
req.method:請求方法
req.url:請求的url
req.httpVersion:使用的http的版本
req.headers: 其余報文宰闰,包含'user-agent'茬贵、'host'、'accept'等移袍。
報文體部分則抽象為一個只讀流對象解藻,如果業(yè)務(wù)邏輯需要讀取報文體中的數(shù)據(jù),則需要在數(shù)據(jù)流結(jié)束后才能進(jìn)行操作葡盗,即req對象上的end事件觸發(fā)后螟左。
http響應(yīng)
http模塊將其封裝為ServerResponse對象,可以將其看成一個可寫的流對象觅够,它影響響應(yīng)報文頭部信息的API為res.setHeader()和res.writeHead()
我們可以調(diào)用setHeader進(jìn)行多次設(shè)置胶背,但只有調(diào)用writeHead后,報頭才會寫入到連接中喘先,除此之外钳吟,http模塊會自動幫你設(shè)置一些頭信息。
報文體部分則是調(diào)用res.write()和res.end()方法實現(xiàn)窘拯,res.end()會調(diào)用write()發(fā)送數(shù)據(jù)红且,然后發(fā)送信號告知服務(wù)器這次響應(yīng)結(jié)束。
值得注意的是涤姊,報頭是在報文體發(fā)送前發(fā)送的暇番,一旦開始了數(shù)據(jù)的發(fā)送,writeHead()和setHeader()將不再生效思喊。
{{% notice tip %}}
無論服務(wù)端在處理業(yè)務(wù)邏輯時是否發(fā)生異常壁酬,務(wù)必在結(jié)束時調(diào)用res.end()結(jié)束請求,否則客戶端將一直處于等待的狀態(tài)。
{{% /notice %}}
http服務(wù)的事件
connection
事件:在開始http請求和響應(yīng)前厨喂,客戶端和服務(wù)器端需要建立底層的TCP連接和措,這個連接可能因為開啟了keep-alive,可以在多次請求和響應(yīng)之間使用蜕煌,當(dāng)這個連接建立時服務(wù)器觸發(fā)一次connection事件派阱。request
事件:建立TCP連接后,http模塊底層將在數(shù)據(jù)流中抽象出HTTP請求和HTTP響應(yīng)斜纪,當(dāng)請求數(shù)據(jù)發(fā)送到服務(wù)端贫母,并解析出http請求頭后,會觸發(fā)該事件盒刚。close
事件:與TCP服務(wù)器的行為一致腺劣,調(diào)用server.close()方法停止接受新的連接,當(dāng)已有的連接都段開始因块,觸發(fā)該事件橘原。checkContinue
事件:某些客戶端在發(fā)送較大的數(shù)據(jù)時,并不會將數(shù)據(jù)直接發(fā)送涡上,而是先發(fā)送頭部帶Expect: 100-continue的請求到服務(wù)器趾断,服務(wù)器觸發(fā)checkContinue觸發(fā),如果服務(wù)器沒有監(jiān)聽這個事件吩愧,將自動響應(yīng)客戶帶100 Continue的狀態(tài)碼表示接受數(shù)據(jù)上傳芋酌,如果不接受的數(shù)據(jù)較多時,則響應(yīng)400 Bad Request拒絕客戶端繼續(xù)發(fā)送雁佳,需要注意的是脐帝,當(dāng)該事件發(fā)送時不會觸發(fā)request事件,兩個事件之間互斥糖权,當(dāng)客戶端收到100 Continue后重新發(fā)起請求時堵腹,才會觸發(fā)request事件。connect
事件:當(dāng)客戶端發(fā)起CONNECT請求時觸發(fā)温兼,而發(fā)起CONNECT請求通常在HTTP代理時出現(xiàn)秸滴,如果不監(jiān)聽該事件,發(fā)起該請求的連接將會關(guān)閉募判。upgradd
事件:當(dāng)客戶端要求升級連接的協(xié)議時,需要和服務(wù)器端協(xié)商咒唆,客戶端會在請求頭中帶上Upgrade字段届垫,服務(wù)器會在接受到這樣的請求時觸發(fā)該事件,如果不監(jiān)聽該事件全释,發(fā)起該請求的連接將會關(guān)閉装处。clientError
事件:連接的客戶端觸發(fā)error事件時,這個錯誤會傳遞到服務(wù)器端,此時觸發(fā)該事件妄迁。
HTTP客戶端
http模塊提供了一個底層API:http.request(options, connect)
寝蹈,用于構(gòu)造HTTP客戶端
var options = {
hostname: '127.0.0.1', // 服務(wù)器名稱
port: 1334, // 服務(wù)器端口
path: '/', // 具體請求的路由
method: 'GET' // 請求的方法
};
// 其他選項
// host: 服務(wù)器的域名或IP地址,默認(rèn)為localhost
// localAddress: 建立網(wǎng)絡(luò)連接的本地網(wǎng)卡
// socketPath: Domain套接字路徑
// headers: 請求頭對象
// auth: Basic認(rèn)證登淘,這個值計算成請求頭中Authorization部分
var req = http.request(options, function(res) { // response listener
console.log('STATUS: ' + res.statusCode);
console.log('HEADERS: ' + JSON.stringify(res.headers));
res.setEncoding('utf8');
res.on('data', function (chunk) {
console.log(chunk);
});
});
req.end();
HTTP代理
為了重用TCP連接箫老,http模塊包含一個默認(rèn)的客戶端代理對象http.globalAgent
,它對每個服務(wù)器端(host + port)創(chuàng)建的連接進(jìn)行管理黔州,默認(rèn)情況下耍鬓,通過ClientRequest對象對同一個服務(wù)器端發(fā)起的HTTP請求最多創(chuàng)建5個連接,如果調(diào)用HTTP客戶端同時對一個服務(wù)器發(fā)送10次HTTP請求時流妻,其實質(zhì)只有5個請求處于并發(fā)狀態(tài)牲蜀,后續(xù)的請求需要等待某個請求完成服務(wù)后才真正發(fā)出。
可以通過在options中傳遞agent選項來改變這個限制绅这。
var agent = new http.Agent({
maxSockets: 10
});
var options = {
hostname: '127.0.0.1',
port: 1334,
path: '/',
method: 'GET',
agent: agent // 直接設(shè)為false涣达,可以使請求不受并發(fā)的控制
};
// Agent對象的sockets和requests屬性分別表示當(dāng)前連接池中使用中的連接數(shù)和處于等待狀態(tài)的請求數(shù)
// 在業(yè)務(wù)中監(jiān)視這兩個值有助于發(fā)現(xiàn)業(yè)務(wù)狀態(tài)的繁忙程度
http客戶端事件
response
:請求發(fā)出后得到服務(wù)端的響應(yīng)時觸發(fā)該事件。socket
:當(dāng)?shù)讓舆B接池中建立的連接分配給當(dāng)前請求對象時证薇,觸發(fā)該事件峭判。connect
:當(dāng)客戶端向服務(wù)端發(fā)送CONNECT請求時,如果服務(wù)器響應(yīng)了200狀態(tài)碼棕叫,客戶端觸發(fā)該事件林螃。upgrade
:客戶端向服務(wù)端發(fā)起Upgrade請求時,如果服務(wù)器響應(yīng)101 Switching Protocols狀態(tài)俺泣,客戶端觸發(fā)該事件疗认。continue
: 客戶端向服務(wù)端發(fā)起Expect:100-continue頭信息,以試圖發(fā)送較大數(shù)據(jù)量伏钠,如果服務(wù)端響應(yīng)了100 continue狀態(tài)横漏,客戶端觸發(fā)該事件。
構(gòu)建WebSocket服務(wù)
{{% notice tip %}}
WebSocekt前端使用講解
{{% /notice %}}
WebSocket與Node之間的配合堪稱完美熟掂,其理由有兩條:
WebSocket客戶端基于事件的編程模型與Node中自定義事件相差無幾
WebSocket實現(xiàn)了客戶端與服務(wù)器端之間的長連接缎浇,而Node事件驅(qū)動的方式十分擅長與大量的客戶端保持高并發(fā)連接。
相比于HTTP赴肚,WebSocket有如下優(yōu)勢:
客戶端與服務(wù)端只建立一個TCP連接即可完成雙向通信素跺,在服務(wù)端和客戶端頻繁通信時,無需頻繁斷開連接和重發(fā)請求誉券,連接可以得到高效應(yīng)用指厌,編程模型也十分簡潔。
WebSocket服務(wù)器端可以推送數(shù)據(jù)到客戶端踊跟,遠(yuǎn)比HTTP的請求響應(yīng)模式更靈活踩验、更高效。
有更輕量級的協(xié)議頭,減少數(shù)據(jù)傳送量箕憾。
{{% notice tip %}}
相比于HTTP牡借,WebSocket更接近于傳輸層協(xié)議,它并沒有在HTTP的基礎(chǔ)上模擬服務(wù)器端的推送袭异,而是在TCP上定義獨立的協(xié)議钠龙,讓人迷惑的部分在于WebSocket的握手部分是由HTTP完成的,使人覺得它可能是基于HTTP實現(xiàn)的
{{% /notice %}}
WebSocket協(xié)議主要分為兩個部分:握手和數(shù)據(jù)傳輸扁远。
WebSocket握手
客戶端建立連接時俊鱼,通過HTTP發(fā)起請求報文:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
其中Upgrade
、Connection
字段代表請求服務(wù)器升級協(xié)議為WebSocket畅买,其中Sec-WebSocket-Protocol
和Sec-WebSocket-Version
指定子協(xié)議和版本并闲,
Sec-WebSocket-Key
字段用于安全校驗,它的值是客戶端隨機生成的Base64編碼的字符串谷羞。服務(wù)端接收之后帝火,將其與字符串258EAFA5-E914-47DA-95CA-C5AB0DC85B11
(固定的)相拼接,然后通過sha1安全散列算法計算出結(jié)果后湃缎,再進(jìn)行Base64編碼犀填,最后當(dāng)做響應(yīng)頭Sec-WebSocket-Accept
的值返回給客戶端。
// 服務(wù)端的對Sec-WebSocket-Key的處理
var crypto = require('crypto');
var WS = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
var key = req.headers['sec-websocket-key'];
key = crypto.createHash('sha1').update(key + WS).digest('base64');
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
服務(wù)器應(yīng)答之后嗓违,Client 拿到Sec-WebSocket-Accept
九巡,然后本地做一次驗證,如果驗證通過了蹂季,就會觸發(fā) onopen
函數(shù)冕广。
WebSocket數(shù)據(jù)傳輸
在握手順利完成后,當(dāng)前連接不再進(jìn)行HTTP的交互偿洁,而是開始WebSocket的數(shù)據(jù)幀協(xié)議撒汉,實現(xiàn)客戶端和服務(wù)器端的數(shù)據(jù)交換。
當(dāng)我們調(diào)用send()發(fā)送一條數(shù)據(jù)時涕滋,協(xié)議可能將這個數(shù)據(jù)封裝為一幀或多幀數(shù)據(jù)睬辐,然后逐幀發(fā)送。
為了安全考慮宾肺,客戶端需要對發(fā)送的數(shù)據(jù)進(jìn)行掩碼處理溯饵,服務(wù)器一旦收到無掩碼幀,連接將關(guān)閉爱榕。
服務(wù)器發(fā)送到客戶端的數(shù)據(jù)則需要做無掩碼處理瓣喊,客戶端如果收到帶掩碼的數(shù)據(jù)幀,連接將關(guān)閉黔酥。
FIN
:占1位,如果這個數(shù)據(jù)幀是最后一幀,這個FIN位是1跪者,其余情況為0棵帽,當(dāng)一個數(shù)據(jù)沒有被分為多楨時,它既是第一幀也是最后一幀渣玲,為1逗概。RSV1、RSV2忘衍、RSV3
:占3位逾苫,保留位。opcode
:占4位的操作碼枚钓,即表示0-15的二進(jìn)制數(shù)值铅搓,用來解釋當(dāng)前數(shù)據(jù)幀,0表示附加數(shù)據(jù)幀搀捷,1表示文本數(shù)據(jù)幀星掰,2表示二進(jìn)制數(shù)據(jù)幀,8表示發(fā)送一個連接關(guān)閉的數(shù)據(jù)幀嫩舟,9表示ping數(shù)據(jù)幀氢烘,10表示pong數(shù)據(jù)值,其余的值暫時未定義家厌。ping數(shù)據(jù)幀和pong數(shù)據(jù)幀用于心跳檢測播玖,當(dāng)一端發(fā)送ping數(shù)據(jù)幀時,另一端必須發(fā)送pong數(shù)據(jù)幀作為響應(yīng)饭于,告知對方這一段仍然處于響應(yīng)狀態(tài)MASK
: 占1位蜀踏,表示是否進(jìn)行掩碼處理,為1時代表是镰绎,客戶端發(fā)送給服務(wù)端為1脓斩,反之服務(wù)端發(fā)送給客戶端時為0。payload length
: 占7畴栖、7+16或7+64位随静,前7位標(biāo)示數(shù)據(jù)的長度,即表示0127的二進(jìn)制數(shù)值吗讶,當(dāng)值在0125之間燎猛,那么該值就是數(shù)據(jù)的真實長度,如果值是126照皆,則后面16位的值是數(shù)據(jù)的真實長度(16位可表示的數(shù)值為0-65535重绷,即在數(shù)據(jù)長度范圍在126b~8kb,65536代表數(shù)據(jù)轉(zhuǎn)換成二進(jìn)制的長度膜毁,則字節(jié)長度 65536 / 8 = 8192b)昭卓,如果值是127愤钾,則后面64位的值是數(shù)據(jù)的真實長度。Masking key
: 當(dāng)MASK為1時存在候醒,即當(dāng)客戶端給服務(wù)端發(fā)送數(shù)據(jù)時能颁,是一個32位長的數(shù)據(jù)位,用于解密數(shù)據(jù)倒淫。Payload Data
: 目標(biāo)數(shù)據(jù)伙菊,位數(shù)為8的整數(shù)。
如果客戶端發(fā)送hello world!
到服務(wù)端敌土,12個字符镜硕,則長度為12 * 8 = 96位,轉(zhuǎn)換為二進(jìn)制位1100000返干,則報文應(yīng)當(dāng)如下:
fin(1) + res(000) + opcode(0001) + masked(1) + payload length(1100000) + masking key(32位) + payload data(hello world!加密后的二進(jìn)制)
服務(wù)器回復(fù)yakexi兴枯,報文則如下,無需掩碼犬金。
fin(1) + res(000) + opcode(0001) + masked(0) + payload length(0110000) + + payload data(yakexi加密后的二進(jìn)制)