php:使用socket函数创建WebSocket服务

embedded/2024/11/22 6:12:57/

一、前言

        闲来无事,最近捣鼓了下socket>websocket,但是不希望安装第三方类库,所以打算用socket基础函数创建个服务。

    

二、构建socket>websocket服务端

php"><?phpclass SocketService
{// 默认的监听地址和端口private $address  = '0.0.0.0';private $port = 8083;private $_sockets;/*** 构造函数,初始化地址和端口** @param string $address 监听的地址,默认 '0.0.0.0'* @param int $port 监听的端口,默认 8083*/public function __construct($address = '', $port = ''){if (!empty($address)) {$this->address = $address;}if (!empty($port)) {$this->port = $port;}}/*** 初始化服务,创建套接字并开始监听*/public function service(){// 获取 TCP 协议号$tcp = getprotobyname("tcp");// 创建 TCP 套接字$sock = socket_create(AF_INET, SOCK_STREAM, $tcp);// 设置套接字选项,允许地址重用socket_set_option($sock, SOL_SOCKET, SO_REUSEADDR, 1);// 如果创建失败,抛出异常if ($sock < 0) {throw new Exception("failed to create socket: " . socket_strerror($sock) . "\n");}// 绑定地址和端口socket_bind($sock, $this->address, $this->port);// 开始监听socket_listen($sock, $this->port);echo "listen on $this->address $this->port ... \n";// 保存套接字$this->_sockets = $sock;}/*** 运行 WebSocket 服务* * 该方法会进入一个无限循环,处理所有客户端连接*/public function run(){// 启动服务$this->service();// 存储客户端套接字$clients[] = $this->_sockets;// 无限循环监听客户端连接while (true) {$changes = $clients;$write = NULL;$except = NULL;// 监听可读的套接字socket_select($changes, $write, $except, NULL);// 处理每个连接的套接字foreach ($changes as $key => $_sock) {// 判断是否是新连接if ($this->_sockets == $_sock) {// 接受新连接if (($newClient = socket_accept($_sock)) === false) {die('failed to accept socket: ' . socket_strerror($_sock) . "\n");}// 读取客户端发送的数据$line = trim(socket_read($newClient, 1024));// 执行 WebSocket 握手$this->handshaking($newClient, $line);// 获取客户端 IPsocket_getpeername($newClient, $ip);// 将新连接的客户端保存$clients[$ip] = $newClient;// 输出客户端 IP 和消息echo "Client ip:{$ip}   \n";echo "Client msg:{$line} \n";} else {// 处理已连接的客户端消息socket_recv($_sock, $buffer, 2048, 0);// 解码接收到的消息$msg = $this->message($buffer);// 在这里处理业务逻辑echo "{$key} client msg: {$msg}\n";// 等待用户输入响应fwrite(STDOUT, 'Please input a argument:');$response = trim(fgets(STDIN));// 发送响应给客户端$this->send($_sock, $response);echo "{$key} response to Client: {$response}\n";}}}}/*** WebSocket 握手处理* * @param resource $newClient 新连接的客户端套接字* @param string $line 接收到的握手请求头* @return int 返回写入的字节数*/public function handshaking($newClient, $line){$headers = array();$lines = preg_split("/\r\n/", $line);// 解析请求头foreach ($lines as $line) {$line = chop($line);if (preg_match('/\A(\S+): (.*)\z/', $line, $matches)) {$headers[$matches[1]] = $matches[2];}}// 获取客户端的 Sec-WebSocket-Key$secKey = $headers['Sec-WebSocket-Key'];// 生成 Sec-WebSocket-Accept$secAccept = base64_encode(pack('H*', sha1($secKey . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')));// 构造握手响应$upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" ."Upgrade: socket>websocket\r\n" ."Connection: Upgrade\r\n" ."WebSocket-Origin: $this->address\r\n" ."WebSocket-Location: ws://$this->address:$this->port/socket>websocket/socket>websocket\r\n" ."Sec-WebSocket-Accept:$secAccept\r\n\r\n";// 发送握手响应return socket_write($newClient, $upgrade, strlen($upgrade));}/*** 解析接收到的 WebSocket 消息* * @param string $buffer 接收到的 WebSocket 数据* @return string 解码后的消息*/public function message($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;}/*** 发送 WebSocket 消息给客户端* * @param resource $newClient 新连接的客户端套接字* @param string $msg 要发送的消息* @return int 返回写入的字节数*/public function send($newClient, $msg){// 封装消息为 WebSocket 数据帧$msg = $this->frame($msg);// 发送数据帧socket_write($newClient, $msg, strlen($msg));}/*** 将消息封装为 WebSocket 数据帧* * @param string $s 要封装的消息* @return string 封装后的 WebSocket 数据帧*/public 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;}/*** 关闭 WebSocket 连接* * @return bool 返回是否成功关闭*/public function close(){return socket_close($this->_sockets);}
}// 创建并运行 WebSocket 服务
$sock = new SocketService();
$sock->run();

三、构建socket>websocket客户端

  接下来写个前端页面,测试服务端是否正常,代码如下:

