• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

php socket:websocket

武飞扬头像
古月的博客
帮助1

websocket基于socket,区别是需要握手,并将协议提升为websocket

一、socket_create方式

server端

<?php
error_reporting(E_ALL ^ E_NOTICE);

/* Allow the script to hang around waiting for connections. */
set_time_limit(0);

/* Turn on implicit output flushing so we see what we're getting
 * as it comes in. */
ob_implicit_flush();// 直接输出到浏览器

$address = '192.168.80.3';
const MAX_LISTEN_NUM = 200;
$port = 8012;
$socketPool = [];
if (($hostSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) === false) {
    echo "socket_create() failed: reason: " . socket_strerror(socket_last_error()) . "\n";
}

// 设置TIME_WAIT状态socket可以复用
socket_set_option($hostSocket, SOL_SOCKET, SO_REUSEADDR,1); 

// 强制关闭socket开关:l_linger=0开启,1平滑关闭
$linger = array ('l_linger' => 0, 'l_onoff' => 1);
socket_set_option($hostSocket, SOL_SOCKET, SO_LINGER, $linger);

if (socket_bind($hostSocket, $address, $port) === false) {
    echo "socket_bind() failed: reason: " . socket_strerror(socket_last_error($hostSocket)) . "\n";
}

if (socket_listen($hostSocket, 200) === false) {
    echo "socket_listen() failed: reason: " . socket_strerror(socket_last_error($hostSocket)) . "\n";
}

// 把所有socket存储在$sockets

$socketPool[] = ['source'=>$hostSocket];

try {
	while(true){
		$sockets = array_column($socketPool,'source'); // 拷贝clients
		$write = null; // 可读socket
		$except = null; // 异常socket
		$readNum = socket_select($sockets,$write,$except,null);
		
		foreach ($sockets as $socket) {
			if ($socket == $hostSocket) {
				// 处理连接情况
				$client = socket_accept($hostSocket);
				echo 'client:'.$client;
				echo 'client:'.(int)$client;
				if ($client < 0) {
					echo "socket_accept() failed: reason: " . socket_strerror(socket_last_error($hostSocket)) . "\n";
				} else {
					$socketPool[(int)$client] = ['source'=>$client,'handshake'=>false];
				}
			} else {
				$len = socket_recv($socket, $buff, 2048,0);
				echo 'len:'.$len;
				echo 'buff:'.$buff;
				
				$recvMsg = decode($buff);
				echo 'recvMsg:'.$recvMsg;
				
				// buff=null 证明socket已经关闭
				if ($buff == null) {
					socket_close($socket);
					unset($socketPool[(int)$socket]);
					break;
				}else if ($socketPool[(int)$socket]['handshake']) {
					
					// 发送消息 
					$answMsg = frame(json_encode(['code'=>0,'msg'=>$recvMsg,'data'=>[]]));
					
					$host = '';
					$port = '';
					// socket_getpeername($socket,$host,$port);
					// echo 'host:'.$host;
					// echo 'port:'.$port;
					@socket_write($socket,$answMsg,strlen($answMsg));
					
				} else {
					// 握手
					$socketPool[(int)$socket]['handshake'] =  true;
					handshake($socket,$buff,$address,$port);
				}
			}
			
			
		}
	}
} catch (Exception $e) {
	echo $e->getMessage();
	socket_shutdown($hostSocket);
	socket_close($hostSocket);
}

