這只是一種技術
<?php $host = "127.0.0.1"; // 指定監聽的端口,注意該端口不能與現有應用的端口沖突 $port = '9505'; $null = null; // 創建Socket。AF_INET:代表通信時使用IPv4協議;SOCK_STREAM:代表傳輸的數據是二進制流數據;SOL_TCP:代表底層使用的協議是TCP $socket = socket_create ( AF_INET, SOCK_STREAM, SOL_TCP ); // 指定Socket相應的屬性。SOL_SOCKET:設定協議的等級;SO_REUSEADDR:設置端口釋放之后可以立即被使用 socket_set_option ( $socket, SOL_SOCKET, SO_REUSEADDR, 1 ); // 綁定端口 socket_bind ( $socket, 0, $port ); // 監聽端口 socket_listen ( $socket ); // 聲明一個數組,用於存放所有的客戶端連接 $clients = array ( $socket ); while ( true ) { $changed_socket = $clients; // 在當前數組中獲取活躍的Socket連接,即當前正在發送請求的連接或正在傳輸數據的連接等 socket_select ( $changed_socket, $null, $null, 0, 10 ); // 判斷當前的Socket是否為活躍的Socket,如果是,說明客戶端在請求連接 if (in_array ( $socket, $changed_socket )) { echo "client connecting"; // 接受連接 $socket_new = socket_accept ( $socket ); $clients [] = $socket_new; // 發送握手信息 $header = socket_read ( $socket_new, 1024 ); perform_handshaking ( $header, $socket_new, $host, $port ); // 在連接成功后,當前Socket要從活躍Socket列表中刪除,否則會陷入死循環 $key = array_search ( $socket, $changed_socket ); unset ( $changed_socket [$key] ); } else { // 不是新連接,是客戶端在發送數據 // 服務器開始讀取客戶端發送的數據 foreach ( $changed_socket as $v ) { while ( socket_recv ( $v, $buf, 1024, 0 ) >= 1 ) { // 解包數據 $received_text = unmask ( $buf ); // 將數據解包后轉成JSON對象 $msgJson = json_decode ( $received_text ); // 讀取數據 $namer = $msgJson->namer; $content = $msgJson->content; // 將數據進行編碼 $message = mask ( json_encode ( [ 'namer' => $namer, 'content' => $content, 'type' => 'usermsg' ] ) ); // 廣播編碼后的數據。服務器進行廣播的消息會觸發客戶端的onmessage事件 send_message ( $message ); // 跳出foreach循環 break 2; } // 刪除已經關閉的Socket $buf = @socket_read ( $v, 1024, PHP_NORMAL_READ ); if ($buf === false) { $key = array_search ( $v, $clients ); socket_getpeername ( $v, $ip ); unset ( $clients [$key] ); $msg = mask ( json_encode ( [ 'type' => 'system', 'content' => $ip . "已經下線。" ] ) ); send_message ( $msg ); } } } } // 發送消息的方法 function send_message($msg) { global $clients; foreach ( $clients as $changed_socket ) { @socket_write ( $changed_socket, $msg, strlen ( $msg ) ); } return true; } // 以下3個函數,我們只需了解總體結構即可。在有需要時可以直接使用 // 解碼數據。服務器解碼客戶端發送過來的數據 function unmask($text) { $length = ord ( $text [1] ) & 127; if ($length == 126) { $masks = substr ( $text, 4, 4 ); $data = substr ( $text, 8 ); } elseif ($length == 127) { $masks = substr ( $text, 10, 4 ); $data = substr ( $text, 14 ); } else { $masks = substr ( $text, 2, 4 ); $data = substr ( $text, 6 ); } $text = ""; for($i = 0; $i < strlen ( $data ); ++ $i) { $text .= $data [$i] ^ $masks [$i % 4]; } return $text; } // 編碼數據。在服務器向客戶端發送數據時需要將數據打包 function mask($text) { $b1 = 0x80 | (0x1 & 0x0f); $length = strlen ( $text ); if ($length <= 125) { $header = pack ( 'CC', $b1, $length ); } elseif ($length > 125 && $length < 65536) { $header = pack ( 'CCn', $b1, 126, $length ); } elseif ($length >= 65536) { $header = pack ( 'CCNN', $b1, 127, $length ); } return $header . $text; } // 握手的邏輯。客戶端與服務端相互識別的過程 function perform_handshaking($receved_header, $client_conn, $host, $port) { $headers = array (); $lines = preg_split ( "/\r\n/", $receved_header ); foreach ( $lines as $line ) { $line = chop ( $line ); if (preg_match ( '/\A(\S+): (.*)\z/', $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: $host\r\n" . "Sec-WebSocket-Accept:$secAccept\r\n\r\n"; socket_write ( $client_conn, $upgrade, strlen ( $upgrade ) ); }