<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"><title>WebSocket</title></head><body><input id="text" value=""><input type="submit" value="发送" onclick="start()"><input type="submit" value="关闭" onclick="close()"><div id="msg"></div><script>/*** WebSocket的连接状态代码:* 0: 未连接* 1: 已连接,可以通讯* 2: 正在关闭* 3: 已关闭或无法打开*/// 创建WebSocket实例var webSocket = new WebSocket("ws://127.0.0.1:8083");// 监听错误事件webSocket.onerror = function (event) {onError(event);};// 监听连接成功事件webSocket.onopen = function (event) {onOpen(event);};// 监听消息事件webSocket.onmessage = function (event) {onMessage(event);};// 监听关闭事件webSocket.onclose = function (event) {onClose(event);};// 错误处理函数function onError(event) {document.getElementById("msg").innerHTML = "<p>连接错误</p>";console.log("错误: " + event.data);}// 连接成功后的回调函数function onOpen(event) {console.log("连接成功: " + sockState());document.getElementById("msg").innerHTML = "<p>已连接到服务</p>";}// 处理接收到的消息function onMessage(event) {console.log("接收到消息");document.getElementById("msg").innerHTML += "<p>响应: " + event.data + "</p>";}// 连接关闭后的回调函数function onClose(event) {document.getElementById("msg").innerHTML = "<p>连接已关闭</p>";console.log("关闭连接: " + sockState());webSocket.close();}// 获取WebSocket连接状态function sockState() {var status = ['未连接', '已连接,可以通讯', '正在关闭', '已关闭或无法打开'];return status[webSocket.readyState];}// 发送消息函数function start(event) {console.log(webSocket);var msg = document.getElementById('text').value;document.getElementById('text').value = ''; // 清空输入框console.log("发送消息: " + sockState());console.log("消息内容: " + msg);webSocket.send("msg=" + msg); // 发送消息document.getElementById("msg").innerHTML += "<p>请求: " + msg + "</p>";}// 关闭连接function close(event) {webSocket.close();}</script></body>
</html>

四、测试结果

出现已连接到服务,代表成功连接。


http://www.ppmy.cn/embedded/139544.html

相关文章

web——sqliabs靶场——第八关——sqlmap的使用

第八关还是用到了盲注&#xff0c;我们来使用kali里的sqlmap工具来搞一下。 1.sqlmap简介 sqlmap 是一款开源的自动化 SQL 注入和数据库接管工具&#xff0c;旨在帮助安全研究人员和渗透测试人员检测和利用 SQL 注入漏洞。它支持多种数据库管理系统&#xff08;如 MySQL、Post…

网络安全之接入控制

身份鉴别 ​ 定义:验证主题真实身份与其所声称的身份是否符合的过程&#xff0c;主体可以是用户、进程、主机。同时也可实现防重放&#xff0c;防假冒。 ​ 分类:单向鉴别、双向鉴别、三向鉴别。 ​ 主题身份标识信息:密钥、用户名和口令、证书和私钥 Internet接入控制过程 …

Vue前端开发子组件向父组件传参

在父组件中&#xff0c;如果需要获取子组件中的数据&#xff0c;有两种方式&#xff0c;一种是在子组件中自定义事件&#xff0c;父组件绑定该事件&#xff0c;当触发自定义事件时&#xff0c;向父组件传入参数;另一种是先通过ref属性给子组件命名&#xff0c;然后在父组件中就…

【贪心算法】贪心算法三

贪心算法三 1.买卖股票的最佳时机2.买卖股票的最佳时机 II3.K 次取反后最大化的数组和4.按身高排序5.优势洗牌&#xff08;田忌赛马&#xff09; 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496; 你的支持是对我最大的鼓励&#…

Spark SQL大数据分析快速上手-完全分布模式安装

【图书介绍】《Spark SQL大数据分析快速上手》-CSDN博客 《Spark SQL大数据分析快速上手》【摘要 书评 试读】- 京东图书 大数据与数据分析_夏天又到了的博客-CSDN博客 Hadoop完全分布式环境搭建步骤-CSDN博客,前置环境安装参看此博文 完全分布模式也叫集群模式。将Spark目…

【论文笔记】Large Brain Model (LaBraM, ICLR 2024)

Code: https://github.com/935963004/LaBraM Data: 无 目录 AbstractIntroductionMethodNeural tokenizer training&#xff1a;Pre-training LaBraM&#xff1a; ResultsExperimental setup&#xff1a;Pre-training result&#xff1a;Comparison with SOTA&#xff1a;Pre-t…

scPair:隐式特征选择提高single-cell paired多模态分析

配对多模态单细胞分析可在同一细胞中分析多模态特征&#xff0c;用于识别染色质和 mRNA 模态互补的细胞状态以及将调控元件与靶基因联系起来。然而&#xff0c;与单模态分析相比&#xff0c;输入特征的高维度和较浅的测序深度给数据分析带来了挑战。在这里&#xff0c;作者介绍…

leetcode:114. 二叉树展开为链表

给你二叉树的根结点 root &#xff0c;请你将它展开为一个单链表&#xff1a; 展开后的单链表应该同样使用 TreeNode &#xff0c;其中 right 子指针指向链表中下一个结点&#xff0c;而左子指针始终为 null 。展开后的单链表应该与二叉树 先序遍历 顺序相同。 示例 1&#xf…