function handshake($socket,$buffer,$host, $port)
{
	 $headers = array();
    $lines = preg_split("/\r\n/", $buffer);
    foreach($lines as $line)
    {
        $line = rtrim($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')));
    //hand shaking header
    $upgrade  = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" .
    "Upgrade: websocket\r\n" .
    "Connection: Upgrade\r\n" .
    "WebSocket-Origin: $host\r\n" .
    "WebSocket-Location: ws://$host:$port\r\n".
    "Sec-WebSocket-Version: 13\r\n" .
    "Sec-WebSocket-Accept:$secAccept\r\n\r\n";
    socket_write($socket, $upgrade);
}

// 解析数据帧
function decode($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;
}

// 返回帧信息处理
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;
}
学新通

web端


发送

// 结果
学新通

二、通过 stream_socket_server方式

server端

<?php

$host = '192.168.80.3';
$port = 8012;
// $path = 'C:/Certbot/live/php.net/';
$transport = 'tcp';
// $ssl = ['ssl' => [
          // 'local_cert'  => $path . 'cert.pem',       // SSL Certificate
          // 'local_pk'    => $path . 'privkey.pem',    // SSL Keyfile
          // 'disable_compression' => true,             // TLS compression attack vulnerability
          // 'verify_peer'         => false,            // Set this to true if acting as an SSL client
          // 'ssltransport' => $transport,              // Transport Methods such as 'tlsv1.1', tlsv1.2'
        // ] ];
// $ssl_context = stream_context_create($ssl);
$server = stream_socket_server($transport . '://' . $host . ':' . $port, $errno, $errstr, STREAM_SERVER_BIND|STREAM_SERVER_LISTEN);
if (!$server) {  die("$errstr ($errno)"); }
$socketPool[] = $server;// 保存所有连接
$write  = NULL;
$except = NULL;
const MAX_LISTEN_NUM = 200;// 最大链接数

try {
	while (true) {
		$curSockets = $socketPool; // 备份
		stream_select($curSockets, $write, $except, MAX_LISTEN_NUM);// 获取可读socket
		
		foreach ($curSockets as $socket) {
			if ($server == $socket) { // 请求连接
				$client = @stream_socket_accept($server);

				if ($client == false) {
					continue;
				} else {
					$socketPool[(int)$client] = $client;
				
					$ip = stream_socket_get_name( $client, true );
					echo "New Client connected from $ip\n";
					
					// 使用阻塞发送握手
					stream_set_blocking($client,true);
					$headers = fread($client, 1024);
					handshake($client, $headers, $host, $port);	
					stream_set_blocking($client,false);
					
					// 握手后发送连接成功消息
					fwrite($client, mask('connected ok'));
					$key = array_search($server,$curSockets);
					unset($curSockets[$key]);		
				}
					
			} else {
				$buffer = stream_get_contents($socket);
				echo 'buffer:'.$buffer;	
				if ($buffer == false) { // 连接已关闭
					stream_socket_shutdown($server,STREAM_SHUT_WR);// 关闭读写
					fclose($socket);// 关闭连接

					unset($socketPool[(int)$socket]);
				} else {
					$recvMsg = unmask($buffer);
					$response = mask($recvMsg);
				
					fwrite($socket, $response);

				}
			}
			
		}
			
	} 
} catch (Exception $e) {
	stream_socket_shutdown($server,STREAM_SHUT_WR);
	fclose($server);
}



/**
 * 将socket接收到的乱码字符转为UTF-8字符
 * @param  string $str
 * @return string
 */

function unmask($text) {
    $length = @ord($text[1]) & 127;
	echo 'text'.$text.PHP_EOL;
	echo 'text1'.$text[1].PHP_EOL;
	echo 'text-ord'.@ord($text[1]).PHP_EOL;
	echo 'length'.$length.PHP_EOL;
    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];    }
	echo 'mask'.$text.PHP_EOL;
    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('CCN', $b1, 127, $length);
    return $header.$text;
}

function handshake($client, $rcvd, $host, $port){
    $headers = array();
    $lines = preg_split("/\r\n/", $rcvd);
    foreach($lines as $line)
    {
        $line = rtrim($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')));
    //hand shaking header
    $upgrade  = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" .
    "Upgrade: websocket\r\n" .
    "Connection: Upgrade\r\n" .
    "WebSocket-Origin: $host\r\n" .
    "WebSocket-Location: wss://$host:$port\r\n".
    "Sec-WebSocket-Version: 13\r\n" .
    "Sec-WebSocket-Accept:$secAccept\r\n\r\n";
  fwrite($client, $upgrade);
}
学新通

web代码

<input type="text" name="msg" value="这是发送消息" id="msg"/><br>
<button onclick="send()">发送</button><br>
<button onclick="closeWs()">关闭连接</button>
<script>
var ws = new WebSocket('ws://192.168.80.3:8012/');

ws.onopen = function(e){
	console.log("Connection open ...");
	ws.send("发送消息");
}
ws.onmessage = function(e){
	console.log("onmessage",e.data);
}

ws.onclose = function(e){
  ws = null;
}

function closeWs(){
	console.log('close ws');
	ws.close();
}
function send(){
	console.log('send');
	var msg = document.getElementById('msg').value;
	console.log('msg',msg);
	ws.send(msg);
}

</script>
学新通

// 结果
学新通
关于pack参考我的另一篇文章:php pack/unpack学习
总结:
socket_create 和 stream_socket_server操作基本一致,只不过后者是文件流操作,前者是文件资源操作,没有本质区别

参考文章:
https://www.cnblogs.com/hustskyking/p/websocket-with-php.html
https://www.cnblogs.com/zhenbianshu/p/6111257.html

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgekjja
系列文章
更多 icon
同类精品
更多 icon
继续加载