1, Brief introduction of socket protocol
What is WebSocket and what are its advantages
WebSocket is a persistent protocol, which is relative to http non persistent. Application layer protocol
For example, the life cycle of http1.0 is defined by request, that is, a request and a response. For HTTP, this session between client and server ends here. In http1.1, there is a slight improvement, that is, adding keep alive, that is, multiple request and multiple response acceptance operations can be performed in one HTTP connection. However, in real-time communication, there is not much effect. HTTP can only be requested by the client, and the server can return information, that is, the server can not actively push information to the client, and can not meet the requirements of real-time communication. WebSocket can make persistent connection, that is, the client only needs to shake hands once, and then data communication will be sustainable after success. It is worth noting that WebSocket can realize full duplex communication between client and server, that is, when the server has data update, it can be actively pushed to the client.
2, Introduce the socket connection principle between client and server
1. The following is a demonstration of handshake when establishing WebSocket connection between client and server
2. The session content of handshake when client and server establish socket, i.e. request and response
a. Information requested by the client to the server when establishing WebSocket
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket / / tells the server to send the WebSocket protocol
Connection: Upgrade
SEC WebSocket key: x3jjhmbdl1ezlkh9gbhxdw = = / / is a value of Base64 encode generated randomly by the browser to verify whether the data returned by the server is WebSocket assistant
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
b. After the server obtains the information requested by the client, it processes and returns the data according to the WebSocket protocol, in which the SEC WebSocket key needs to be encrypted
HTTP/1.1 101 Switching Protocols
Upgrade: websocket / / is still fixed. Tell the client to upgrade the Websocket protocol instead of Mozilla socket, lurnarsocket or shitsocket
Connection: Upgrade
SEC WebSocket accept: hsmrc0smlyukagmm5oppg2hagwk = / / this is the SEC WebSocket key that has been confirmed by the server and encrypted. That is, the client requires to establish the WebSocket authentication certificate
Sec-WebSocket-Protocol: chat
3. Schematic diagram of socket connection:
3, How to build websocket in PHP
SocketService.php:
<?php class SocketService { private $address = '0.0.0.0'; private $port = 8083; private $_sockets; public function __construct($address = '', $port='') { if(!empty($address)){ $this->address = $address; } if(!empty($port)) { $this->port = $port; } } public function service(){ //Get the tcp protocol number. $tcp = getprotobyname("tcp"); $sock = socket_create(AF_INET, SOCK_STREAM, $tcp); socket_set_option($sock, SOL_SOCKET, SO_REUSEADDR, 1); if($sock < 0) { throw new Exception("failed to create socket: ".socket_strerror($sock)."\n"); } socket_bind($sock, $this->address, $this->port); socket_listen($sock, $this->port); echo "listen on $this->address $this->port ... \n"; $this->_sockets = $sock; } public function run(){ $this->service(); $clients[] = $this->_sockets; while (true){ $changes = $clients; $write = NULL; $except = NULL; //When select is waiting, a of the two clients sends the data first, then socket [Select] will keep the socket of a in $changes and run down, and the socket of the other client will be discarded, so when looping again, it will only listen to A. This can add all the linked client sockets into $changes again in the new loop, so this logic error of this program can be avoided /** socket_select It's blocking. Only when there is a data request can it be processed. Otherwise, it will be blocked all the time * Here $changes will read to the currently active connection * For example, the data before socket select is as follows (describe the resource ID of socket): * $socket = Resource id #4 * $changes = Array * ( * [0] => Resource id #5 //Client 1 * [1] => Resource id #4 //server socket resource of bound port * ) * After calling socket select, there are two situations: * Case 1: if it is a new client 2 connection, $changes = array ([1] = > resource ID × 4), which is now used to receive the new client 2 connection * Case 2: if client 1 (resource ID ා) sends a message, then $changes = array ([1] = > resource ID ා), and the user receives the data of client 1 * * As can be seen from the above description, socket [Select] has two functions, which also realizes IO multiplexing * 1,Here comes the new client. Introduce the new connection through resource ID 4, as shown in case 1 * 2,If there is a connection to send data, switch to the current connection in real time and receive data, as in case 2*/ socket_select($changes, $write, $except, NULL); foreach ($changes as $key => $_sock){ if($this->_sockets == $_sock){ //Judge whether it is a new socket if(($newClient = socket_accept($_sock)) === false){ die('failed to accept socket: '.socket_strerror($_sock)."\n"); } $line = trim(socket_read($newClient, 1024)); if($line === false){ socket_shutdown($newClient); socket_close($newClient); continue; } $this->handshaking($newClient, $line); //Get client ip socket_getpeername ($newClient, $ip); $clients[$ip] = $newClient; echo "Client ip:{$ip} \n"; echo "Client msg:{$line} \n"; } else { $byte = socket_recv($_sock, $buffer, 2048, 0); if($byte < 7) continue; $msg = $this->message($buffer); //Business code here echo "{$key} clinet msg:",$msg,"\n"; fwrite(STDOUT, 'Please input a argument:'); $response = trim(fgets(STDIN)); $this->send($_sock, $response); echo "{$key} response to Client:".$response,"\n"; } } } } /** * handshake processing * @param $newClient socket * @return int Information received */ public function handshaking($newClient, $line){ $headers = array(); $lines = preg_split("/\r\n/", $line); foreach($lines as $line) { $line = rtrim($line); if(preg_match('/^(\S+): (.*)$/', $line, $matches)) { $headers[$matches[1]] = $matches[2]; } } $secKey = $headers['Sec-WebSocket-Key']; $secAccept = base64_encode(pack('H*', sha1($secKey . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'))); $upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" . "Upgrade: websocket\r\n" . "Connection: Upgrade\r\n" . "WebSocket-Origin: $this->address\r\n" . "WebSocket-Location: ws://$this->address:$this->port/websocket/websocket\r\n". "Sec-WebSocket-Accept:$secAccept\r\n\r\n"; return socket_write($newClient, $upgrade, strlen($upgrade)); } /** * Parsing received data * @param $buffer * @return null|string */ public function message($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; } /** * send data * @param $newClinet New socket * @param $msg Data to send * @return int|string */ public function send($newClinet, $msg){ $msg = $this->frame($msg); socket_write($newClinet, $msg, strlen($msg)); } public 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; } /** * Close socket */ public function close(){ return socket_close($this->_sockets); } } $sock = new SocketService(); $sock->run();
web.html:
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width,initial-scale=1, maximum-scale=1, user-scalable=no"> <title>websocket</title> </head> <body> <input id="text" value=""> <input type="submit" value="send" onclick="start()"> <input type="submit" value="close" onclick="close()"> <div id="msg"></div> <script> /** webSocket.readyState 0: Unconnected 1: Connection successful, communicable 2: Shutting down 3: Connection is closed or cannot be opened */ //Create a webSocket instance var webSocket = new WebSocket("ws://192.168.31.152:8083"); webSocket.onerror = function (event){ onError(event); }; // Open websocket webSocket.onopen = function (event){ onOpen(event); }; //Monitoring messages webSocket.onmessage = function (event){ onMessage(event); }; webSocket.onclose = function (event){ //Triggered after the server is shut down onClose(event); } //Turn off listening to websocket function onError(event){ document.getElementById("msg").innerHTML = "<p>close</p>"; console.log("error"+event.data); }; function onOpen(event){ console.log("open:"+sockState()); document.getElementById("msg").innerHTML = "<p>Connect to Service</p>"; }; function onMessage(event){ console.log("onMessage"); document.getElementById("msg").innerHTML += "<p>response:"+event.data+"</p>" }; function onClose(event){ document.getElementById("msg").innerHTML = "<p>close</p>"; console.log("close:"+sockState()); webSocket.close(); } function sockState(){ var status = ['Unconnected','Connection successful, communicable','Shutting down','Connection is closed or cannot be opened']; return status[webSocket.readyState]; } function start(event){ console.log(webSocket); var msg = document.getElementById('text').value; document.getElementById('text').value = ''; console.log("send:"+sockState()); console.log("msg="+msg); webSocket.send("msg="+msg); document.getElementById("msg").innerHTML += "<p>request"+msg+"</p>" }; function close(event){ webSocket.close(); } </script> </body> </html>