IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> PHP知识库 -> php websocket通信 -> 正文阅读

[PHP知识库]php websocket通信

服务端

websocket.php 文件
<?php
$socket1 = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
socket_bind($socket1,"0.0.0.0",6003);
socket_listen($socket1,3);
$clients = array($socket1);
while(true){
    //socket_select对读写套子节的数字是引用,为了保证clients不被改变,拷贝一份。
    $read=$clients;
    $write = null;
    $except= null;
    if( socket_select($read , $write,$except,null) > 0){
        foreach($read as $socket_item){
            if ($socket_item == $socket1){
                if(!$socket_client = socket_accept($socket_item)) continue;
                $hello = @socket_read($socket_client, 1024);
                if ($hello == false){
                    socket_close($socket_client);continue;
                }
                handshake($hello,$socket_client);
                socket_getpeername($socket_client,$ip,$port);
                //区分前端客户端 消息 还是 php客户端消息
                if(preg_match('/Sec-WebSocket-Key: (.*)\r\n/',$hello)){
                    $clients["ws:".$ip." :". $port] =$socket_client;
                }else{
                    $clients["cl:".$ip." :". $port] =$socket_client;
                }

                echo "new client[".$ip.":".$port. "]\n";
                echo "hello msg [" .$hello."]\n";
            }else{
                $result = @socket_recv($socket_item,$msg,1024,0);
                /*
                //调试用
                echo "result :\n";
                var_dump($result);

                echo "mag1 :\n";
                var_dump($msg);

                echo "mag2 decode :\n";
                var_dump(decode($msg));

                echo "mag3 ord-decode :\n";
                var_dump(ord(decode($msg)));*/
                //$result === 8 && ord(decode($msg)) == 3 前端客户端 页面关闭条件
                //$result === false && $msg === NULL php 客户端 关闭条件
                if($result === 0 || ($result === 8 && ord(decode($msg)) == 3) || ($result === false && $msg === NULL)){
                    socket_close($socket_item);
                    $key1=array_search($socket_item,$read);
                    unset($read[$key1]);
                    $key2=array_search($socket_item,$clients);
                    unset($clients[$key2]);
                    echo "client [$key2] is closed!\n";
                }else  if(!empty($result)){
                    $id = search($socket_item,$clients);
                    //前端客户端消息要解码  客户端消息不解码
                    $web_msg = strpos($id,"ws") !== false ? decode($msg) : $msg;
                    echo "client [".$id."] say: " .$web_msg."\n" ;
                    foreach($clients as $client_socket) {//发给别人(除去监听和自己)
                        if($client_socket != $socket1 ){
                            $key2=array_search($client_socket,$clients);
                            $broadcast =  strpos($key2,"ws") !== false ? encode($web_msg) :  $web_msg;

                            if(false==@socket_write($client_socket,$broadcast,strlen($broadcast))){
                                socket_close($client_socket);
                                $key1=array_search($client_socket,$read);
                                unset($read[$key1]);
                                $key2=array_search($client_socket,$clients);
                                unset($clients[$key2]);
                                echo "otherclient [$key2] is closed!\n";
                           }
                        }
                    }
                }
                if ($result === false) continue;
            }
        }
    }else{
        continue;
    }
}


