1 <?php 2 $demo = new ws('192.168.90.47',12345); 3 $demo->run(); 4 5 6 class ws 7 { 8 //当前服务端主连接 9 private $currentFirstSocket; 10 //存放客户端socket连接 11 private $socketList = array(); 12 //存放自定义的客户端连接 13 private $clientList = array(); 14 15 16 public function __construct($address, $port) 17 { 18 $this->currentFirstSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); 19 socket_set_option($this->currentFirstSocket, SOL_SOCKET, SO_REUSEADDR, 1); 20 socket_bind($this->currentFirstSocket, $address, $port); 21 socket_listen($this->currentFirstSocket,2); 22 23 $this->socketList[] = $this->currentFirstSocket; 24 self::notice('Server Started ...'); 25 self::notice('Listening on : ' . $address . ' port ' . $port); 26 } 27 28 29 public function run() 30 { 31 while (TRUE) { 32 $connect = $this->socketList; 33 $write = NULL; 34 $except = NULL; 35 self::notice(count($this->socketList) . " wait to connect ...."); 36 socket_select($connect, $write, $except, NULL); 37 38 foreach ($connect as $currentConnect) { 39 self::notice($currentConnect . " into connect"); 40 41 //第一次连接 42 if ($currentConnect === $this->currentFirstSocket) { 43 $connectSuccessHandle = socket_accept($this->currentFirstSocket); 44 $this->socketList[] = $connectSuccessHandle; 45 $this->clientList[] = array( 46 "handshake" => false, 47 "clientHandle" => $connectSuccessHandle 48 ); 49 self::notice($connectSuccessHandle. " new connect success ..."); 50 } else { 51 $currentConnectKey = $this->searchSocketByHandle($currentConnect); 52 //第二次接收到的是客户端发送的websocket头协议 53 $len = @socket_recv($currentConnect, $buffer, 2048, 0); 54 self::notice("len ".$len); 55 if ($len < 10) { //如果接收到的信息长度小于10当作客户端退出 56 $this->socketExit($currentConnectKey); 57 self::notice($currentConnect . " exit" . " have:" . count($this->socketList)); 58 break; 59 } 60 //检查是否握手,去握手 61 if ($this->clientList[$currentConnectKey]['handshake'] === false) { 62 self::notice($currentConnect." handshake success..."); 63 $this->handshake($currentConnectKey, $buffer); 64 } else { 65 //正式连接,解包客户端的信息 66 $buffer = $this->unmask($buffer); 67 self::notice($currentConnect." send to server..."); 68 69 $this->send($buffer); 70 } 71 } 72 } 73 } 74 } 75 76 /* 77 * 握手 78 * */ 79 private function handshake($currentConnectKey, $buffer) 80 { 81 $buf = substr($buffer, strpos($buffer, 'Sec-WebSocket-Key:') + 18); 82 $key = trim(substr($buf, 0, strpos($buf, " "))); 83 $new_key = base64_encode(sha1($key . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true)); 84 $new_message = "HTTP/1.1 101 Switching Protocols "; 85 $new_message .= "Upgrade: websocket "; 86 $new_message .= "Sec-WebSocket-Version: 13 "; 87 $new_message .= "Connection: Upgrade "; 88 $new_message .= "Sec-WebSocket-Accept: " . $new_key . " "; 89 90 socket_write($this->clientList[$currentConnectKey]['clientHandle'], $new_message, strlen($new_message)); 91 $this->clientList[$currentConnectKey]['handshake'] = true; 92 } 93 94 /* 95 * 客户端退出处理 96 * */ 97 private function socketExit($currentConnectKey) 98 { 99 $currentSocket = $this->clientList[$currentConnectKey]['clientHandle']; 100 socket_shutdown($currentSocket); 101 socket_close($currentSocket); 102 foreach ($this->socketList as $key => $val) { 103 if ($val === $currentSocket) { 104 self::notice("delete " . $this->socketList[$key]); 105 unset($this->socketList[$key]); 106 } 107 } 108 unset($this->clientList[$currentConnectKey]); 109 } 110 111 /* 112 * 发送信息给所有客户端 113 * */ 114 private function send($sendMsg) 115 { 116 $sendMsg = $this->mask($sendMsg); 117 foreach ($this->clientList as $val) { 118 self::notice("write to " . $val['clientHandle'] . " " . $sendMsg); 119 socket_write($val['clientHandle'], $sendMsg, strlen($sendMsg)); 120 } 121 } 122 123 /* 124 * 解包客户端发送的信息 125 * */ 126 function unmask($text) 127 { 128 $length = ord($text[1]) & 127; 129 if ($length == 126) { 130 $masks = substr($text, 4, 4); 131 $data = substr($text, 8); 132 } elseif ($length == 127) { 133 $masks = substr($text, 10, 4); 134 $data = substr($text, 14); 135 } else { 136 $masks = substr($text, 2, 4); 137 $data = substr($text, 6); 138 } 139 $text = ""; 140 for ($i = 0; $i < strlen($data); ++$i) { 141 $text .= $data[$i] ^ $masks[$i % 4]; 142 } 143 return $text; 144 } 145 /* 146 * 封包信息发送给客户端 147 * */ 148 function mask($text) 149 { 150 $b1 = 0x80 | (0x1 & 0x0f); 151 $length = strlen($text); 152 153 if ($length <= 125) 154 $header = pack('CC', $b1, $length); 155 elseif ($length > 125 && $length < 65536) 156 $header = pack('CCn', $b1, 126, $length); 157 elseif ($length >= 65536) 158 $header = pack('CCNN', $b1, 127, $length); 159 return $header . $text; 160 } 161 162 private function searchSocketByHandle($handle) 163 { 164 foreach ($this->clientList as $key => $value) { 165 if ($value['clientHandle'] === $handle) { 166 return $key; 167 } 168 } 169 return false; 170 } 171 172 private static function notice($message, $num = 1) 173 { 174 echo date("H:i:s") . " " . $message; 175 echo str_repeat(" ", $num); 176 } 177 }
客户端
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>client</title> <script src="bootstrap/js/jq.js"></script> <script> var socket; $(document).ready(function(e) { connect(); }); function connect(){ var url='ws://192.168.174.132:12345'; socket = new WebSocket(url); socket.onopen=function(){ if(socket.readyState==1){ socket.send("hello this is html"); }else{ console.log("login fail"); } } socket.onmessage=function(msg){ console.log(msg); } socket.onclose=function(){ console.log("close"); } } function sendMsg() { var contetent = $("#test").val(); console.log("send: "+contetent); socket.send(contetent); } </script> </head> <body> <input type="input" id="test"> <input type="button" onclick="sendMsg()" value="send"> </body> </html>