摘要:在服務(wù)器編程中函數(shù)把進(jìn)程變?yōu)橐粋€(gè)服務(wù)器,并指定相應(yīng)的套接字變?yōu)楸粍?dòng)連接其中的能存儲(chǔ)的請(qǐng)求不明的數(shù)目。
PHP部分
[ * info * ] * ] * todo 解釋socket與file號(hào)對(duì)應(yīng) */ private $sockets = []; private $master; public function __construct($host, $port) { try { $this->master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); // 設(shè)置IP和端口重用,在重啟服務(wù)器后能重新使用此端口; socket_set_option($this->master, SOL_SOCKET, SO_REUSEADDR, 1); // 將IP和端口綁定在服務(wù)器socket上; socket_bind($this->master, $host, $port); // listen函數(shù)使用主動(dòng)連接套接口變?yōu)楸贿B接套接口,使得一個(gè)進(jìn)程可以接受其它進(jìn)程的請(qǐng)求,從而成為一個(gè)服務(wù)器進(jìn)程。在TCP服務(wù)器編程中l(wèi)isten函數(shù)把進(jìn)程變?yōu)橐粋€(gè)服務(wù)器,并指定相應(yīng)的套接字變?yōu)楸粍?dòng)連接,其中的能存儲(chǔ)的請(qǐng)求不明的socket數(shù)目。 socket_listen($this->master, self::LISTEN_SOCKET_NUM); } catch (Exception $e) { $err_code = socket_last_error(); $err_msg = socket_strerror($err_code); $this->error([ "error_init_server", $err_code, $err_msg ]); } $this->sockets[0] = ["resource" => $this->master]; $pid = posix_getpid(); $this->debug(["server: {$this->master} started,pid: {$pid}"]); while (true) { try { $this->doServer(); } catch (Exception $e) { $this->error([ "error_do_server", $e->getCode(), $e->getMessage() ]); } } } private function doServer() { $write = $except = NULL; $sockets = array_column($this->sockets, "resource"); $read_num = socket_select($sockets, $write, $except, NULL); // select作為監(jiān)視函數(shù),參數(shù)分別是(監(jiān)視可讀,可寫(xiě),異常,超時(shí)時(shí)間),返回可操作數(shù)目,出錯(cuò)時(shí)返回false; if (false === $read_num) { $this->error([ "error_select", $err_code = socket_last_error(), socket_strerror($err_code) ]); return; } foreach ($sockets as $socket) { // 如果可讀的是服務(wù)器socket,則處理連接邏輯 if ($socket == $this->master) { $client = socket_accept($this->master); // 創(chuàng)建,綁定,監(jiān)聽(tīng)后accept函數(shù)將會(huì)接受socket要來(lái)的連接,一旦有一個(gè)連接成功,將會(huì)返回一個(gè)新的socket資源用以交互,如果是一個(gè)多個(gè)連接的隊(duì)列,只會(huì)處理第一個(gè),如果沒(méi)有連接的話,進(jìn)程將會(huì)被阻塞,直到連接上.如果用set_socket_blocking或socket_set_noblock()設(shè)置了阻塞,會(huì)返回false;返回資源后,將會(huì)持續(xù)等待連接。 if (false === $client) { $this->error([ "err_accept", $err_code = socket_last_error(), socket_strerror($err_code) ]); continue; } else { self::connect($client); continue; } } else { // 如果可讀的是其他已連接socket,則讀取其數(shù)據(jù),并處理應(yīng)答邏輯 $bytes = @socket_recv($socket, $buffer, 2048, 0); if ($bytes < 9) { $recv_msg = $this->disconnect($socket); } else { if (!$this->sockets[(int)$socket]["handshake"]) { self::handShake($socket, $buffer); continue; } else { $recv_msg = self::parse($buffer); } } array_unshift($recv_msg, "receive_msg"); $msg = self::dealMsg($socket, $recv_msg); $this->broadcast($msg); } } } /** * 將socket添加到已連接列表,但握手狀態(tài)留空; * * @param $socket */ public function connect($socket) { socket_getpeername($socket, $ip, $port); $socket_info = [ "resource" => $socket, "uname" => "", "handshake" => false, "ip" => $ip, "port" => $port, ]; $this->sockets[(int)$socket] = $socket_info; $this->debug(array_merge(["socket_connect"], $socket_info)); } /** * 客戶端關(guān)閉連接 * * @param $socket * * @return array */ private function disconnect($socket) { $recv_msg = [ "type" => "logout", "content" => $this->sockets[(int)$socket]["uname"], ]; unset($this->sockets[(int)$socket]); return $recv_msg; } /** * 用公共握手算法握手 * * @param $socket * @param $buffer * * @return bool */ public function handShake($socket, $buffer) { // 獲取到客戶端的升級(jí)密匙 $line_with_key = substr($buffer, strpos($buffer, "Sec-WebSocket-Key:") + 18); $key = trim(substr($line_with_key, 0, strpos($line_with_key, " "))); // 生成升級(jí)密匙,并拼接websocket升級(jí)頭 $upgrade_key = base64_encode(sha1($key . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true));// 升級(jí)key的算法 $upgrade_message = "HTTP/1.1 101 Switching Protocols "; $upgrade_message .= "Upgrade: websocket "; $upgrade_message .= "Sec-WebSocket-Version: 13 "; $upgrade_message .= "Connection: Upgrade "; $upgrade_message .= "Sec-WebSocket-Accept:" . $upgrade_key . " "; socket_write($socket, $upgrade_message, strlen($upgrade_message));// 向socket里寫(xiě)入升級(jí)信息 $this->sockets[(int)$socket]["handshake"] = true; socket_getpeername($socket, $ip, $port); $this->debug([ "hand_shake", $socket, $ip, $port ]); // 向客戶端發(fā)送握手成功消息,以觸發(fā)客戶端發(fā)送用戶名動(dòng)作; $msg = [ "type" => "handshake", "content" => "done", ]; $msg = $this->build(json_encode($msg)); socket_write($socket, $msg, strlen($msg)); return true; } /** * 解析數(shù)據(jù) * * @param $buffer * * @return bool|string */ private function parse($buffer) { $decoded = ""; $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 json_decode($decoded, true); } /** * 將普通信息組裝成websocket數(shù)據(jù)幀 * * @param $msg * * @return string */ private function build($msg) { $frame = []; $frame[0] = "81"; $len = strlen($msg); if ($len < 126) { $frame[1] = $len < 16 ? "0" . dechex($len) : dechex($len); } else if ($len < 65025) { $s = dechex($len); $frame[1] = "7e" . str_repeat("0", 4 - strlen($s)) . $s; } else { $s = dechex($len); $frame[1] = "7f" . str_repeat("0", 16 - strlen($s)) . $s; } $data = ""; $l = strlen($msg); for ($i = 0; $i < $l; $i++) { $data .= dechex(ord($msg{$i})); } $frame[2] = $data; $data = implode("", $frame); return pack("H*", $data); } /** * 拼裝信息 * * @param $socket * @param $recv_msg * [ * "type"=>user/login * "content"=>content * ] * * @return string */ private function dealMsg($socket, $recv_msg) { $msg_type = $recv_msg["type"]; $msg_content = $recv_msg["content"]; $response = []; switch ($msg_type) { case "login": $this->sockets[(int)$socket]["uname"] = $msg_content; // 取得最新的名字記錄 $user_list = array_column($this->sockets, "uname"); $response["type"] = "login"; $response["content"] = $msg_content; $response["user_list"] = $user_list; break; case "logout": $user_list = array_column($this->sockets, "uname"); $response["type"] = "logout"; $response["content"] = $msg_content; $response["user_list"] = $user_list; break; case "user": $uname = $this->sockets[(int)$socket]["uname"]; $response["type"] = "user"; $response["from"] = $uname; $response["content"] = $msg_content; break; } return $this->build(json_encode($response)); } /** * 廣播消息 * * @param $data */ private function broadcast($data) { foreach ($this->sockets as $socket) { if ($socket["resource"] == $this->master) { continue; } socket_write($socket["resource"], $data, strlen($data)); } } /** * 記錄debug信息 * * @param array $info */ private function debug(array $info) { $time = date("Y-m-d H:i:s"); array_unshift($info, $time); $info = array_map("json_encode", $info); file_put_contents(self::LOG_PATH . "websocket_debug.log", implode(" | ", $info) . " ", FILE_APPEND); } /** * 記錄錯(cuò)誤信息 * * @param array $info */ private function error(array $info) { $time = date("Y-m-d H:i:s"); array_unshift($info, $time); $info = array_map("json_encode", $info); file_put_contents(self::LOG_PATH . "websocket_error.log", implode(" | ", $info) . " ", FILE_APPEND); } } $ws = new WebSocket("127.0.0.1", "8080");
HTML部分
websocket聊天室
當(dāng)前在線:0
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/108327.html
摘要:在服務(wù)器編程中函數(shù)把進(jìn)程變?yōu)橐粋€(gè)服務(wù)器,并指定相應(yīng)的套接字變?yōu)楸粍?dòng)連接其中的能存儲(chǔ)的請(qǐng)求不明的數(shù)目。 PHP部分
摘要:在服務(wù)器編程中函數(shù)把進(jìn)程變?yōu)橐粋€(gè)服務(wù)器,并指定相應(yīng)的套接字變?yōu)楸粍?dòng)連接其中的能存儲(chǔ)的請(qǐng)求不明的數(shù)目。 PHP部分
摘要:早期的輪詢是通過(guò)不斷自動(dòng)刷新頁(yè)面而實(shí)現(xiàn)的。長(zhǎng)輪詢的另一個(gè)問(wèn)題是缺乏標(biāo)準(zhǔn)實(shí)現(xiàn)。服務(wù)器端接到這個(gè)請(qǐng)求后作出回應(yīng)并不斷更新連接狀態(tài)以保證客戶端和服務(wù)器端的連接不過(guò)期。協(xié)議解析協(xié)議包含兩部分一部分是握手,一部分是數(shù)據(jù)傳輸。 Websocket是什么? Websocket是一個(gè)因?yàn)閼?yīng)用場(chǎng)景越來(lái)越復(fù)雜而提出的,針對(duì)瀏覽器和web服務(wù)器之間雙向持續(xù)通信而設(shè)計(jì),而且優(yōu)雅地兼容HTTP的協(xié)議(我猜想:同...
摘要:是一個(gè)請(qǐng)求對(duì)象,包含了客戶端發(fā)來(lái)的握手請(qǐng)求信息事件函數(shù)中可以調(diào)用向客戶端發(fā)送數(shù)據(jù)或者調(diào)用關(guān)閉連接事件回調(diào)是可選的當(dāng)服務(wù)器收到來(lái)自客戶端的數(shù)據(jù)幀時(shí)會(huì)回調(diào)此函數(shù)。 前言:了解概念之后就應(yīng)該練練手啦,不然就是巨嬰 有收獲的話請(qǐng)加顆小星星,沒(méi)有收獲的話可以 反對(duì) 沒(méi)有幫助 舉報(bào)三連 代碼倉(cāng)庫(kù) 實(shí)戰(zhàn)swoole【聊天室】 在線體驗(yàn) 準(zhǔn)備工作 需要先看初識(shí)swoole【上】,了解基本的服務(wù)端...
摘要:國(guó)際慣例,先上維基百科的解釋。維基百科上面是維基百科對(duì)的解釋,別問(wèn)我如何解釋上面這段話,因?yàn)槲乙矝](méi)看懂,那么下面我用人話解釋一下吧僅僅是我的理解是一個(gè)協(xié)議,可以簡(jiǎn)單看成是協(xié)議的一個(gè)補(bǔ)充協(xié)議,借助協(xié)議的基礎(chǔ)完成服務(wù)器主動(dòng)與客戶端實(shí)時(shí)傳輸數(shù)據(jù)。 現(xiàn)在,很多網(wǎng)站為了實(shí)現(xiàn)推送技術(shù),所用的技術(shù)都是輪詢。輪詢是在特定的的時(shí)間間隔(如每1秒),由瀏覽器對(duì)服務(wù)器發(fā)出HTTP request,然后由服務(wù)...
閱讀 1686·2021-11-19 09:40
閱讀 2939·2021-09-24 10:27
閱讀 3227·2021-09-02 15:15
閱讀 1888·2019-08-30 15:54
閱讀 1213·2019-08-30 15:54
閱讀 1377·2019-08-30 13:12
閱讀 642·2019-08-28 18:05
閱讀 2808·2019-08-27 10:53