前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >开源高性能PHP应用容器workerman一对一聊天多人聊天

开源高性能PHP应用容器workerman一对一聊天多人聊天

作者头像
Tinywan
发布2025-02-27 15:23:10
发布2025-02-27 15:23:10
9100
代码可运行
举报
文章被收录于专栏:开源技术小栈
运行总次数:0
代码可运行

概述

Workerman 是一款纯 PHP 开发的开源高性能 PHP 应用容器,它突破了传统 PHP 应用的限制,能够开发高性能的实时网络应用。它不是传统的 MVC 框架,而是一个底层通用的服务框架,支持 TCP、UDP、HTTP、WebSocket 等多种协议,适用于开发即时通讯、物联网、游戏服务器、高性能 HTTP 服务等多种应用。

主要特点

  1. 高性能:常驻内存运行,避免了传统 PHP 每次请求的初始化开销,支持高并发连接。
  2. 多进程支持:充分利用多核 CPU,通过多进程处理并发请求。
  3. 长连接支持:适合需要保持长时间连接的应用,如聊天室、游戏等。
  4. 丰富的协议支持:支持标准协议和自定义协议。
  5. 分布式部署:支持大规模分布式部署。
  6. 平滑重启:支持服务的平滑升级,不影响客户端使用。

应用场景

  • 即时通讯:如实时聊天室、消息推送。
  • 物联网:与智能硬件设备通信。
  • 游戏服务器:支持棋牌游戏、MMORPG 等。
  • 高性能 HTTP 服务:用于开发高性能网站或 API。
  • 数据监控:实时推送数据变化。

Workerman 的设计理念是极简、稳定和高性能,适合希望突破传统 PHP 开发限制的开发者。

预览

安装

代码语言:javascript
代码运行次数:0
复制
composer require workerman/workerman

服务器端代码

start.php 文件

代码语言:javascript
代码运行次数:0
复制
<?php
useWorkerman\Worker;
useWorkerman\Connection\TcpConnection;
useWorkerman\Timer;
require_once__DIR__ . '/vendor/autoload.php';

// 心跳间隔55秒
define('HEARTBEAT_TIME', 55);
$worker = new Worker('websocket://0.0.0.0:8484');
$worker->count = 1;
// $worker->transport = 'ssl'; // 强制使用 SSL 传输层
$worker->connectionList = [];
// 进程启动后设置一个每10秒运行一次的定时器
$worker->onWorkerStart = function ($worker){
    Timer::add(10, function() use ($worker){
        $time_now = time();
        foreach($worker->connections as $connection) {
            // 有可能该connection还没收到过消息,则lastMessageTime设置为当前时间
            if (empty($connection->lastMessageTime)) {
                $connection->lastMessageTime = $time_now;
                continue;
            }
            // 上次通讯时间间隔大于心跳间隔,则认为客户端已经下线,关闭连接
            if ($time_now - $connection->lastMessageTime > HEARTBEAT_TIME) {
                $connection->send('您长时间未请求服务器,链接已经断开');
                $connection->close();
            }
        }
    });

    // 开启一个内部端口,方便内部系统推送数据,Text协议格式 文本+换行符
    $textWorker = new Worker('tcp://0.0.0.0:5678');
    $textWorker->onMessage = function($connection, $data) use ($worker)
    {
        // $data数组格式,里面有uid,表示向那个uid的页面推送数据
        $data = json_decode($data, true);
        //聊天
        if ($data['type'] == 'text')
        {
            if (isset($worker->connectionList[$data['userToId']]))
            {
                $conns = $worker->connectionList[$data['userToId']];
                foreach ($conns as $conn){
                    $conn->send(json_encode([
                        'status' => 1,
                        'message' => $data['message'],
                    ]));
                }
                $connection->send(json_encode([
                    'status' => 1,
                    'message' => '消息发送成功',
                ]));
            }else{
                $connection->send(json_encode([
                    'status' => 0,
                    'message' => '消息发送失败,对方下线了哈',
                ]));
            }
        }
    };
    $textWorker->listen();

//            $output->writeln('用户onWorkerStart'  );
};

$worker->onConnect = function(TcpConnection $connection) use ($worker)
{
    // 指令输出
    // echo('用户:'.$worker->id.'-'.$connection->id.'链接成功');
    echo($connection->id.'ok');
};

