websocket初體驗(能傳文字和圖片)


這兩天花時間看了一下websocket,自己也跟着動手做了一個畢竟簡單的實現 記錄一下:

websocket分為 客戶端 和 服務端 兩部分  

客戶端代碼 

                var ws = new WebSocket('ws://127.0.0.1:7777'); // 訪問的是本地的服務器

		function sendMsg() {
			let _file = document.querySelector('input[type=file]').files[0];
			let text = document.getElementById('txtinput').value;
			
			if (ws.readyState == 1) {
				// 處理圖片為base64格式
				if (_file) {
					let reader = new FileReader();
					reader.readAsDataURL(_file); // 轉化為二進制流
					reader.onload = () => {
						ws.send(reader.result);
					}
				} else {
					ws.send(text);
				}
			}
		}

		// 連接服務器
		ws.onopen = function (e) {
			console.log('connect');
		}

		// 接收服務器的消息
		ws.onmessage = function(e) {
			if (e.data.indexOf('base64,') > 0) {
				let img = new Image();
				img.src = e.data;
				document.getElementById('area').appendChild(img);
			} else {
				let text = document.createElement('div');
				text.innerHTML = e.data;

				document.getElementById('area').appendChild(text);
			}
		}

		ws.onclose = e => {
			console.log('lost connect');
			ws = 0;
		}
		
		ws.onerror = e => {
			console.log('error');
			console.log(e);
		}        

  客戶端第一次連接服務端的時候會有一個握手的步驟 客服端發送WebSocket-Key給服務端,服務端拿到之后 又加密返給客戶端 雙方都通過后 websocket連接建立成功

 

 

服務端代碼(PHP實現)

class SocketController
{
    public $master;
    public $sockets;
    private $len = 1024;

    public function __construct($ip, $port)
    {
        // 創建一個IPV4的套接字
        $this->master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        socket_set_option($this->master, SOL_SOCKET, SO_REUSEADDR, 1); // 設置接收所有數據
        // 綁定IP 端口
        socket_bind($this->master, $ip, $port);
        //開啟監聽
        socket_listen($this->master);
//        socket_set_nonblock($this->master);
        $this->sockets['master'] = $this->master;
        echo 'socket create' . PHP_EOL;
    }



    // 接收消息
    public function run()
    {
        while (true)
        {
            $changes = $this->sockets;
            $write = $exception = [];
        // 沒處理異常的情況 關於第四個參數 null 表示等待客戶端發消息或有新連接再往下走 會阻塞在這里 非null的情況沒做嘗試 socket_select($changes, $write, $exception, null); foreach ($changes as $k => $master) { // 說明有新的客戶端進來 if ($master == $this->master) { $client = socket_accept($master); //獲取客戶端發送內容 $buffer = trim(socket_read($client,1024)); $this->hand($client, $buffer); // 握手 $key = uniqid(); echo 'new connect ' . $key . PHP_EOL; $this->sockets[$key] = $client; } else // 說明客戶端發了信息過來 { $content = ''; $contentLength = 0; echo 'begin get content ' . $k . PHP_EOL; // 用do while 是因為socket_recv 會阻塞程序 消息接收完了也卡在這不往下走 do { $len = socket_recv($master, $subContent, $this->len, 0); $content .= $subContent; $contentLength += $len; }while($len == $this->len); if ($contentLength > 0) { // 把消息 給到其他連接 $this->sendMsg($content); } else { $this->unlinkSocket($k); // 關閉socket連接 } } } } } // 發送消息 public function sendMsg($content) { $fixedContent = self::encode($content); foreach ($this->sockets as $k => $socket) { if ($k == 'master') continue; socket_write($socket, $fixedContent, strlen($fixedContent)); } echo 'send msg' . PHP_EOL; } // 關閉套接字 public function unlinkSocket($connect) { echo 'lost connect' . $connect . PHP_EOL; @socket_shutdown($this->sockets[$connect]); socket_close($this->sockets[$connect]); unset($this->sockets[$connect]); } // 握手 public function hand($client, $buffer) { $buf = substr($buffer, strpos($buffer,'Sec-WebSocket-Key:') + 18); $key = trim(substr($buf, 0, strpos($buf, "\r\n"))); $newKey = base64_encode(sha1($key . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true)); //按照協議組合信息進行返回 $newMessage = "HTTP/1.1 101 Switching Protocols\r\n"; $newMessage .= "Upgrade: websocket\r\n"; $newMessage .= "Sec-WebSocket-Version: 13\r\n"; $newMessage .= "Connection: Upgrade\r\n"; $newMessage .= "Sec-WebSocket-Accept: " . $newKey . "\r\n\r\n"; socket_write($client, $newMessage, strlen($newMessage)); } /** * https://www.cnblogs.com/zhangmingda/p/12678630.html 很好的解釋了$masks $data為什么這樣取值 * @param $buffer * @return string */ public function encode($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]; } // end // 這段的意思是 把數據分割成125個字節的長度大小 大於125個字節的 就分段返回 // $a = str_split($decoded, 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; // end // 這個就不分段了 $frame = array(); $frame[0] = '81'; $len = strlen($decoded); if ($len < 126) { $frame[1] = $len < 16 ? '0' . dechex($len) : dechex($len); } elseif ($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; } $msg = ''; for ($i = 0; $i < $len; $i++) { $msg .= dechex(ord($decoded{$i})); } $frame[2] = $msg; $data = implode('', $frame); return pack("H*", $data); } } $sc = new SocketController('127.0.0.1', 7777); $sc->run();

  剛寫完服務端代碼的時候並沒有發送過來的數據做處理,以為是發送過來一個 ‘hello world’ 服務端拿到的就應該是一個明文的字符串 一眼就能看到懂那種,后來打印了之后才發現事情並沒有這么簡單,看了  https://www.cnblogs.com/zhangmingda/p/12678630.html  這個老哥的文章才知道數據在這個過程中是怎么傳遞的 牽扯到位運算及字符操作函數 對這方面也了解不深就不贅述了

 

上面已經基本上算是實現了聊天室的功能


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM