网络编程
位置:首页>> 网络编程>> php编程>> PHP实现WebSocket实例详解

PHP实现WebSocket实例详解

作者:Azure沫  发布时间:2023-06-11 23:08:01 

标签:PHP,WebSocket

WebSocket 是什么?

摘抄网上的一些解释:

WebSocket 协议是基于 TCP 的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。

WebSocket 通信协议于2011年被 IETF 定为标准 RFC 6455,并被 RFC7936 所补充规范。

—— 百度百科

WebSocket 是一个持久化的协议,这是相对于 http 非持久化来说的。

举个简单的例子,http1.0 的生命周期是以 request 作为界定的,也就是一个 request,一个 response,对于 http 来说,本次 client 与 server 的会话到此结束;而在 http1.1 中,稍微有所改进,即添加了 keep-alive,也就是在一个 http 连接中可以进行多个 request 请求和多个 response 接受操作。然而在实时通信中,并没有多大的作用,http 只能由 client 发起请求,server 才能返回信息,即 server 不能主动向 client 推送信息,无法满足实时通信的要求。而 WebSocket 可以进行持久化连接,即 client 只需进行一次握手,成功后即可持续进行数据通信,值得关注的是 WebSocket 实现 client 与 server 之间全双工通信,即 server 端有数据更新时可以主动推送给 client 端。

PHP实现WebSocket实例详解

上图是一个演示client和server之间建立WebSocket连接时握手部分

client 建立 WebSocket 时向服务器端请求的信息


GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket //告诉服务器现在发送的是WebSocket协议
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== //是一个Base64 encode的值,这个是浏览器随机生成的,用于验证服务器端返回数据是否是WebSocket助理
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com

服务器获取到 client 请求的信息后,根据 WebSocket 协议对数据进行处理并返回,其中要对 Sec-WebSocket-Key 进行加密等操作


HTTP/1.1 101 Switching Protocols
Upgrade: websocket //依然是固定的,告诉客户端即将升级的是Websocket协议,而不是mozillasocket,lurnarsocket或者shitsocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= //这个则是经过服务器确认,并且加密过后的 Sec-WebSocket-Key,也就是client要求建立WebSocket验证的凭证
Sec-WebSocket-Protocol: chat

PHP 服务端


<?php

if(($socket = socket_create(AF_INET,SOCK_STREAM,SOL_TCP)) < 0) {
   echo "socket_create() 失败的原因是:".socket_strerror($sock)."\n";
}
if(($ret = socket_bind($socket,'127.0.0.1','9090')) < 0) {
   echo "socket_bind() 失败的原因是:".socket_strerror($ret)."\n";
}
if(($ret = socket_listen($socket,3)) < 0) {
   echo "socket_listen() 失败的原因是:".socket_strerror($ret)."\n";
}

$all_sockets = [$socket];    // socket 集合

do {
   $copy_sockets = $all_sockets;   // 单独拷贝一份

   // 因为客户端是长连接,如果客户端非正常断开,服务端会在 socket_accept 阻塞,现在使用 select 非阻塞模式 socket
   if(socket_select($copy_sockets, $write, $except, 0) === false)
       exit('sosket_select error!');

   // 接收第一次 socket 连入,连入后移除服务端 socket
   if(in_array($socket, $copy_sockets)) {
       $client = socket_accept($socket);
       if($client) {
           $buf = socket_read($client, 1024);
           echo $buf;

           // 匹配 Sec-Websocket-Key 标识
           if (preg_match("/Sec-WebSocket-Key: (.*)\r\n/i",$buf,$match)) {
               // 需要将 Sec-WebSocket-Key 值累加字符串,并依次进行 SHA-1 加密和 base64 加密
               $key = base64_encode(sha1($match[1] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',true));
               // 拼凑响应内容
               $res= "HTTP/1.1 101 Switching Protocol".PHP_EOL
                   ."Upgrade: WebSocket".PHP_EOL
                   ."Connection: Upgrade".PHP_EOL
                   ."WebSocket-Location: ws://127.0.0.1:9090".PHP_EOL
                   ."Sec-WebSocket-Accept: " . $key .PHP_EOL.PHP_EOL;  // 注意这里,需要两个换行
               // 向客户端应答 Sec-WebSocket-Accept
               socket_write($client, $res, strlen($res));
               // 向客户端发送消息
               socket_write($client, buildMsg('socket ok'), 1024);

               // 加入客户端 socket
               $all_sockets[] = $client;
           }
           // 移除服务端 socket
           $key = array_search($socket, $copy_sockets);
           unset($copy_sockets[$key]);

           // socket_close($client);
       }
   }

   // 循环所有客户端 sockets
   foreach ($copy_sockets as $s) {
       // 获取客户端发给服务端的内容
       $buf = socket_read($s, 8024);
       echo strlen($buf).'---'.PHP_EOL;
       // 代表客户端主动关闭
       if(strlen($buf) < 9) {
           $key = array_search($s, $all_sockets);
           unset($all_sockets[$key]);
           socket_close($s);
           continue;
       }
       // 输出
       echo getMsg($buf).PHP_EOL;
   }

}while(true);
socket_close($socket);

// 编码服务端向客户端发送的内容
function buildMsg($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);
}

// 解析客户端向服务端发送的内容
function getMsg($buffer) {
   $res = '';
   $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++) {
       $res .= $data[$index] ^ $masks[$index % 4];
   }
   return $res;
}

