Nodejs implementation of WebSocket data receiving and sending

Source: Internet
Author: User
Tags sha1

In the last year, wrote a blog about WebSocket: http://www.cnblogs.com/axes/p/3586132.html, mainly with the help of Nodejs-websocket this plugin, Later also used Socket.io do some demo, but, these are with the help of others encapsulation good plug-in made out, websocket in the end is how to realize it before I really did not think about, recently in See Pauling the Great God's "in the Nodejs", See WebSocket that chapter, looked at the websocket of the data frame definition, pondering their own use of nodejs to achieve a bit.

Finally also realized out, first on the Demo:http://wanghx.cn:9030/wsindex more open several windows, and then in a window input data points sent can see the effect. (because I was broadcasting directly when I received a message, so a person who sent a message to open the page can receive it)

The client's code does not say, WebSocket API is very simple, through the OnMessage, OnOpen, OnClose, and the Send method can be achieved.

The main service-side code:

The first is the upgrade of the protocol, this is relatively simple, briefly describe:

When the client executes the new Websocket ("ws://xxx.com/"), the client initiates a request message for the handshake request, there is an important key in the message is the Sec-websocket-key, the server gets the key, The key is then concatenated with the string 258eafa5-e914-47da-95ca-c5ab0dc85b11, the new string is computed by the SHA1 secure Hash algorithm, then Base64 encoded, and the result is placed in the request header " Sec-websocket-accept "to complete the handshake. You can then transfer the data

Client Request Header:

  

In response to the server, see the code:

