Here I draw a diagram demonstrating the handshake part of a websocket connection between the client and the server, which can be done very easily in node, because the net module provided by node has encapsulated the socket socket, and the developer will use only Consider the interaction of the data without dealing with the establishment of the connection. and PHP does not, from the socket connection, establishment, binding, monitoring, etc., these need our own to operate, so it is necessary to take out to say.
① and ② are actually an HTTP request and response, but we're dealing with a string that's not parsed. Such as:
Copy Code code as follows:
Get/chat http/1.1
Host:server.example.com
Origin:http://www.jb51.com
The request we always see is this way, and when it gets to the server side, we can get this information directly from some code base.
First, in PHP processing WebSocket
The WebSocket connection is initiated by the client, so everything has to go from the client. The first step is to parse the Sec-websocket-key string sent over to the client.
Copy Code code 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
Format of client requests
First PHP to establish a socket connection, listening to the port information.
1. Socket connection Establishment
On the establishment of socket sockets, I believe that many universities have repaired the computer network, the following is a link to establish the process:
Copy Code code as follows:
Create a Socket 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 to node, this place is too cumbersome to handle, and a few lines of code do not establish a connection, except that the code creates what a socket socket must write. Because the processing process is slightly complicated, I write all kinds of processing into a class for easy management and invocation.
Copy Code code as follows:
demo.php
Class WS {
var $master; Client connecting to Server
var $sockets = array (); Socket management in different states
var $handshake = false; To decide whether to shake hands
function __construct ($address, $port) {
Create a Socket 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 to message if it is a handshake to automatically select a host
$write = NULL;
$except = NULL;
Socket_select ($this->sockets, $write, $except, NULL);
foreach ($this->sockets as $socket) {
Client that connects to 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 you don't shake hands, shake hands and respond first.
Dohandshake ($socket, $buffer);
echo "shakehands\n";
} else {
If you have already shook hands, accept the data directly and process
$buffer = decode ($buffer);
Process ($socket, $buffer);
echo "Send file\n";
}
}
}
}
}
}
The above code is after I debug, not too big problem, if you want to test, you can type in the cmd command line php/path/to/demo.php; Of course, it's just a class, and if you want to test it, you have to create a new instance.
Copy Code code as follows:
$ws = new ws (' localhost ', 4000);
Client code can be a little bit simpler:
Copy Code code as follows:
var ws = new WebSocket ("ws://localhost:4000");
Ws.onopen = function () {
Console.log ("handshake success");
};
Ws.onerror = function () {
Console.log ("error");
};
Running the server code, when the client connects, we can see:
2. Extract Sec-websocket-key Information
Copy Code code as follows:
function Getkey ($req) {
$key = null;
if (Preg_match ("/sec-websocket-key: (. *) \r\n/", $req, $match)) {
$key = $match [1];
}
return $key;
}
Here is relatively simple, direct regular matching, websocket information header must contain Sec-websocket-key, so we match up also relatively fast ~
3. Encryption Sec-websocket-key
Copy Code code as follows:
function Encry ($req) {
$key = $this->getkey ($req);
$mask = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
Return Base64_encode (SHA1 ($key. ' 258eafa5-e914-47da-95ca-c5ab0dc85b11 ', true);
}
The SHA-1 encrypted string is then base64 encrypted again. If the encryption algorithm is wrong, the client will make a direct error when carrying out the school check:
4. Answer Sec-websocket-accept
Copy Code code as follows:
function Dohandshake ($socket, $req) {
Get encryption Key
$acceptKey = $this->encry ($req);
$upgrade = "http/1.1 switching protocols\r\n".
"Upgrade:websocket\r\n".
"Connection:upgrade\r\n".
"Sec-websocket-accept:". $acceptKey. "\ r \ n".
"\ r \ n";
Write socket
Socket_write (socket, $upgrade. chr (0), strlen ($upgrade. chr (0)));
The token handshake has been successful and the next time the data is accepted in data frame format
$this->handshake = true;
}
Here must pay attention to each request and the corresponding format, finally have a blank line, that is, \ r \ n, start testing when this thing to lose, tangled up for half a day.
When the client successfully checks the key, the OnOpen function is triggered:
5. Data frame Processing
Copy Code code as follows:
Parsing data Frames
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 coding issues involved here have been mentioned in the previous article, here is not to repeat, PHP on the character processing function too much, also remember is not particularly clear, there is no detailed introduction of the decoding program, directly to send the client data as it is returned, can be regarded as a chat room model bar.
Copy Code code as follows:
return frame Information Processing
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 Code code as follows:
var ws = new WebSocket ("ws://localhost:4000");
Ws.onopen = function () {
Console.log ("handshake success");
};
Ws.onmessage = function (e) {
Console.log ("message:" + e.data);
};
Ws.onerror = function () {
Console.log ("error");
};
Ws.send ("Li Jing");
Send the data after connectivity, the server is returned as is:
Second, pay attention to the problem
1. WebSocket version problem
The client has a sec-websocket-version:13 in the handshake request, and this is an upgraded version, which is now used by browsers. The previous version was more cumbersome in the Data encryption section, and it would send two key:
Copy Code code 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 it is this version (older, no longer in use), you need to get it in the following way
Copy Code code as follows:
function Encry ($key 1, $key 2, $l 8b) {//get The numbers Preg_match_all ('/([\d]+)/', $key 1, $key 1_num); Preg_match_all ('/[ \d]+)/', $key 2, $key 2_num);
$key 1_num = implode ($key 1_num[0]);
$key 2_num = implode ($key 2_num[0]);
Count spaces
Preg_match_all ('/([]+)/', $key 1, $key 1_SPC);
Preg_match_all ('/([]+)/', $key 2, $key 2_SPC);
if ($key 1_spc==0| $key 2_spc==0) {$this->log ("Invalid key");
Some Math
$key 1_sec = Pack ("N", $key 1_num/$key 1_spc);
$key 2_sec = Pack ("N", $key 2_num/$key 2_spc);
return MD5 ($key 1_sec $key 2_sec. $l 8b,1);
}
Only Infinite spit this verification way! Compared to the Nodejs websocket operation mode:
Copy Code code as follows:
Server programs
var crypto = require (' crypto ');
var WS = ' 258eafa5-e914-47da-95ca-c5ab0dc85b11 ';
Require (' net '). Createserver (function (o) {
var key;
O.on (' Data ', function (e) {
if (!key) {
Shake hands
Key = E.tostring (). Match (/sec-websocket-key: (. +)/) [1];
Key = Crypto.createhash (' SHA1 '). Update (key + WS). Digest (' base64 ');
O.write (' http/1.1 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 give the Decodeframe such data frame parsing code, the previous text gives the data frame format, the analysis is purely manual work.