客户端


<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <title>Title</title>
   <script>
       // 创建一个Socket实例
       var socket = new WebSocket('ws://localhost:9090');

       // 打开Socket
       socket.onopen = function(event) {
           // 发送一个初始化消息
           socket.send("init msg");

       };
       socket.onmessage = function(event) {
           console.log('收到消息',event);

       };

       // 监听Socket的关闭
       socket.onclose = function(event) {
           console.log('关闭监听',event);
       };

       function  send()
       {
           socket.send("client msg");
       }
   </script>
</head>
<body>
<button onclick="send()">发送消息</button>
</body>
</html>

运行测试:

Client

PHP实现WebSocket实例详解

Server

PHP实现WebSocket实例详解

来源:https://www.cnblogs.com/tangxuliang/p/9713614.html

0
投稿

猜你喜欢

  • 学习使用存储过程(Stored Procedure),是ASP程序员的必须课之一。所有的大型数据库都支持存储过程,比如Oracle
  • 两个多月来唯一一次有时间哄么么睡觉,我先给他讲了遍《从前有座山》,还是不睡。又给他讲了这个“保安的故事”:小A是名很敬业的保安,负责保护客户
  • 网站或应用的登录页面有时候通常用户会看很多遍,同时也有机会诱使临时用户注册,所以,一个设计良好的登录页面会比你想象的更有用。这里是一些我们收
  • 随着手机用户的不断增加,WAP站点如雨后春笋迅速的滋长开来,手机邮箱也不断的出现在人的眼前,笔者也曾经开发了一套手机邮箱的系统,但由于时间仓
  • 最好也是最简单的办法就是利用Cookie,而不必用到数据库。当然,你愿意用数据库也可以。下面就是利用Cookie来实现的:< 
  • ASP给图片加水印是需要组件的...常用的有aspjpeg软件和中国人自己开发的wsImage软件,可以上网搜索下载这两个软件,推荐使用咱们
  •  代码如下:ALTER proc [dbo].[sp_common_paypal_AddInfo] ( @paypalsql va
  • jQuery的选择器可谓异常强大,没有什么DOM里的任何数据能逃出它的掌心,这点是我非常喜欢的,以前获取NODE要用getElementBy
  • 代码如下:<% set rs=server.createobject("adodb.recordset&
  • 我还我还是有必要改一个标题,(原题为 让你想不通的"bug"),以免有同学误会。先看代码。看完之后我有个问题提问一下,看
  • 本文介绍了四种asp导出excel数据的方法:1.使用OWC ,2.用Excel的Application组件,3.直接在IE中打开,4.导出
  • 一:命名空间里的namespace关键字和__NAMESPACE__常量的运用PHP支持两种抽象的访问当前命名空间内部元素的方法,__NAM
  • 众所周知windows平台漏洞百出,补丁一个接一个,但总是补也补不净。我把我所知道的看asp源码的方法总结了一下,并且用c#写了个应用程序来
  • 感谢LeXRus为我们带来他费心制作的教程,这是一个非常棒的动画教程,教程中不仅有 DW MX 2004 的操作方法,还有一些代码的写作和方
  • 1.函数array() 功能:创建一个数组变量 格式:array(list) 参数:list为数组变量中的每个数值列,中间用逗号间隔 例子:
  • 大家都知道JAVA里最流行的是MVC模型的编程方式,如果你不知道MVC的概念,可以去网上搜索下,应该会马上找到N多资料。PHP5推出之后,也
  • 移动互联网被称为“第五次科技革命”,而随着iPhone和Android等智能手机的日渐流行和iPad等平板电脑的出现,移动互联网的潜力和趋势
  • 代码如下:CREATE TABLE #tmptb(tbname sysname,tbrows int ,tbREserved varchar
  • 这只是个asp小技巧类的东西,它虽然适合在每个不同文件名里调用这个函数,但是也是有前提的,下面让我们来仔细看看其中的原委。  &n
  • 备注: 关于label和tag,在中文中都翻译成标签,而下文中出现的标签,都是对label的翻译,比如”用户名”+输入框, 这里的”用户名”
手机版 网络编程 asp之家 www.aspxhome.com