function handshake( $buffer,$client_socket){
    //截取Sec-WebSocket-Key的值并加密
    preg_match('/Sec-WebSocket-Key: (.*)\r\n/',$buffer,$match);
    $server_key = base64_encode(sha1($match[1]. '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
    echo "client_key [".$match[1]."]\n";
    echo "server_key [". $server_key."]\n\n";
    if ($match[1] == '') return false;
    $handshake_msg = "HTTP/1.1 101 Switching Protocols\r\n";
    $handshake_msg .= "Upgrade: websocket\r\n";
    $handshake_msg .= "Sec-webSocket-Version: 13\r\n";
    $handshake_msg .= "Connection:Upgrade\r\n";
    //一定要切记  最后一行需要两个换行符  \r\n\r\n
    $handshake_msg .= "Sec-webSocket-Accept: " . $server_key . "\r\n\r\n";
    socket_write($client_socket ,$handshake_msg,strlen($handshake_msg));
    echo $handshake_msg;
    return true;
}

function encode($buffer){
    $len =strlen($buffer);
    if ($len<=125) {
        return "\x81" . chr($len) . $buffer;
    } else if ($len<=65535) {
        return "\x81" . chr(126) . pack( "n", $len) . $buffer;
    } else {
        return "\x81" . chr(127) . pack("xxxxN", $len) . $buffer;
    }
}

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 search($socket_client,$clients){
    $search = array_search($socket_client,$clients,true);
    if ($search === null) $search = false;
    return $search;
}

客户(两种)

一 、php 作为客户端 只负责发送信息 这里方便测试建了前端

index.html 文件

<head>
    <meta charset="UTF-8">
</head>

<form action="http://localhost/" method="post">
    <input type="text" name="data" value="">
    <input type="submit" value="发送">
</form>

index.php 文件

<?php
header("Content-Type: text/html; charset=utf-8");
$host = '127.0.0.1';
$port = 6003;
$sock = null;
if (($sock = socket_create(AF_INET,SOCK_STREAM,SOL_TCP)) === false){//创建socket
    echo "socket_create(0) failed: reason: ".socket_strerror(socket_last_error()) . "\n";
    return;
}
if($con = socket_connect($sock, $host,$port) === false){
    echo 'connect fail' . "\n";
    return;
}else{
    echo ' connect success'. "\n";
}

$client_msg="client202204281630";

if(socket_write($sock, $client_msg,strlen($client_msg)) === false){
    echo 'fail to write '.socket_strerror(socket_last_error());
}else{
    echo 'client write success' .PHP_EOL. "\n";
    //读取服务端返回来的套接流信息
//    while(1){
//        if($serverback = @socket_read($sock, 1024)){
//            echo "server return message is: " .$serverback.PHP_EOL;
//        }
//        $date = date("Y-m-d H:i:s");
//
//        if(socket_write($sock, $date,strlen($date)) === false){
//            echo 'fail to write '.socket_strerror(socket_last_error());
//        }else{
//            echo "send msg : $date \n";
//        }
//
//        $serverback = socket_read($sock, 1024);
//        echo "serverback : \n";
//        var_dump($serverback);
//        sleep(1);
//    }
    sleep(1);
    $data = $_POST['data'];

    if(socket_write($sock, $data,strlen($data)) === false){
        echo 'data send fail : '.socket_strerror(socket_last_error());
    }else{
        echo 'data send success';
    }

    socket_close($sock);
}

header("Location:index.html");
二 、前端websocket
var ws = new WebSocket("ws://127.0.0.1:6003");

ws.onopen = function(evy){
    console.log("connection open....");
  
}

ws.onmessage = function(evt){
  console.log("recv msg:"+ evt.data);
}

ws.onclose = function(){
    console.log("connection closed");
}

在这里插入图片描述

基础理解篇
service.php文件

<?php
/**
    AF_INET:  IPv4网络协议。TCP和UDP都可使用此协议。
    AF_INET6: IPv6网络协议。TCP和UDP都可使用此协议。
    AF_UNIX:  本地通讯协议。具有高性能和低成本的IPC(进程间通讯)

    SOCK_STREAMTCP 协议套接宇。
    SOCK_DGRAMUDP  协设套接字
    SOL_TCP:TCP协议。
    SOL_UDP:UDP协议。
*/
$socket1 = socket_create(AF_INET, SOCK_STREAM, SOL_TCP) ;

socket_bind($socket1,'192.168.197.128','6003');

socket_listen($socket1,500);

$clients = array($socket1); /* 个人理解 此处$socket1 为客户端的连接资源  以下 $socket2 为客户端的消息资源*/

while (true){
    /**
    socket_select对读写套子节的数字是引用,为了保证clients不被改变,拷贝一份。$read=$clients;
    */
    $read = $clients; // 个人理解 $clients 里存储 连接资源 和 消息资源
    $write = null;
    $except = null;

   /* echo "clients_num:".count($clients) ."\r\n";
    echo "read_num:".count($read) ."\r\n";
    echo "1111 var_dump clients:"."\r\n";
    var_dump($clients);
    echo "1111 var_dump read:"."\r\n";
    var_dump($read);

    echo "\r\n";*/
    /**
     当read发生变化,说明:有客户端发生连接操作了,
     $write/$except均为null,所以只监测read数组的变化第
     4个参数:
     null,表示阻塞;socket_select()会阻塞—直到发现变化,
     0,表示为非阻塞,调用socket_select()后,立即返回,然后继续调用socket_select()不断循环返回值是修改后的数组中包含的套接字资源的数量

     个人理解:socket_select 判断 $read 里的各种资源 有没有变化 ? 有的变化 说明资源活跃  留在 $read (一般只存在一种资源 消息 或 连接);没有的 删除掉;

     助解:https://blog.csdn.net/Im_KK/article/details/45033533
     */
    if(socket_select($read,$write,$except,null) > 0){

       /* echo "in_array var_dump socket1:"."\r\n";
        var_dump($socket1);
        echo "\r\n";
        echo "in_array var_dump read:"."\r\n";
        var_dump($read);
        echo "\r\n";*/

        // 经过 socket_select  $read 里都是有的变化 活跃资源   当连接资源 $socket1 在 $read 存在 说明 有人连接客户端了
        if(in_array($socket1,$read)){

            $socket2 = socket_accept($socket1); // socket接受后  成为存消息的资源 $socket2
            $clients[] = $socket2;
            socket_write($socket2,"yuo are ".(count($clients)-1)." client\r\n");
            socket_getpeername($socket2,$ip,$port);
            echo "新连接客户端 [ip:{$ip} port:{$port}]\r\n";
            $key = array_search($socket1,$read);
            unset($read[$key]); //删除 $read中的 连接资源 ;因为下面要进行 消息发生 $read中 应该是消息资源
        }


        /*echo "clients_num:".count($clients) ."\r\n";
        echo "read_num:".count($read) ."\r\n";

        echo "2222 var_dump clients:"."\r\n";
        var_dump($clients);

        echo "222 var_dump read:"."\r\n";
        var_dump($read);*/

        if(count($read) > 0){
            foreach ($read as $socket_item){
                $msg = socket_read($socket_item,1024);
                echo "接收到消息:{$msg} \r\n";
                socket_write($socket_item,"接收到消息:{$msg}",strlen($msg));

                foreach ($clients as $client_socket){
                    //$clients 中的 连接资源没有被删除 ; 不给 连接资源$socket1发消息
                    // 也不给 自己发消息 $socket_item 当前活跃的消息资源
                    if($socket1 != $client_socket && $socket_item != $client_socket){
                        sleep(1);
                        socket_write($client_socket,$msg,strlen($msg));
                    }
                }
            }
        }

        /*echo "\r\n";
        echo "\r\n";
        echo "\r\n";*/
    }
}



websocket封装成类

<?php
class SocketServer
{
	private $socket;
	private $readGroup = array();									//保存读的套接字
	private $writeGroup = array();									//保存写的套接字
	private $except = array();
	private $mcrypt_key = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';	//websocket协议中用于加密的字符串
	private $test = false;

	/**
	*初始化
	*@param String $host ip地址
	*@param int $port 端口
	*@param int $backlog 最大连接数
	*ws://121.40.165.18:8800
	*/
	public function __construct($host = '192.168.197.128',$port = '8081', $backlog = 10)
	{
		$this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or die('socket创建失败');
		socket_bind($this->socket,$host,$port);
		socket_listen($this->socket,$backlog);
		$this->readGroup[] = $this->socket; 	//将所有套接字存于该数组
        echo PHP_EOL;
        echo "  readGroup:".PHP_EOL;
        echo PHP_EOL;
        var_dump($this->readGroup);
	}

	public function start()
	{
		while(true)
		{
			//socket_select会移除了数组元素,所以必须用临时变量重置数组
			$socketArr = $this->readGroup;  
			//阻塞,直到捕获到变化
			socket_select($socketArr, $this->writeGroup , $this->except , 3600);
			//遍历读的套接数组
			foreach($socketArr as $socket)
			{
                echo PHP_EOL;
                echo "  socketArr:".PHP_EOL;
                echo PHP_EOL;
                var_dump($socketArr);


                echo PHP_EOL;
                echo "  socket:".PHP_EOL;
                echo PHP_EOL;
                var_dump($socket);
				//如果是当前服务器的监听连接
				if($this->socket == $socket)
				{
					$client = socket_accept($this->socket);

                    echo PHP_EOL;
                    echo "client:".PHP_EOL;
                    echo PHP_EOL;

                    var_dump($client);

					//添加客户端套接字
					$this->add_client($client);					
				}
				else
				{
					//获取客户端发送来的信息
					$msg = @socket_read($socket,1024);

                    echo PHP_EOL;
					echo "msg:".PHP_EOL;
                    echo PHP_EOL;

					var_dump($msg);
					//如果检测到客户端发送的是握手协议,则向客户端发送握手协议
					if(empty($msg))
					{
						continue;
					}
					else if(preg_match('/Sec-WebSocket-Key: (.*)\r\n/', $msg,$matches))						
					{
                        echo PHP_EOL;
                        echo "matches:".PHP_EOL;
                        echo PHP_EOL;

					    var_dump($matches);
						$upgrade = $this->createHandShake($matches[1]);
						@socket_write($socket,$upgrade,strlen($upgrade));


                        echo PHP_EOL;
                        echo "  socketArr222:".PHP_EOL;
                        echo PHP_EOL;
                        var_dump($socketArr);
					}
					else
					{
						//解码客户端发送的消息
						$client_info = $this->decodeMsg($msg);

                        echo PHP_EOL;
                        echo "client_info:".PHP_EOL;
                        echo PHP_EOL;

						echo $client_info.PHP_EOL;
						//向其他客户端进行广播
						$this->send_to_other($client_info,$socket);
					}
				}
			}
		}
	}


	/**
	*@param resource $client 客户端的套接字
	*/
	private function add_client($client)
	{
		$this->readGroup[] = $client;
	}

	/**
	*发送到指定客户端
	*@param resource $socket 客户端套接字
	*@param String $data 要发送的消息
	*/
	private function send_to_one($socket,$data)
	{
		//先对信息进行编码
		$data = $this->encodeMsg($data);
		socket_write($socket, $data);
	}

	/**
	*广播至所有客户端
	*@param String $data
	*/
	private function send_to_all($data)
	{
		$writeGroup = $this->readGroup;
		unset($writeGroup[0]);				//除去服务器自身
		$data = $this->encodeMsg($data);
		foreach($writeGroup as $socket)
		{
			socket_write($socket, $data);
		}
	}

	/**
	*广播至除发送消息外的客户端
	*@param String $data
	*@param resource $client 
	*/
	private function send_to_other($data,$client)
	{
		$writeGroup = $this->readGroup;
		unset($writeGroup[0]);				//除去服务器自身
		$data = $this->encodeMsg($data);
		foreach($writeGroup as $socket)
		{
			if($socket != $client)
			{
				socket_write($socket, $data);
			}
		}

	}

	/**
	*计算返回客户端的协议
	*@param String $msg 客户端发送过来的协议
	*@return String $upgrade 返回给客户端的协议
	*/
	private function createHandShake($client_key)
	{
		$key = base64_encode(sha1($client_key.$this->mcrypt_key,true));
		$upgrade  = "HTTP/1.1 101 Switching Protocol\r\n" .
					"Upgrade: websocket\r\n" .
					"Connection: Upgrade\r\n" .
					"Sec-WebSocket-Accept: " . $key . "\r\n\r\n";		//结尾一定要两个\r\n\r\n
		return $upgrade;
	}

	/**
	*解码客户端发送过来的信息
	*@param binary $buffer 客户端传来的信息
	*@return String $decoded 解码后的字符串
	*/
	private function decodeMsg($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;
	}

	/**
	*发送到服务端前进行编码
	*/
	private function encodeMsg($msg)
	{
        $head = str_split($msg, 125);
        if (count($head) == 1){
            return "\x81" . chr(strlen($head[0])) . $head[0];
        }
        $info = "";
        foreach ($head as $value){
            $info .= "\x81" . chr(strlen($value)) . $value;
        }
        return $info;
	}


	public function __destruct(){
		socket_unbind($this->socket);
	}
}

$socketServer = new SocketServer();
$socketServer->start();
  PHP知识库 最新文章
Laravel 下实现 Google 2fa 验证
UUCTF WP
DASCTF10月 web
XAMPP任意命令执行提升权限漏洞(CVE-2020-
[GYCTF2020]Easyphp
iwebsec靶场 代码执行关卡通关笔记
多个线程同步执行,多个线程依次执行,多个
php 没事记录下常用方法 (TP5.1)
php之jwt
2021-09-18
上一篇文章      下一篇文章      查看所有文章
加:2022-05-04 07:24:36  更:2022-05-04 07:24:42 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/18 18:54:52-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码