Analysis of Nodejs to realize websocket data receiving and sending _node.js

Source: Internet
Author: User
Tags base64 comments sha1

WebSocket is a network technology for Full-duplex communication between browsers and servers that HTML5 has started to provide. In the WebSocket API, the browser and the server only need to do a handshake (handshaking) of the action, then the browser and the server between the formation of a fast path. The data can be transmitted directly between the two.

WebSocket is a communication protocol, divided into servers and clients. Server in the background, maintain a long connection with the client, complete the task of communication between the two sides. The client is generally implemented in the support HTML5 browser core, by providing JAVASCRIPTAPI to use the Web page can establish websocket connection.

In my writing this article: based on HTML5 and Nodejs combined to achieve websocket even if the communication, which is mainly through the use of Nodejs-websocket this plug-in, Later also used Socket.io to do some demo, but, these are the help of others packaged Plug-ins made out, websocket in the end is how to achieve it? Really did not think before, recently see Pauling "Deep analysis Node.js" time, saw the WebSocket that section, saw the websocket of the data frame definition, want to use NODEJS implementation. After some toss to achieve.

The client's code is not said, WebSocket API is very simple, through the OnMessage, OnOpen, OnClose, and send method can be implemented.
The WebSocket API implements the client's code through the OnMessage, OnOpen, OnClose, and send methods. The details are not much to say.

Basically said the service side code:

The first is the protocol upgrade, this is relatively simple, briefly: when the client executes the new Websocket ("ws://xxx.com/"), the client will initiate the request message handshake application, The message has a very important key is Sec-websocket-key, the server gets the key, and then the key and string 258eafa5-e914-47da-95ca-c5ab0dc85b11 connected, The new string is Base64 encoded after the result is computed by the SHA1 secure hashing algorithm, and the result is returned in the "sec-websocket-accept" of the request header to complete the handshake. See the code specifically:

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 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);
});

Upgrade event is actually HTTP this module encapsulation, and then to the bottom is the net module implementation, in fact, almost, if the direct use of net modules to achieve, is to listen for the data event of the server object returned by the Net.createserver, the first one received is the update request message sent by the client.

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

Before looking at the data transfer, first look at the definition of the WebSocket data frame (because it is easy to understand the frame definition diagram in Nodejs, so put this on):