$worker->onMessage = function ($connection, $data) use ($worker) {
    // 给connection临时设置一个lastMessageTime属性,用来记录上次收到消息的时间
    $connection->lastMessageTime = time();
    $data = json_decode($data, true);
    //绑定用户ID
    if ($data['type'] == 'bind' && isset($data['userId']) && !isset($connection->userId))
    {
        $connection->userId = $data['userId'];
        $worker->connectionList[$connection->userId][$connection->id] = $connection;
    }

    //聊天
    if ($data['type'] == 'text')
    {
        if (isset($worker->connectionList[$data['userToId']]))
        {
            $conns = $worker->connectionList[$data['userToId']];
            foreach ($conns as $conn){
                $conn->send('用户'.$data['userToId'].':'.$data['message']);
            }
        }else{
            $connection->send('我:对方下线了');
        }
    }

    //心跳接收并回应
    if ($data['type'] == 'ping')
    {
        $connection->send('peng');
    }
    //向客户端自己发送数据
    if (!empty($data['message'])){
        $connection->send('我:'.$data['message']);
    }
//            // 指令输出
//            $output->writeln('用户:'.$connection->id.',客户端接收到数据:'.json_encode($data)  );

};
// 客户端连接断开时,断开对应的链接连接
$worker->onClose = function(TcpConnection $connection) use ($worker)
{

    if(isset($connection->userId) && isset($worker->connectionList[$connection->userId]))
    {
        // 连接断开时删除映射
        if (count($worker->connectionList[$connection->userId]) == 1){
            unset($worker->connectionList[$connection->userId]);
        }else{
            unset($worker->connectionList[$connection->userId][$connection->id]);
        }
    }
    // 指令输出
    // echo('用户:'.$connection->id.'已经断开链接。'.$connection->userId);
};

// 运行worker
Worker::runAll();

客户端测试代码

index.html文件

代码语言:javascript
代码运行次数:0
复制
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebSocket Example</title>
</head>
<body>
    <p>用户:<input type="text" id="userToId" value="1000"></p>
    <p>内容:<input type="text" id="msg"></p>
    <p><button id="sendMessageButton">发送消息</button></p>

    <script>
        function getUrlParams() {
            const url = window.location.search
          const paramsRegex = /[?&]+([^=&]+)=([^&]*)/gi;
          const params = {};
          let match;
          while (match = paramsRegex.exec(url)) {
            params[match[1]] = match[2];
          }
          return params;
        }
        // 创建 WebSocket 连接
        const ws = new WebSocket('wss://hospital.s.cntmt.cn/websocket');
        let data = getUrlParams()
        // 连接打开时触发
        ws.onopen = function() {
            console.log('已连接到服务器.',data.userid);
            ws.send(JSON.stringify({
                type: 'bind',
                userId: data.userid,
            }));
            // 间隔30秒通知一次服务器我还在线
            setInterval(() => {
                ws.send(JSON.stringify({
                    type: 'ping'
                }));
            },50000);
        };

        // 收到服务器消息时触发
        ws.onmessage = function(event) {
            console.log(event.data);
            // 可以在这里添加更多的处理逻辑,比如更新页面内容
            // alert('Received from server: ' + event.data);
        };

        // 连接关闭时触发
        ws.onclose = function() {
            console.log('已断开与服务器的连接');
        };

        // 连接出错时触发
        ws.onerror = function(error) {
            console.error('WebSocket Error:', error);
        };

        // 获取按钮元素
        const sendMessageButton = document.getElementById('sendMessageButton');
        const msg = document.getElementById('msg');
        const userToId = document.getElementById('userToId');

        // 为按钮添加点击事件监听器
        sendMessageButton.addEventListener('click', function() {
            // 检查 WebSocket 连接是否已打开
            if (ws.readyState === WebSocket.OPEN) {
                // 发送消息给服务器'发送:' + msg.value
                ws.send(JSON.stringify({
                    type: 'text',
                    userToId: userToId.value,
                    message: msg.value,
                }));
                // console.log('Message sent to server: Hello Server!');
            } else {
                console.error('WebSocket未打开。就绪状态:', ws.readyState);
            }
        });

    </script>
</body>
</html>

服务器后端通知前端测试代码

send.php 文件

代码语言:javascript
代码运行次数:0
复制
<?php

// 建立socket连接到内部推送端口
//134.175.18.35:5678这个是workerman服务器地址
$client = stream_socket_client('tcp://134.175.18.35:5678', $errno, $errmsg, 1);
// 推送的数据,包含uid字段,表示是给这个uid推送
$data = [
    'type' => 'text',
    'userToId' => '1000',
    'message' => [
        'title'=>'baiti',
        'content'=>'neirong',
        'time'=>date('Y-m-d H:i:s')
    ]
];
// 发送数据,注意5678端口是Text协议的端口,Text协议需要在数据末尾加上换行符
fwrite($client, json_encode($data)."\n");
// 读取推送结果
echo fread($client, 8192);

服务器配置 nginx反向代理支持https

www.test.com站点配置可以跨服务器,配置下面代理地址

代码语言:javascript
代码运行次数:0
复制
location /websocket
{
  proxy_pass http://134.175.18.35:8484;
  proxy_redirect off;
  proxy_set_header Host $host;
  proxy_set_header X-Real_IP $remote_addr;
  proxy_set_header X-Forwarded-For $remote_addr:$remote_port;
  proxy_set_header X-Forwarded-Proto $scheme;
  proxy_http_version 1.1;
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection upgrade;
  break; 
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-02-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 开源技术小栈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概述
    • 主要特点
    • 应用场景
  • 预览
  • 安装
  • 服务器端代码
  • 客户端测试代码
  • 服务器后端通知前端测试代码
  • 服务器配置 nginx反向代理支持https
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档