Server.on (' Upgrade ', function (req, socket, upgradehead) {            var key = req.headers[' Sec-websocket-key '];            Key = Crypto.createhash ("SHA1"). Update (key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"). Digest ("base64");            var headers = [                ' http/1.1 101 switching Protocols ',                ' upgrade:websocket ',                ' Connection:upgrade ',                ' Sec-websocket-accept: ' + key            ];            Socket.setnodelay (true);            Socket.write (Headers.join ("\ r \ n") + "\r\n\r\n", ' ASCII ');            var ws = new WebSocket (socket);            Websocketcollector.push (WS);            Callback (WS);        });

The upgrade event is actually the HTTP module encapsulation, and then to the bottom of the net module implementation, in fact, are similar, if directly with the net module to achieve, is to listen to the data event of the server object returned by Net.createserver, and the first data received is the upgrade request message sent by the client.

The above code completes the WebSocket handshake, and then you can start the data transfer.

Look at the data transfer before, look at the definition of the websocket (because it is easy to understand the frame definition diagram in the Nodejs, so it is the most understandable, so it is affixed to this one):

  

In the above figure, each column is a byte, a byte total is 8 bits, each one is a binary number, the values of different bits will correspond to different meanings.

  fin: indicates that this is the last fragment of the message. The first fragment may also be the last fragment. If 1 is the last fragment, (in fact, the use of this bit I am a bit puzzled, according to the book and the information on the Internet, when the data is fragmented, different slices should have a fin, depending on whether the fin is the final frame is determined, but the actual implementation found that when the data is larger need to Shard, The data received by the server is only the first frame is the fin bit is 1, the other frame is the entire frame is a data segment, that is, feel this fin bit does not seem to be used, at least I wrote the demo is the data length to determine whether to the last frame, completely useless to this fin bit is the first judgment)

  Rsv1, Rsv2, Rsv3: each occupies a bit, for the expansion of the negotiations, basically do not need to reason, is generally 0

  opcode: Four bits, can represent the 0~15 of the decimal, 0 for the additional data frame, 1 for the text data frame, 2 for the binary data frame, 8 for sending a connection closed data frame, 9 means that ping,10 represents pong, Both ping and Pong are used for heartbeat detection, and when one side sends a ping, the other end must respond to Pong to indicate that it is still in a responsive state.

  masked: A bit that indicates whether mask processing is performed, when the client is sent to the server at 1, and 0 when the server is sent to the client

  Payload Length: 7 bits, or 7+16 bit, or 7+64 bit. If the decimal value of the next seven bits of the second byte is less than or equal to 125, the data length is directly represented by these seven bits, and if the value is 126, the 125< data length is <65535 (16 of the maximum value of the potential energy description, which is 16 1), is represented by the third byte and the fourth byte, or 16 bits, if the value is 127, the data length is already greater than 65535, the 16 bits are not enough to describe the length of the data, and the third to tenth byte of the eight bytes is used to describe the data length.

  Masking key: only exists when masked is 1 for decrypting the data we need.

  Payload Data: What we need, if the masked is 1, the data will be encrypted, to be decrypted by masking key to obtain the real data.

  

After the frame definition is interpreted, it can be parsed according to the data, and when there is data coming in, first obtain the necessary information, the following code will get the data in the location, and the data length, masking key and opcode:

WebSocket.prototype.handleDataStat = function (data) {if (!this.stat) {var dataindex = 2;       The data index, because the first byte and the second byte are certainly not data, so the initial value is 2 var secondbyte = data[1]; Represents the masked bit and may be the second byte of the payloadlength bit var hasmask = secondbyte >= 128; If it is greater than or equal to 128, the masked bit is 1 secondbyte-= Hasmask?    128:0;        If there is a mask, the mask needs to be removed from the Var datalength, Maskeddata;            If 126, then the 16-bit long data is the data length, if 127, then 64 bits of data for the data length if (Secondbyte = = 126) {dataindex + = 2;        Datalength = Data.readuint16be (2);            } else if (Secondbyte = = 127) {dataindex + = 8;        Datalength = Data.readuint32be (2) + DATA.READUINT32BE (6);        } else {datalength = Secondbyte; }//If there is a mask, obtain the 32-bit binary masking key while updating the index if (hasmask) {maskeddata = Data.slice (Dataindex, DataI            Ndex + 4);        Dataindex + = 4; }//The maximum data volume is 10kb if (Datalength > 10240) {this.send ("Warning:data limit10KB ");                } else {//when computed here, Dataindex is the starting position of the data bit, DATALENGTH is the data length, Maskeddata is the binary decrypted data This.stat = { Index:dataindex, Totallength:datalength, Length:datalength, Maskeddata:        Maskeddata, Opcode:parseint (data[0].tostring) Split ("") [1], 16)//Get the first byte of the opcode bit};    }} else {this.stat.index = 0; }};

There are comments in the code, it should not be difficult to understand, directly look at the next step, to obtain data information, it is necessary to analyze the actual data:

After the processing of the above Handledatastat method, stat has the data in the relevant information, first Judge OpCode, if the 9 description is the client-initiated ping heartbeat detection, directly return the pong response, if 10 is the service-side heartbeat detection. If there is a masking key, the data segment is traversed, each byte is different from the byte of the masking key (the online view is very image: that is, the X-Relationship in turn), the ^ symbol is to make an XOR operation. If there is no masking key, the data is intercepted directly by the slice method.

After obtaining the data, put into the datas to save, because it is possible that the data is fragmented, so the length of the stat minus the current data length, only when the length of the stat is 0, indicating that the current frame is the last frame, Then, by merging all the data through Buffer.concat, and then judging opcode, if opcode is 8, then the client initiates a shutdown request, and the data we get is the reason for the shutdown. If it is not 8, then this data is the data we need. The stat is then reset to the Null,datas array to empty. At this point, our data parsing is complete.

WebSocket.prototype.dataHandle = function (data) {this.handledatastat (data);    var stat; if (! (    Stat = this.stat)) return; If opcode is 9, the pong response is sent, if the opcode is 10 pingtimes is 0 if (stat.opcode = = = 9 | | stat.opcode = =) {(Stat.opcode = = 9)?        (This.sendpong ()): (this.pingtimes = 0);        This.reset ();    Return    } var result;        if (stat.maskeddata) {result = new Buffer (data.length-stat.index);            for (var i = stat.index, j = 0; i < data.length; i++, J + +) {//Each byte is XOR, masked is 4 bytes, so% 4, this loop        RESULT[J] = data[i] ^ stat.maskeddata[j% 4];    }} else {result = Data.slice (Stat.index, data.length);    } this.datas.push (Result);    Stat.length-= (Data.length-stat.index);        When the length is 0, the current frame is the last frame if (stat.length = = 0) {var buf = Buffer.concat (This.datas, stat.totallength);        if (Stat.opcode = = 8) {this.close (buf.tostring ()); } else {this.emit ("message", Buf.tostRing ());    } this.reset (); }};

  

The client-sent data parsing is done, and a service-side-to-client approach is required, which is to assemble the data and send it according to the frame definition described above. The following code basically has comments on each line, which should still be easier to understand.

Data Send WebSocket.prototype.send = function (message) {    if (this.state!== "OPEN") return;        Message = String (message);    var length = buffer.bytelength (message);//  The starting position of the data, if the data length 16 bits can not be described, then 64 bits, or 8 bytes, if 16 bits can be described by 2 bytes, otherwise the second byte description    var  index = 2 + (length > 65535 8: (Length > 2:0));//  define buffer, length is description byte length + message length    var buffer = new Buffer (index + length);//  first Byte, fin bit is 1,opcode 1    buffer[0] = 129;//    because it is sent to the client by the server, there is no need to masked the mask    if ( Length > 65535) {        buffer[1] = 127;//      length More than 65535 is represented by 8 bytes, because 4 bytes can be expressed in length of 4294967295, is fully sufficient, so directly 4 bytes 0        buffer.writeuint32be (0, 2);        Buffer.writeuint32be (length, 6);    } else if (length >) {        buffer[1] = 126;//longer      than 125 is represented by a 2-byte        buffer.writeuint16be (length, 2);    } else {        buffer[1] = length;    }    writes    the body buffer.write (message, index);    This.socket.write (buffer);};

In addition to the realization of a function, is heartbeat detection: To prevent the service side for a long time do not interact with the client caused the client to close the connection, so every 10 seconds to send a ping for heartbeat detection

Heartbeat detection every 10 seconds, if the heartbeat is issued three times without receiving a response, then close socketWebSocket.prototype.checkHeartBeat = function () {var = this    ;    SetTimeout (function () {        if (that.state!== "OPEN") return;        if (That.pingtimes >= 3) {            that.close ("Time Out");            return;        }        Record the number of heartbeats        that.pingtimes++;        That.sendping ();        That.checkheartbeat ();    }, 10000);}; WebSocket.prototype.sendPing = function () {    this.socket.write (new Buffer ([' 0x89 ', ' 0x0 '])}; WebSocket.prototype.sendPong = function () {    this.socket.write (new Buffer ([' 0x8A ', ' 0x0 '])};

Finally, it is called directly in the main function, and when the message is received it is broadcast ...

var server = Http.createserver (function (req, res) {    Router.route (req, res);}). Listen (9030); websocket.update (server, function (WS) {    ws.on (' Close ', function (reason) {        console.log ("socket Closed: "+reason);    });    Ws.on (' message ', function (data) {        websocket.brocast (data);    });

At this point, the implementation of the entire websocket is completed, this demo is only about the implementation of WebSocket just, in the security and so on, there must be many problems, if the real production environment or with socket.io such mature plug-in is better. But it's still worth learning.

Include the demo's GitHub address: Https://github.com/whxaxes/node-test/tree/master/server/websocket

"Transfer from Http://www.cnblogs.com/axes/p/4514199.html?utm_source=tuicool"

Nodejs implementation of WebSocket data receiving and sending

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.