In the above diagram, each column is a byte, a byte total is 8 bits, each is a binary number, the value 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 the 1 is the last fragment, (actually this bit of use I personally a little puzzled, according to the book as well as online information, when the data is fragmented, different slices should have fin bit, according to Fin is not 0来 to determine whether the last frame, but the actual implementation is found, when the data is large need to slice, The data that the server receives is only the first frame has a fin bit of 1, the other frame is the entire frame is a data segment, that is to say, feel that the fin bit does not seem to use, at least I wrote the demo by the length of the data to determine whether the last frame, completely useless to this fin bit is to judge whether

RSV1, Rsv2, Rsv3: Each occupies a bit, used to expand the consultation, basically not need rationale, generally are 0

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

Masked: Occupies a bit, indicates whether the mask processing, the client sends to the service end is 1, when the server sends to the client is 0

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

Masking key: When the masked is 1, it exists to decrypt the data we need.

Payload: The data we need, if the masked is 1, the data will be encrypted, the masking key to the different or operational decryption to get the real data.

After the definition of the frame is explained, it can be parsed according to the data, and when there is data coming, first get the information you need, the following code will get the position of the data, and the data length, masking key and opcode:

WebSocket.prototype.handleDataStat = function (data) {if (!this.stat) {var dataindex = 2;//index, because the first byte and the second byte are definitely not data, So the initial value is 2 var secondbyte = data[1]; Represents the masked bit and the second byte that may be payloadlength bit var hasmask = secondbyte &gt;= 128; If greater than or equal to 128, indicates masked bit is 1 secondbyte-= Hasmask? 128:0;
 If there is a mask, you need to remove the mask that is Var datalength, Maskeddata;
  If 126, then the 16-bit long data is the data length, and if 127, the back 64-bit Long data is 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 a 32-bit binary masking key, and update the index if (hasmask) {maskeddata = Data.slice (dataindex, Dataindex + 4);
 Dataindex + 4;
 ///The maximum amount of data is 10kb if (Datalength &gt; 10240) {this.send ("Warning:data limit 10kb"); else {//when computed here, Dataindex is the starting position of the data bit, DATALENGTH is the data length, Maskeddata is the binary decryption data This.stat = {Index:dataindex, Totalleng Th:datalength, Length:datalength, MASkeddata:maskeddata, Opcode:parseint (data[0].tostring). Split ("") [1], 16)//Get the first byte opcode bit};
 } else {this.stat.index = 0; }
};

There are comments in the code, it should not be difficult to understand, directly to see 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 already had data in the relevant information, first judged opcode, if the 9 description is the client-initiated ping heartbeat detection, direct return pong response, if 10 for the server-initiated heartbeat detection. If there is a masking key, then traverse the data segment, each byte and masking key byte is different or operation (online see a saying is very image: is taking place x relationship), ^ symbol is to do XOR or operation. If there is no masking key, the data is intercepted directly through the slice method.

After getting the data, put it in the datas, because it is possible that the data is fragmented, so the length of the stat is subtracted from the current data length, only when the length of the stat is 0, the current frame is the last frame, Then by buffer.concat all the data merge, and then judge the opcode, if the opcode is 8, then the client initiates a shutdown request, and the data we get is the shutdown reason. If 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 the opcode is 9, the pong response is sent, and if opcode is 10 then the pingtimes is 0 if (Stat.opcode = 9 | | stat.opcode = ten) {(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 + +) {//per byte xor, masked is 4 bytes, so% 4, take 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 (); 
}
};

Completes the client sends the data to parse, also needs one service to send the data to the client method, namely according to the above frame definition to assemble the data and sends out. The following code basically has comments in each row, and it should 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 use 64 bits, that is 8 bytes, if 16 bits can be described with 2 bytes, otherwise, the second byte is used to describe the
 var index = 2 + (length > 65535? 8: (Length > 12 5? 2:0));
Define buffer, length for description byte length + message length
 var buffer = new Buffer (index + length);
The first byte, the fin bit is 1,opcode 1
 buffer[0] = 129;
Because the server is sent to the client, there is no need for masked mask
 if (length > 65535) {
 buffer[1] = 127;
The length of more than 65535 is represented by 8 bytes, because 4 bytes can express a length of 4294967295, already fully sufficient, so directly to the front 4 bytes 0
 buffer.writeuint32be (0, 2);
 Buffer.writeuint32be (length, 6);
 } else if (length >) {
 buffer[1] = 126;
A length of more than 125 is represented by 2 bytes
 buffer.writeuint16be (length, 2);
 } else {
 buffer[1] = length;
 }
Write
 the body buffer.write (message, index);
 This.socket.write (buffer);

Finally, a function, that is heartbeat detection: Prevent the service end of the long time does not interact with the client and cause the client to shut down the connection, so every 10 seconds will send a ping for heartbeat detection

A heartbeat every 10 seconds, if the three consecutive heartbeat but did not receive a response then close the socket
WebSocket.prototype.checkHeartBeat = function () {
 var that = this;
 settimeout (function () {
 if (that.state!== "OPEN") return;
 if (That.pingtimes >= 3) {
  that.close ("Time Out");
  return;
 }
 Record the heartbeat times
 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 '])
};

At this point, the entire WebSocket implementation is completed, this demo just probably realized a little websocket just, in security and so on there are certainly many problems, if the real production environment or with socket.io such mature plug-ins better. But it's still worth learning.

The above content is small make up to share the analysis Nodejs realize WebSocket data receive and send all content, hope everybody likes.

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.