The following figure shows the handshake between the client and the server when a websocket connection is established. This part can be easily completed in node, because the net module provided by node has encapsulated socket sockets, developers only need to consider data interaction instead of connection creation. Php does not, from socket connection, establishment, binding, listening, etc., all of which need to be operated by ourselves, so it is necessary to come up with it.
① And ② are actually an HTTP request and response, but what we get during the processing is a string that has not been parsed. For example:
Copy codeThe Code is as follows:
GET/chat HTTP/1.1
Host: server.example.com
Origin: http://www.jb51.com
We often see this request. When it comes to the server side, we can directly obtain this information through some code libraries.
I. websocket processing in php
The WebSocket connection is actively initiated by the client, so everything needs to start from the client. The first step is to parse the Sec-WebSocket-Key string sent from the client.
Copy codeThe Code is as follows:
GET/chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ =
Origin: http://www.jb51.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Client request format
First, php establishes a socket connection to listen to the port information.
1. Establish a socket connection
Concerning the Establishment of socket, I believe that many people who have studied computer networks in universities know it. The following is a process of establishing a connection:
Copy codeThe Code is as follows:
// Create a socket
$ Master = socket_create (AF_INET, SOCK_STREAM, SOL_TCP );
Socket_set_option ($ master, SOL_SOCKET, SO_REUSEADDR, 1 );
Socket_bind ($ master, $ address, $ port );
Socket_listen ($ master );
Compared with node, the processing in this place is too troublesome. The above lines of code do not establish a connection, but these codes are what must be written to build a socket. Because the processing process is a little complicated, I wrote various processing into a class to facilitate management and calling.
Copy codeThe Code is as follows:
// Demo. php
Class WS {
Var $ master; // connect to the client of the server
Var $ sockets = array (); // manage sockets in different States
Var $ handshake = false; // determines whether to shake hands.
Function _ construct ($ address, $ port ){
// Create a socket
$ This-> master = socket_create (AF_INET, SOCK_STREAM, SOL_TCP)
Or die ("socket_create () failed ");
Socket_set_option ($ this-> master, SOL_SOCKET, SO_REUSEADDR, 1)
Or die ("socket_option () failed ");
Socket_bind ($ this-> master, $ address, $ port)
Or die ("socket_bind () failed ");
Socket_listen ($ this-> master, 2)
Or die ("socket_listen () failed ");
$ This-> sockets [] = $ this-> master;
// Debug
Echo ("Master socket:". $ this-> master. "\ n ");
While (true ){
// Automatically select the socket for the message. If it is a handshake, the host is automatically selected.
$ Write = NULL;
$ Response T = NULL;
Socket_select ($ this-> sockets, $ write, $ hour T, NULL );
Foreach ($ this-> sockets as $ socket ){
// Connect the client of the host
If ($ socket ==$ this-> master ){
$ Client = socket_accept ($ this-> master );
If ($ client <0 ){
// Debug
Echo "socket_accept () failed ";
Continue;
} Else {
// Connect ($ client );
Array_push ($ this-> sockets, $ client );
Echo "connect client \ n ";
}
} Else {
$ Bytes = @ socket_recv ($ socket, $ buffer, 2048,0 );
If ($ bytes = 0) return;
If (! $ This-> handshake ){
// If there is no handshake, shake hands first to respond
// DoHandShake ($ socket, $ buffer );
Echo "shakeHands \ n ";
} Else {
// If the handshake has been made, directly accept the data and process it
$ Buffer = decode ($ buffer );
// Process ($ socket, $ buffer );
Echo "send file \ n ";
}
}
}
}
}
}
The above code has been debugged by me. It is not a big problem. If you want to test it, you can type php/path/to/demo in the cmd command line. php; of course, the above is just a class. If you want to test it, you have to create a new instance.
Copy codeThe Code is as follows:
$ Ws = new WS ('localhost', 4000 );
The client code can be a little simpler:
Copy codeThe Code is as follows:
Var ws = new WebSocket ("ws: // localhost: 4000 ");
Ws. onopen = function (){
Console. log ("handshake successful ");
};
Ws. onerror = function (){
Console. log ("error ");
};
Run the server code. when the client is connected, we can see:
2. Extract Sec-WebSocket-Key information
Copy codeThe Code is as follows:
Function getKey ($ req ){
$ Key = null;
If (preg_match ("/Sec-WebSocket-Key: (. *) \ r \ n/", $ req, $ match )){
$ Key = $ match [1];
}
Return $ key;
}
This is relatively simple. Regular Expression matching is required. The websocket header must contain the Sec-WebSocket-Key, so we can quickly match the header ~
3. Encrypted Sec-WebSocket-Key
Copy codeThe Code is as follows:
Function encry ($ req ){
$ Key = $ this-> getKey ($ req );
$ Mask = "258eafa5-e914-4710995ca-c5ab0dc85b11 ";
Return base64_encode (sha1 ($ key. '258eafa5-E914-47DA-95CA-C5AB0DC85B11 ', true ));
}
The string encrypted with SHA-1 is encrypted with base64 again. If the encryption algorithm is incorrect, the client will directly report an error during the verification:
4. Respond to Sec-WebSocket-Accept
Copy codeThe Code is as follows:
Function dohandshake ($ socket, $ req ){
// Obtain the encryption key
$ AcceptKey = $ this-> encry ($ req );
$ Upgrade = "HTTP/1.1 101 Switching Protocols \ r \ n ".
"Upgrade: websocket \ r \ n ".
"Connection: Upgrade \ r \ n ".
"Sec-WebSocket-Accept:". $ acceptKey. "\ r \ n ".
"\ R \ n ";
// Write the socket
Socket_write (socket, $ upgrade. chr (0), strlen ($ upgrade. chr (0 )));
// The handshake is successfully marked, and the data received next time is in the data frame format.
$ This-> handshake = true;
}
Note that each request and the corresponding format have an empty line, \ r \ n, which is lost during the test and has been entangled for a long time.
After the client successfully verifies the key, the onopen function is triggered:
5. data frame Processing
Copy codeThe Code is as follows:
// Parse the data frame
Function decode ($ buffer ){
$ Len = $ masks = $ data = $ decoded = null;
$ Len = ord ($ buffer [1]) & 127;
If ($ len = 126 ){
$ Masks = substr ($ buffer, 4, 4 );
$ Data = substr ($ buffer, 8 );
} Else if ($ len = 127 ){
$ Masks = substr ($ buffer, 10, 4 );
$ Data = substr ($ buffer, 14 );
} Else {
$ Masks = substr ($ buffer, 2, 4 );
$ Data = substr ($ buffer, 6 );
}
For ($ index = 0; $ index <strlen ($ data); $ index ++ ){
$ Decoded. = $ data [$ index] ^ $ masks [$ index % 4];
}
Return $ decoded;
}
The encoding problems involved here have already been mentioned in the previous article. I will not go into details here. php has too many character processing functions. I still remember it is not very clear. I have not introduced the decoding program in detail here, directly return the data sent from the client as is, which can be regarded as a chat room mode.
Copy codeThe Code is as follows:
// Process the returned frame information
Function frame ($ s ){
$ A = str_split ($ s, 125 );
If (count ($ a) = 1 ){
Return "\ x81". chr (strlen ($ a [0]). $ a [0];
}
$ Ns = "";
Foreach ($ a as $ o ){
$ Ns. = "\ x81". chr (strlen ($ o). $ o;
}
Return $ ns;
}
// Return data
Function send ($ client, $ msg ){
$ Msg = $ this-> frame ($ msg );
Socket_write ($ client, $ msg, strlen ($ msg ));
}
Client code:
Copy codeThe Code is as follows:
Var ws = new WebSocket ("ws: // localhost: 4000 ");
Ws. onopen = function (){
Console. log ("handshake successful ");
};
Ws. onmessage = function (e ){
Console. log ("message:" + e. data );
};
Ws. onerror = function (){
Console. log ("error ");
};
Ws. send ("Li Jing ");
After the connection, the server returns the data as is:
Ii. Notes
1. websocket version Problems
The client has the Sec-WebSocket-Version: 13 in the handshake request. This is an upgraded Version, which is used by all browsers. In the previous version, data encryption is more troublesome. It sends two keys:
Copy codeThe Code is as follows:
GET/chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Origin: http://www.jb51.net
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Key1: xxxx
Sec-WebSocket-Key2: xxxx
If this version is used (it is old and is no longer in use), you need to obtain it using the following method:
Copy codeThe Code is as follows:
Function encry ($ key1, $ key2, $ l8b) {// Get the numbers preg_match_all ('/([\ d] +)/', $ key1, $ key1_num ); preg_match_all ('/([\ d] +)/', $ key2, $ key2_num );
$ Key1_num = implode ($ key1_num [0]);
$ Key2_num = implode ($ key2_num [0]);
// Count spaces
Preg_match_all ('/([] +)/', $ key1, $ key1_spc );
Preg_match_all ('/([] +)/', $ key2, $ key2_spc );
If ($ key1_spc = 0 | $ key2_spc = 0) {$ this-> log ("Invalid key"); return ;}
// Some math
$ Keydomainsec = pack ("N", $ keydomainnum/$ keydomainspc );
$ Key2_sec = pack ("N", $ key2_num/$ key2_spc );
Return md5 ($ key1_sec. $ key2_sec. $ l8b, 1 );
}
This verification method can only be used without limit! Compared with nodeJs websocket operations:
Copy codeThe Code is as follows:
// Server program
Var crypto = require ('crypto ');
Var WS = '8eafa5-E914-47DA-95CA-C5AB0DC85B11 ';
Require ('net'). createServer (function (o ){
Var key;
O. on ('data', function (e ){
If (! Key ){
// Handshake
Key = e. toString (). match (/Sec-WebSocket-Key: (. +)/) [1];
Key = crypto. createHash ('sha1'). update (key + WS). digest ('base64 ');
O. write ('HTTP/1.1 101 Switching Protocols \ r \ n ');
O. write ('upgrade: websocket \ r \ n ');
O. write ('Connection: Upgrade \ r \ n ');
O. write ('sec-WebSocket-Accept: '+ key +' \ r \ n ');
O. write ('\ r \ n ');
} Else {
Console. log (e );
};
});
}). Listen (8000 );
2. Data Frame Parsing Code
This article does not provide data Frame Parsing Code such as decodeFrame. The format of the data frame is given in the previous article. Parsing is purely physical.