5 回复

记一次关于php8.0安装event问题

yitao 回复了问题 • 6 人关注 • 2021-09-07 10:37 • 来自相关话题

2 回复

【测压】腾讯云SCF镜像部署webman业务

adminv 发表了文章 • 2 个评论 • 210 次浏览 • 2021-08-29 18:28 • 来自相关话题

附加: 解释预留模式 统一Ab测压 总量1w,并发100 。 阿里/腾讯 预制 100个实例 结果,阿里云 , 腾讯云 阿里云 ...查看全部

附加: 解释预留模式


截图


统一Ab测压 总量1w,并发100 。


阿里/腾讯 预制 100个实例


结果,阿里云 ,


腾讯云


截图
截图


截图


阿里云


截图
截图


截图

4 回复

在iphone上使用Alpine Linux虚拟机跑webman

lalalala 回复了问题 • 5 人关注 • 2021-08-27 00:17 • 来自相关话题

14 回复

腾讯云SCF镜像部署webman业务

adminv 发表了文章 • 14 个评论 • 166 次浏览 • 2021-08-25 19:06 • 来自相关话题

https://hu60.cn/q.php/bbs.topic.101245.html
0 回复

webman克隆在gitee上,需要获得作者的同意吗?

回复

cai584887013 发起了问题 • 1 人关注 • 2021-08-25 11:44 • 来自相关话题

0 回复

Windows下的自动重载启动器

liruizhe1 发表了文章 • 0 个评论 • 134 次浏览 • 2021-08-23 10:54 • 来自相关话题

一个windows下的workerman程序启动器,适用于 Workerman 框架编写的程序,可以在程序代码变更时自动重启进程,让开发更高效。 功能列表 Gui 界面,简洁便捷 可以设定文件及文件夹监视 ...查看全部

一个windows下的workerman程序启动器,适用于 Workerman 框架编写的程序,可以在程序代码变更时自动重启进程,让开发更高效。


功能列表



  • Gui 界面,简洁便捷

  • 可以设定文件及文件夹监视

  • 可以设定文件扩展名过滤器

  • 延迟和合并处理文件变更,避免频繁重启进程

  • 单文件,无需安装。

  • MIT 协议


代码已开源 欢迎大家测试,如果喜欢请点个赞
https://github.com/Itinysun/WorkermanDev

12 回复

swoole和workerman性能对比

alpha 回复了问题 • 14 人关注 • 2021-08-19 14:59 • 来自相关话题

0 回复

关于 webman 重定向传递参数的使用

回复

zhanbai 发起了问题 • 1 人关注 • 2021-08-17 08:43 • 来自相关话题

0 回复

windows下thinkphp同时运行多个workerman

adminppper 发表了文章 • 0 个评论 • 181 次浏览 • 2021-07-17 21:05 • 来自相关话题

start "cmd窗口名称" /b php xxxx /b 不新建窗口打开

start "cmd窗口名称" /b php xxxx


/b 不新建窗口打开
截图

1 回复

关于onConnect和onMessage的一些理解

blogdaren 回复了问题 • 2 人关注 • 2021-07-02 13:35 • 来自相关话题

3 回复

分享一个使用php发起websocket的ws协议链接的类

小七他哥 发表了文章 • 3 个评论 • 236 次浏览 • 2021-06-25 18:23 • 来自相关话题

不多说废话,上代码: <?php class BadUriException extends Exception { } class ServerConnectException ...查看全部

不多说废话,上代码:


<?php

class BadUriException extends Exception {
}

class ServerConnectException extends Exception {
}

class HandshakeException extends Exception {
}

class BadFrameException extends Exception {
}

class SocketRWException extends Exception {
}

class WebSocketClient {

const PROTOCOL_WS = 'ws';
const PROTOCOL_WSS = 'wss';

const HTTP_HEADER_SEPARATION_MARK = "\r\n";
const HTTP_HEADER_END_MARK = "\r\n\r\n";

const UUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';

const PACKET_SIZE = (1 << 15);

// 还有后续帧
const OPCODE_CONTINUATION_FRAME = 0;
// 文本帧
const OPCODE_TEXT_FRAME = 1;
// 二进制帧
const OPCODE_BINARY_FRAME = 2;
// 关闭连接
const OPCODE_CLOSE = 8;
// ping
const OPCODE_PING = 9;
// pong
const OPCODE_PONG = 10;

const FRAME_LENGTH_LEVEL_1_MAX = 125;
const FRAME_LENGTH_LEVEL_2_MAX = 65535;

private $protocol;

private $host;

private $port;

private $path;

private $sock;

private $secWebSocketKey;

private $handshakePass = false;

private $readBuf;

private $currentReadBufLen;

private $readBufPos;

private $closed = false;

/**
* WebSocketClient constructor.
* @param string $wsUri
* @param float $connectTimeout 设置连接服务器的超时时间
* @param float $rwTimeout 设置读写数据的超时时间
* @throws Exception
*/
public function __construct($wsUri, $connectTimeout = 1.0, $rwTimeout = 5.0) {
$this->parseUri($wsUri);

$this->connect($connectTimeout, $rwTimeout);

$this->handshake();

$this->initReadBuf();
}

/**
* 解析websocket连接地址
* @param $wsUri
* @throws BadUriException
*/
protected function parseUri($wsUri) {
$uriData = parse_url($wsUri);
if (!$uriData) {
throw new BadUriException('不正确的ws uri格式', __LINE__);
}
if ($uriData['scheme'] != self::PROTOCOL_WS && $uriData['scheme'] != self::PROTOCOL_WSS) {
throw new BadUriException('ws的uri必须是以ws://或wss://开头', __LINE__);
}
$this->protocol = $uriData['scheme'];
$this->host = $uriData['host'];

if ($uriData['port']) {
$this->port = (int)$uriData['port'];
} else {
if ($this->protocol == self::PROTOCOL_WSS) {
$this->port = 443;
} else {
$this->port = 80;
}
}
$this->path = !empty($uriData['path']) ?: '/';
if (!empty($uriData['query'])) {
$this->path .= '?' . $uriData['query'];
}
if (!empty($uriData['fragment'])) {
$this->path .= '#' . $uriData['fragment'];
}
}

/**
* 连接websocket服务器
* @param float $timeout 连接服务器的超时时间
* @param float $rwTimeout 设置读写数据的超时时间
* @throws ServerConnectException
*/
protected function connect($timeout, $rwTimeout) {
$this->sock = stream_socket_client(
($this->protocol == self::PROTOCOL_WSS ? 'ssl://' : 'tcp://') . $this->host . ':' . $this->port,
$errno,
$errstr,
$timeout
);

if (!$this->sock) {

if ($errstr) {
throw new ServerConnectException('连接ws服务器失败:' . $errstr, $errno);
}

throw new ServerConnectException('连接ws服务器失败: 未知错误', __LINE__);
}

$this->setSockTimeout($rwTimeout);
}

/**
* 设置socket的读写超时时间
* @param float $seconds
*/
public function setSockTimeout($seconds) {
if (strpos($seconds, '.') !== false) {
$original = $seconds;
$seconds = (int)$seconds;
$microseconds = bcmul($original, 1000000, 0) - ($seconds * 1000000);
} else {
$microseconds = 0;
}
stream_set_timeout($this->sock, (int)$seconds, $microseconds);
}

/**
* @param $data
* @throws SocketRWException
*/
protected function writeToSock($data) {
if ($this->closed) {
throw new SocketRWException('连接已关闭, 不允许再发送消息', __LINE__);
}

$dataLen = strlen($data);
if ($dataLen > self::PACKET_SIZE) {
$dataPieces = str_split($data, self::PACKET_SIZE);
foreach ($dataPieces as $piece) {
$this->writeN($piece);
}
} else {
$this->writeN($data);
}
}

/**
* 向socket写入N个字节
* @param $str
* @throws SocketRWException
*/
protected function writeN($str) {
if ($this->closed) {
throw new SocketRWException('连接已关闭, 不允许再发送消息', __LINE__);
}

$len = strlen($str);
$writeLen = 0;
do {
if ($writeLen > 0) {
$str = substr($str, $writeLen);
}
$n = fwrite($this->sock, $str);
if ($n === false) {
$meta = stream_get_meta_data($this->sock);
if ($meta['timed_out']) {
throw new SocketRWException('向服务器发送数据超时', __LINE__);
}
throw new SocketRWException('无法发送数据,socket连接已断开?', __LINE__);
}
$writeLen += $n;
} while ($writeLen < $len);
}

/**
* 随机产生一个 Sec-WebSocket-Key
* @return false|string
*/
protected static function generateWsKey() {
return base64_encode(md5(uniqid() . mt_rand(1, 8192), true));
}

/**
* websocket握手
* @throws Exception
*/
protected function handshake() {
$this->secWebSocketKey = self::generateWsKey();
$headers = [
'GET ' . $this->path . ' HTTP/1.1',
'Host: ' . $this->host . ':' . $this->port,
'Upgrade: websocket',
'Connection: Upgrade',
'Sec-WebSocket-Key: ' . $this->secWebSocketKey,
'Sec-WebSocket-Version: 13',
];
$htmlHeader = implode(self::HTTP_HEADER_SEPARATION_MARK, $headers) . self::HTTP_HEADER_END_MARK;
$this->writeToSock($htmlHeader);
$response = '';
$end = false;
do {
$str = fread($this->sock, 8192);
if (strlen($str) == 0) {
break;
}
$response .= $str;
$end = strpos($response, self::HTTP_HEADER_END_MARK);
} while ($end === false);

if ($end === false) {
throw new HandshakeException('握手失败:握手响应不是标准的http响应', __LINE__);
}

$resHeader = substr($response, 0, $end);
$headers = explode(self::HTTP_HEADER_SEPARATION_MARK, $resHeader);

if (strpos($headers[0], '101') === false) {
throw new HandshakeException('握手失败:服务器返回http状态码不是101', __LINE__);
}
for ($i = 1; $i < count($headers); $i++) {
list($key, $val) = explode(':', $headers[$i]);
if (strtolower(trim($key)) == 'sec-websocket-accept') {
$accept = base64_encode(sha1($this->secWebSocketKey . self::UUID, true));
if (trim($val) != $accept) {
throw new HandshakeException('握手失败: sec-websocket-accept值校验失败', __LINE__);
}
$this->handshakePass = true;
break;
}
}
if (!$this->handshakePass) {
throw new HandshakeException('握手失败:缺少sec-websocket-accept http头', __LINE__);
}
}

/**
* @param int $opCode 帧类型
* @param string $playLoad 携带的数据
* @param bool $isMask 是否使用掩码
* @param int $status 关闭帧状态
* @return false|string
*/
protected function packFrame($opCode, $playLoad = '', $isMask = true, $status = 1000) {
$firstByte = 0x80 | $opCode;
if ($isMask) {
$secondByte = 0x80;
} else {
$secondByte = 0x00;
}

$playLoadLen = strlen($playLoad);
if ($opCode == self::OPCODE_CLOSE) {
// 协议规定关闭帧必须使用掩码
$isMask = true;
$playLoad = pack('CC', (($status >> 8) & 0xff), $status & 0xff) . $playLoad;
$playLoadLen += 2;
}
if ($playLoadLen <= self::FRAME_LENGTH_LEVEL_1_MAX) {
$secondByte |= $playLoadLen;
$frame = pack('CC', $firstByte, $secondByte);
} elseif ($playLoadLen <= self::FRAME_LENGTH_LEVEL_2_MAX) {
$secondByte |= 126;
$frame = pack('CCn', $firstByte, $secondByte, $playLoadLen);
} else {
$secondByte |= 127;
$frame = pack('CCJ', $firstByte, $secondByte, $playLoadLen);
}

if ($isMask) {
$maskBytes = [mt_rand(1, 255), mt_rand(1, 255), mt_rand(1, 255), mt_rand(1, 255)];
$frame .= pack('CCCC', $maskBytes[0], $maskBytes[1], $maskBytes[2], $maskBytes[3]);
if ($playLoadLen > 0) {
for ($i = 0; $i < $playLoadLen; $i++) {
$playLoad[$i] = chr(ord($playLoad[$i]) ^ $maskBytes[$i % 4]);
}
}
}

$frame .= $playLoad;

return $frame;
}

/**
* ping服务器
* @throws Exception
*/
public function ping() {
try {
$frame = $this->packFrame(self::OPCODE_PING, '', true);
$this->writeToSock($frame);
do {
$pong = $this->recv();
if ($pong->opcode == self::OPCODE_PONG) {
return true;
}
} while ($pong->opcode != self::OPCODE_PONG);
return false;
} catch (Exception $e) {
return false;
}
}

/**
* 响应服务器的ping
* @throws Exception
*/
public function pong() {
$frame = $this->packFrame(self::OPCODE_PONG, '', true);
$this->writeToSock($frame);
}

/**
* 主动关闭与服务器的连接
* @return bool
* @throws Exception
*/
public function close() {
$frame = $this->packFrame(self::OPCODE_CLOSE, '', true, 1000);

try {
$this->writeToSock($frame);
// 主动关闭需要再接收一次对端返回的确认消息
$wsData = $this->recv();
if ($wsData->opcode == self::OPCODE_CLOSE) {
return true;
}
} catch (\Throwable $e) {
} finally {
$this->closed = true;
stream_socket_shutdown($this->sock, STREAM_SHUT_RDWR);
}
return false;
}

/**
* ping服务器失败或服务器响应异常时调用,用于关闭socket资源
*/
public function abnormalClose() {
if (!$this->closed && $this->sock) {
$this->closed = true;
try {
stream_socket_shutdown($this->sock, STREAM_SHUT_RDWR);
} catch (\Throwable $e) {
}
}
}

/**
* 响应服务器的关闭消息
* @throws SocketRWException
*/
protected function replyClosure() {
$frame = $this->packFrame(self::OPCODE_CLOSE, '', true, 1000);
$this->writeToSock($frame);
$this->closed = true;
stream_socket_shutdown($this->sock, STREAM_SHUT_RDWR);
}

/**
* @param string $data 要发送的数据
* @param int $opCode 发送的数据类型 WebSocketClient::OPCODE_TEXT_FRAME 或 WebSocketClient::OPCODE_BINARY_FRAME
* @param bool $isMask 是否使用掩码,默认使用
* @throws Exception
*/
public function send($data, $opCode = self::OPCODE_TEXT_FRAME, $isMask = true) {
if ($opCode != self::OPCODE_TEXT_FRAME && $opCode != self::OPCODE_BINARY_FRAME) {
throw new \InvalidArgumentException('不支持的帧数据类型', __LINE__);
}

$frame = $this->packFrame($opCode, $data, $isMask);

$this->writeToSock($frame);
}

/**
* 初始化收取数据缓冲区
*/
private function initReadBuf() {
$this->readBuf = '';
$this->currentReadBufLen = 0;
$this->readBufPos = 0;
}

/**
* 从读取缓冲区中当前位置返回指定长度字符串
* @param int $len 返回长度
* @return bool|string
* @throws SocketRWException
*/
private function fetchStrFromReadBuf($len = 1) {
$target = $this->readBufPos + $len;

while ($target > $this->currentReadBufLen) {
if ($this->closed) {
throw new SocketRWException('连接已关闭, 不允许再收取消息', __LINE__);
}
$read = fread($this->sock, self::PACKET_SIZE);
if (!$read) {
$meta = stream_get_meta_data($this->sock);
if ($meta['timed_out']) {
throw new SocketRWException('读取服务器数据超时', __LINE__);
}
throw new SocketRWException('无法读取服务器数据,错误未知', __LINE__);
}
$this->readBuf .= $read;
$this->currentReadBufLen += strlen($read);
}
$str = substr($this->readBuf, $this->readBufPos, $len);
$this->readBufPos += $len;
return $str;
}

/**
* 返回读取缓冲区当前位置字符的ascii码
* @return int
* @throws SocketRWException
*/
private function fetchCharFromReadBuf() {
$str = $this->FetchStrFromReadBuf(1);
return ord($str[0]);
}

/**
* 丢弃读取缓冲区已处理的指定长度数据
* @param $len
*/
private function discardReadBuf($len) {
// 未处理的数据不会被丢弃
if ($len > $this->readBufPos) {
$len = $this->readBufPos;
}
if ($len > 0) {
$this->readBuf = substr($this->readBuf, $len);
$this->readBufPos -= $len;
$this->currentReadBufLen -= $len;
}
}

/**
* @return WsDataFrame
* @throws Exception
*/
public function recv() {
$dataFrame = $this->readFrame();
switch ($dataFrame->opcode) {

case self::OPCODE_PING:
$this->pong();
break;

case self::OPCODE_PONG:
break;

case self::OPCODE_TEXT_FRAME:
case self::OPCODE_BINARY_FRAME:
case self::OPCODE_CLOSE:
if ($dataFrame->fin == 0) {
do {
$continueFrame = $this->readFrame();
$dataFrame->playload .= $continueFrame->playload;
} while ($continueFrame->fin == 0);
}

if ($dataFrame->opcode == self::OPCODE_CLOSE) {
$this->replyClosure();
}
break;
default:
throw new BadFrameException('无法识别的frame数据', __LINE__);
break;
}
return $dataFrame;
}

/**
* 读取一个数据帧
* @return WsDataFrame
* @throws SocketRWException
*/
protected function readFrame() {
$firstByte = $this->fetchCharFromReadBuf();
$fin = ($firstByte >> 7);
$opcode = $firstByte & 0x0F;
$secondByte = $this->fetchCharFromReadBuf();
$isMasked = ($secondByte >> 7);
$dataLen = $secondByte & 0x7F;
if ($dataLen == 126) {
// 2字节无符号整形
$dataLen = ($this->fetchCharFromReadBuf() << 8) + $this->fetchCharFromReadBuf();
} elseif ($dataLen == 127) {
// 8字节无符号整形
$dataLen = $this->fetchStrFromReadBuf(8);
$res = unpack('Jlen', $dataLen);
if (isset($res['len'])) {
$dataLen = $res['len'];
} else {
$dataLen = (ord($dataLen[0]) << 56)
+ (ord($dataLen[1]) << 48)
+ (ord($dataLen[2]) << 40)
+ (ord($dataLen[3]) << 32)
+ (ord($dataLen[4]) << 24)
+ (ord($dataLen[5]) << 16)
+ (ord($dataLen[6]) << 8)
+ ord($dataLen[7]);
}
}

$data = '';
$status = 0;
if ($dataLen > 0) {
if ($isMasked) {
// 4字节掩码
$maskChars = $this->fetchStrFromReadBuf(4);
$maskSet = [ord($maskChars[0]), ord($maskChars[1]), ord($maskChars[2]), ord($maskChars[3])];
$data = $this->fetchStrFromReadBuf($dataLen);
for ($i = 0; $i < $dataLen; $i++) {
$data[$i] = chr(ord($data[$i]) ^ $maskSet[$i % 4]);
}
} else {
$data = $this->fetchStrFromReadBuf($dataLen);
}
if ($opcode == self::OPCODE_CLOSE) {
$status = (ord($data[0]) << 8) + ord($data[1]);
$data = substr($data, 2);
}
}

$this->discardReadBuf($this->readBufPos);

$dataFrame = new WsDataFrame();
$dataFrame->opcode = $opcode;
$dataFrame->fin = $fin;
$dataFrame->status = $status;
$dataFrame->playload = $data;
return $dataFrame;
}

/**
* __destruct
*/
public function __destruct() {
$this->abnormalClose();
}
}

/**
* websocket数据帧
* Class wsDataFrame
* @package library\util
*/
class WsDataFrame {
/**
* @var int $opcode
*/
public $opcode;

/**
* @var int $fin 标识数据包是否已结束
*/
public $fin;

/**
* @var int $status 关闭时的状态码,如果有的话
*/
public $status;

/**
* @var string 数据包携带的数据
*/
public $playload;
}

下面看看调用代码:


try {
$ws = new WebSocketClient('ws://' . $GLOBALS['ws_server_config']['host'] . ':' . $GLOBALS['ws_server_config']['port']);
$ws->send($data);
$frame = $ws->recv();
//echo "收到服务器响应数据:" . $frame->playload . PHP_EOL;
$ws->close();
} catch (\Exception $e) {
//echo "错误: ";
//var_dump($e->__toString());
}

这个其实也是我在网上找的代码,个人感觉比 workerman 官方的那个要简洁一些。


官方的方案是:http://doc.workerman.net/faq/as-wss-client.html


感觉这个好复杂,我要发起一个ws协议链接,还得起一个worker?感觉动作太大了。


我还是个新手,欢迎大家喷一下,我这个有问题没,谢谢。

0 回复

望能帮到你:《TCP IP详解卷1:协议 原书第2版》

liziyu 发表了文章 • 0 个评论 • 251 次浏览 • 2021-06-07 22:46 • 来自相关话题

希望对workerman开发有帮助,分享给大家! 如果有侵犯到版权之类的,可以联系本人或直接找本社区管理人员删除! 谢谢!! 链接: https://pan.baidu.com/s/1BNjeUIp3JKVPaohujzXt

希望对workerman开发有帮助,分享给大家!


如果有侵犯到版权之类的,可以联系本人或直接找本社区管理人员删除!


谢谢!!


链接: https://pan.baidu.com/s/1BNjeUIp3JKVPaohujzXtEg 密码: c5r3

1 回复

webname路由获取别名

askuiop 回复了问题 • 2 人关注 • 2021-06-01 15:05 • 来自相关话题

0 回复

记录一下getAllClientCount和getAllClientSessions为空的情况

回复

wanglong126 发起了问题 • 1 人关注 • 2021-05-28 11:17 • 来自相关话题

1 回复

workerman日志占用空间,导致磁盘爆满

朕震惊了 回复了问题 • 2 人关注 • 2021-05-25 10:42 • 来自相关话题

0 回复

在 k8s 里部署 workerman-chat

cloudbeer 发表了文章 • 0 个评论 • 270 次浏览 • 2021-05-25 10:20 • 来自相关话题

0 workerman 介绍 Workerman 是一个 PHP 编写的高性能的 socket 服务器通讯框架,用于快速开发各种网络应用,包括tcp的、udp的、长连接、短连接应用。 但他的官网并未提供 K8S 部署教程。下面咱们来用 ...查看全部

0 workerman 介绍


Workerman 是一个 PHP 编写的高性能的 socket 服务器通讯框架,用于快速开发各种网络应用,包括tcp的、udp的、长连接、短连接应用。


但他的官网并未提供 K8S 部署教程。下面咱们来用 K8S 部署一下 workerman-chat,他的源代码在 https://github.com/walkor/workerman-chat


本教程的包含了他的源代码(仅作为示例代码保存,代码版权属于原作者,正式部署请至 Workerman 官方下载),修改了部分代码,修改处会在下文中说明。


本文的 git 地址在 https://github.com/cloudbeer/workerman-chat-k8s


我们将按照如下的架构部署:
部署架构


docker 镜像均已经上传到 hub.docker.com,本文的脚本可以在腾讯云 TKE 中直接运行。


部署脚本如下:


kubectl apply -f ns.yaml
kubectl apply -f register.yaml
kubectl apply -f gateway.yaml
kubectl apply -f businessworker.yaml

1 编写 Dockerfile & 修改源代码


workerman 运行环境,需要安装 pcntl,代码如下。
这个 docker 包标记为:cloudbeer/workerman-base:1.0


FROM php:7.3

RUN docker-php-ext-configure pcntl --enable-pcntl \
&& docker-php-ext-install pcntl

我们使用分布式部署,所以需要修改 gateway 代码中的注册地址:127.0.0.1,但在容器中分布式部署,我们无法得知pod 的 ip,故改成动态获取 ip 的方式。
register 的 ip 也未知,但在 K8S 中,这个地址可以通过 service 发布出来,我们暂且命名这个服务为 register,gateway 和 businessworker 中注册中心的地址修改为 register 即可。


Applications/Chat/start_gateway.php,修改如下:


//....
// 分布式部署时请设置成内网ip(非127.0.0.1)
// $gateway->lanIp = '127.0.0.1';
$gateway->lanIp = getHostByName(getHostName());

//....

// 服务注册地址
// $gateway->registerAddress = '127.0.0.1:1236';
$gateway->registerAddress = 'register:1236';

//....

Applications/Chat/start_businessworker.php,修改如下:


//....

// 服务注册地址
// $worker->registerAddress = '127.0.0.1:1236';
$worker->registerAddress = 'register:1236';

//....

打包代码(别忘记 composer install 一下先):


FROM cloudbeer/workerman-base:1.0
RUN mkdir /app
COPY ./workerman-chat /app

2 部署 register


apiVersion: apps/v1
kind: Deployment
metadata:
name: workerman-chat-register
namespace: workerman
labels:
app: workerman-chat-register
spec:
replicas: 1
selector:
matchLabels:
app: workerman-chat-register
template:
metadata:
labels:
app: workerman-chat-register
spec:
containers:
- name: register
image: cloudbeer/workerman-code:1.0
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "500m"
ports:
- containerPort: 1236
protocol: TCP
command: [php, /app/Applications/Chat/start_register.php, start]
---
apiVersion: v1
kind: Service
metadata:
name: register
namespace: workerman
spec:
selector:
app: workerman-chat-register
ports:
- protocol: TCP
port: 1236
targetPort: 1236

3 部署 gateway


部署 gateway 的 pod,由于需要对外提供服务,使用了 LoadBalancer 的 service 发布服务。这里也可以创建 ingress 来发布服务。


apiVersion: apps/v1
kind: Deployment
metadata:
name: workerman-chat-gateway
namespace: workerman
labels:
app: workerman-chat-gateway
spec:
replicas: 1
selector:
matchLabels:
app: workerman-chat-gateway
template:
metadata:
labels:
app: workerman-chat-gateway
spec:
containers:
- name: gateway
image: cloudbeer/workerman-code:1.0
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "500m"
ports:
- containerPort: 7272
protocol: TCP
command: [php, /app/Applications/Chat/start_gateway.php, start]
---
apiVersion: v1
kind: Service
metadata:
name: gateway
namespace: workerman
spec:
type: LoadBalancer
selector:
app: workerman-chat-gateway
ports:
- protocol: TCP
port: 7272
targetPort: 7272

4 部署 businessworker


businessworker 在内部工作,只需要部署 pod 即可。


apiVersion: apps/v1
kind: Deployment
metadata:
name: workerman-chat-businessworker
namespace: workerman
labels:
app: workerman-chat-businessworker
spec:
replicas: 1
selector:
matchLabels:
app: workerman-chat-businessworker
template:
metadata:
labels:
app: workerman-chat-businessworker
spec:
containers:
- name: businessworker
image: cloudbeer/workerman-code:1.0
resources:
requests:
memory: "1Gi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "500m"
command: [php, /app/Applications/Chat/start_businessworker.php, start]

测试服务


workerman-chat 提供了一个 聊天室的 web 测试端。


下面我们在本地启动这个聊天室:
先修改 websocket 的目标地址,为 gateway 的 Loadbalancer 的地址(假设为 1.2.3.4),修改 Applications/Chat/Web/index.php 文件如下:



// ws = new WebSocket("ws://"+document.domain+":7272");
ws = new WebSocket("ws://1.2.3.4:7272");

本地启动:


php ./Applications/Chat/start_web.php start

打开浏览器:http://localhost:55151


多开几个聊天室,完美运行。


扩充实例


按照 workerman-chat 的文档,gateway 和 businessworker 可以扩展。register 为单实例,不能扩。


现在可以试着扩冲 businessworker 的数量:


kubectl scale --replicas=3 -f businessworker.yaml

或者直接修改 yaml 的 replicas 的数量。


扩展成功后,多开几个聊天室,就会发现 businessworker 的 pod 的日志开始接收聊天信息了。
测试的时候需要注意:只有新用户加入的时候,新扩展的 businessworker 才提供服务。


同理可扩充 gateway,gateway 的服务由 LoadBalancer 负载,经测试,聊天正常。


正式运行环境也可以使用 HPA 自动伸缩。

2 回复

nginx反向代理webman出现502错误 upstream prematurely closed

supereric 发表了文章 • 2 个评论 • 488 次浏览 • 2021-05-22 13:47 • 来自相关话题

nginx反向代理webman出现502错误 nginx 错误 2021/05/21 23:21:46 [error] 75037#0: *234 upstream prematurely closed connecti ...查看全部

nginx反向代理webman出现502错误


nginx 错误



2021/05/21 23:21:46 [error] 75037#0: *234 upstream prematurely closed connection while reading response header from upstream, client: 172.16.253.1, server: webman.cc, request: "GET / HTTP/1.1", upstream: "http://127.0.0.1:8080/";, host: "webman.cc"



nginx location配置为


location /
{
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header REMOTE-HOST $remote_addr;
add_header X-Cache $upstream_cache_status;
#Set Nginx Cache
add_header Cache-Control no-cache;
expires 12h;
}

将配置项


proxy_set_header Connection "Upgrade";

修改为


proxy_set_header Connection "Keep-Alive";

后问题解决。ws连接正常
和文档中不太一样有人可以解释一下问题吗

2 回复

Auto Route 注解路由 自动生成 OpenAPI 文档

qnnp 发表了文章 • 2 个评论 • 395 次浏览 • 2021-04-22 01:39 • 来自相关话题

Webman Auto Route 是一个基于 PHP 8 注解开发的一个Webman 扩展组件。 Webman Auto Route 可以做什么 为 Webman 项目的控制器提供注解路由功能 保留 Webm ...查看全部

Webman Auto Route 是一个基于 PHP 8 注解开发的一个Webman 扩展组件。



  • Webman Auto Route 可以做什么

  • 为 Webman 项目的控制器提供注解路由功能

  • 保留 Webman 路由中间件能力

  • 根据注解信息实时生成 OpenAPI 3.0 文档

  • 自带 Swagger UI 提供接口自测、对接

  • 根据注解信息自动验证过滤输入信息 ( 1.0.0开始提供 )


文档及项目地址

2 回复

webman tinkphp组件应用示例

weijer 发表了文章 • 2 个评论 • 579 次浏览 • 2021-04-09 11:19 • 来自相关话题

https://github.com/teamones-open/webman/tree/thinkphp
2 回复

写个workerman简易服务操作面板,使用服务模式运行

chongsheng 发表了文章 • 2 个评论 • 471 次浏览 • 2021-03-16 23:31 • 来自相关话题

1,先发下演示图片, 2,使用方法,下载后将文件解压到workerman目录下,,运行xxjs.exe 然后点"启动"按钮就可以了,运行前可以先看下配置.ini文件,如下图 ...查看全部

1,先发下演示图片,

截图


2,使用方法,下载后将文件解压到workerman目录下,,运行xxjs.exe
然后点"启动"按钮就可以了,运行前可以先看下配置.ini文件,如下图

文件很小 360有可能会杀,无毒,请放行

1 回复

webman AOP中start.php文件中初始化应该写完整命名空间

walkor 回复了问题 • 2 人关注 • 2020-12-28 09:49 • 来自相关话题

4 回复

关于libevent扩展升级php7遇到的问题

youwuku 回复了问题 • 3 人关注 • 2020-09-07 17:28 • 来自相关话题

1 回复

workerman 4.0.8版本 在nfs文件系统下的问题

walkor 回复了问题 • 2 人关注 • 2020-08-31 15:41 • 来自相关话题

2 回复

我被多线程逼疯了,我要发一次灌水贴,我不管,我要发

半壶水 回复了问题 • 3 人关注 • 2020-08-18 16:19 • 来自相关话题

1 回复

webman中 illuminate/database模型问题

walkor 回复了问题 • 3 人关注 • 2020-07-27 17:04 • 来自相关话题

2 回复

nodemon监听文件变化,并重启项目webman

dazhaozhao 发表了文章 • 2 个评论 • 740 次浏览 • 2020-06-19 10:09 • 来自相关话题

最近玩node.js发现了一个第三方模块,nodemon,挺有意思的,能监听文件变化,并自动重启web服务,就想着能不能用在webman上,就不用手动重启了,发现了老外写的一篇文章,动手试验了下,居然可以,非常惊喜,大伙可以试试结合着来,加快开发速度。链接放下 ...查看全部

最近玩node.js发现了一个第三方模块,nodemon,挺有意思的,能监听文件变化,并自动重启web服务,就想着能不能用在webman上,就不用手动重启了,发现了老外写的一篇文章,动手试验了下,居然可以,非常惊喜,大伙可以试试结合着来,加快开发速度。链接放下边:


https://sergeyzhuk.me/2019/09/16/live-reload-php-applications/

5 回复

小白使用workman指南

独孤紫龙 发表了文章 • 5 个评论 • 1610 次浏览 • 2020-06-08 15:21 • 来自相关话题

项目背景: 自己做的项目(基于TP5框架)需要个语音通知系统,只要我的后台接收到了信息,我就发送消息给微信小程序,于是就用到了workman。 使用工具: TP5(thinkphp5)+Workerman 整体思 ...查看全部

项目背景:


自己做的项目(基于TP5框架)需要个语音通知系统,只要我的后台接收到了信息,我就发送消息给微信小程序,于是就用到了workman。


使用工具:


TP5(thinkphp5)+Workerman


整体思路:


用workman的soket链接,当项目需要发消息的时候,发送给小程序,然后小程序根据传过来的数据做逻辑判断选择是否播报语音。


实现过程:


1、安装workman
(对于composer工具,作为标准小白是一直不会用的。其实用很简单,打开命令行工具,就是电脑自带的命令行,定位到自己的项目文件夹里去,然后按照workman文档里的命令粘贴进去,就可以安装。我的服务器是阿里云linux系统的,所以我执行的是以下两条命令。因为每个人的服务器都不一样,规则不同,所以执行的情况就不一样。我也遇到了错误,基本上都能百度到原因,差不多就是版本不匹配的原因。)


命令:阿里云下composer安装,先切换镜像:


composer config repo.packagist composer https://mirrors.aliyun.com/composer/


安装命令:


composer require topthink/think-worker=1.0.*


注意自己TP5的版本哈。以下是我个人理解的小白总结,如果有不对,大佬指正哈:


接下来根据版本选择composer:


tp5.0-worker1.0
tp5.1-worker2.0
tp6-worker3.o


当你看到自己项目里vendor文件夹里多出来俩文件夹:topthink和workman,就是成功了。


2、启动workman


在项目的根目录,也就是和application同级的目录写一个启动文件,通过命令行执行。


<?php
// +----------------------------------------------------------------------
// | Workerman启动页
// | Author: Worker fan
// +----------------------------------------------------------------------

define('APP_PATH', __DIR__ . '/application/');
define('BIND_MODULE','worker/Worker');
// 加载框架引导文件
require __DIR__ . '/thinkphp/start.php';

这个文件随便命名,我的叫service.php


然后构建服务器的处理消息模块,按照上面文件写的规则建立控制器。


<?php
/*
workerman的控制器
Author:workerfan
*/

namespace app\worker\controller;
use think\worker\Server;

class Worker extends Server
{
protected $socket = 'websocket://0.0.0.0:9383';

/**
* 收到信息
* @param $connection
* @param $data
*/
public function onMessage($connection, $data)
{
$connection->send('我收到你的信息了');
}

/**
* 当连接建立时触发的回调函数
* @param $connection
*/
public function onConnect($connection)
{

}

/**
* 当连接断开时触发的回调函数
* @param $connection
*/
public function onClose($connection)
{

}

/**
* 当客户端的连接上发生错误时触发
* @param $connection
* @param $code
* @param $msg
*/
public function onError($connection, $code, $msg)
{
echo "error $code $msg\n";
}

/**
* 每个进程启动
* @param $worker
*/
public function onWorkerStart($worker)
{

}
}

上面是个文档里的代码,我对于workerman的需求比较简单,所以就用了最精简的控制器。因为是小程序通信,所以监听的是:0.0.0.0,如果是项目内通信,可以监听:127.0.0.1。端口自己随便定义,只要不被占用就行。


写好上面这些文件,就可以启动了。


启动命令两种:


php service.php start (这种启动会随着终端断开而断开)


php service.php start -d (这种启动就会一直保持在线,除非手动关停,所以叫守护进程启动,其实字面理解就能理解。)


一般都是第二种命令启动。


3、微信小程序链接workman


这一步费时挺多,因为啥都不懂,所以很狂暴。


其实都是挺简单的问题,就是小白啥都不懂,所以得一点点自己慢慢找线索。


因为每一个小白的服务器配置都不一样,所以遇到的问题也五花八门因此也没有固定回答。


这里我就说两个可能是最通用的小白问题吧。


首先是微信小程序的代码,很简单:


//连接socket服务器
wx.connectSocket({
  url: 'wss://www.*************.com:443/wss',
success:(function(){
console.log("发送请求");
})  
});  
//socket打开时执行的事情
wx.onSocketOpen(function(res) { 
   wx.sendSocketMessage('Hellow,service');  
   console.log("给服务器发一个问候");  
});
wx.onSocketError(function(errMsg){
console.log("错误:".errMsg);
})
//服务器给用户发信息触发的事件          
wx.onSocketMessage(function(res) {  
  console.log("收到服务端的消息:" + res.data);  
});  

首先微信小程序socket是只能和https进行通讯的,所以网站必须是https的。配置https网站如果你用的是宝塔的管理平台就非常简单了,只需要到自己网站下找到ssl配置把证书给粘贴进去就行。然后把workman里关于转发的服务器配置给粘贴到自己的服务器配置上去就可以了。


这里会有俩常见问题。


1)端口没有放行,会有连接超时的警告或者错误。


宝塔的管理虽然也可以放行端口,但是貌似不是绝对,到阿里云的控制台的安全规则里面没有发现端口被放行,于是总是超时。


所以workman开了的时候,要先用命令测试下,自己的端口能不能访问到。


2) 客户端访问地址的书写错误,会导致ssl协议不正确的错误。


因为对配置文件的内容有误解,一开始访问的地址写的都是自己配置的端口号。后来知道了原理,nginx服务器监听的是443端口,自己修改的端口是从443转发到上面去的。所以url地址应该是:


wss://****.com:443/wss


而不是wss://****.com:3344/wss


这点很重要,估计能解决大把小白的疑惑。


做完以上这些,微信小程序就能成功和workman通信了,nice.


总结:


workman是一个很棒的php框架,就和它介绍自己一样,填充了php关于socket的很多空白,相信它将来一定会越来越好。对于我们这类的小白,因为基础知识的欠缺,所以有些答案就没有真正解决问题。其实,控制台回馈的错误就很好的告诉了问题的所在,只要静下心来一步步地根据错误去解决问题,最终都会有一个正确的答案的。以上。就是作为小白总结的全部内容了,希望能对别人有所帮助。


最后,感谢workman!

2 回复

thinkphp5整合workerman,tp5整合workerman,使用原汁原味的workerman

回复

a392223903 回复了问题 • 2 人关注 • 2020-04-17 22:52 • 来自相关话题

1 回复

做了一个windows下监控文件修改自动重载workerman的工具

damao 回复了问题 • 2 人关注 • 2020-03-20 10:50 • 来自相关话题

1 回复

用workerman代替redis

six 回复了问题 • 3 人关注 • 2020-02-27 10:03 • 来自相关话题

1 回复

jsonRPC调用加载动态对象

walkor 回复了问题 • 2 人关注 • 2020-02-06 17:15 • 来自相关话题

1 回复

实测GatewayWorker性能问题

智佳思远 回复了问题 • 2 人关注 • 2020-01-05 15:00 • 来自相关话题

3 回复

让windows开发支持代码热更新,workerman-filemonitor for windows

lxping 发表了文章 • 3 个评论 • 1259 次浏览 • 2019-12-31 02:05 • 来自相关话题

workerman-filemonitor只能用于linux,但是我们平时开发都是用的windows。基于workerman-filemonitor二次修改,可完美实现windwos开发时的代码热更新。 经过测试,windows下不能直接在file ...查看全部

workerman-filemonitor只能用于linux,但是我们平时开发都是用的windows。基于workerman-filemonitor二次修改,可完美实现windwos开发时的代码热更新。


经过测试,windows下不能直接在filemonitor启动的进程中获取我们需要的父进程,所以需要在你启动的gateway文件中添加如下代码(该代码一定要放在Worker::runAll()之前)


require_once __DIR__ . '/FileMonitor.php';
new FileMonitor($web, 'www', 5);

FileMonitor.php


<?php

use \Workerman\Worker;
use \Workerman\Lib\Timer;

/**
* workerman-filemonitor for windows
*
* 监控文件更新并自动reload workerman
*
* 使用方法:
* require_once __DIR__ . '/FileMonitor.php';
* new FileMonitor($worker, $dir, $timer);
*/
class FileMonitor
{
//待监听的项目目录
private $_monitor_dir = '';

//热更新间隔时间,默认3s
private $_interval = 0;

//最后一次同步时间
private $_last_time = 0;

function __construct ($worker, $dir, $timer = 3)
{
// watch Applications catalogue
$this->_monitor_dir = __DIR__ .'/'. $dir;

$this->_interval = $timer;

$this->_last_time = time();

// Emitted when data received
$worker->reloadable = false;

// Emitted when data received
$worker->onWorkerStart = function()
{
// watch files only in daemon mode
if (Worker::$daemonize === false)
{
// chek mtime of files per second
Timer::add($this->_interval, [$this, 'monitor']);
}
};
}

//监听器,kill进程
public function monitor ()
{
// recursive traversal directory
$iterator = new RecursiveDirectoryIterator($this->_monitor_dir);
$iterator = new RecursiveIteratorIterator($iterator);

foreach ($iterator as $file)
{
// only check php files
if (pathinfo($file, PATHINFO_EXTENSION) != 'php') continue;

// check mtime
if ($this->_last_time < $file->getMTime())
{
exec('taskkill -f -pid '. getmypid());
$this->_last_time = $file->getMTime();
return true;
}
}
}
}
0 回复

让start_for_win.bat自动识别start文件

lxping 发表了文章 • 0 个评论 • 1146 次浏览 • 2019-12-31 01:55 • 来自相关话题

把gatewayworker示例文件start_for_win.bat替换为如下代码,即可自动识别项目中的start_开头的php文件,仅限windows。 代码中的YourApp为你的项目文件夹 @echo off set ...查看全部

把gatewayworker示例文件start_for_win.bat替换为如下代码,即可自动识别项目中的start_开头的php文件,仅限windows。


代码中的YourApp为你的项目文件夹


@echo off
setlocal enabledelayedexpansion
set app=Applications\YourApp
for /f "delims=" %%i in ('dir %app%\start_*.php /b') do (
set FILES=!FILES! %app%\%%i
)
php !FILES!
pause
5 回复

16进制字符串转换为32位单精度浮点数

flyingfish 发表了文章 • 5 个评论 • 1880 次浏览 • 2019-08-14 16:52 • 来自相关话题

日前,想用gatewayworker接收设备发过来的信息,但是发现php中没有直接将16进制字符串转换为32位单精度浮点数的函数,在网上查了许久,查到了这个 http://www.zhanglirong.cn/article/index/cid/1/id/69 ...查看全部

日前,想用gatewayworker接收设备发过来的信息,但是发现php中没有直接将16进制字符串转换为32位单精度浮点数的函数,在网上查了许久,查到了这个 http://www.zhanglirong.cn/article/index/cid/1/id/69.html
[code]$a = '4145C28F';
$v = hexdec($a);
$x = ($v & ((1 << 23) - 1)) + (1 << 23)  ($v >> 31 | 1);
$exp = ($v >> 23 & 0xFF) - 127;
$res = $x 
 pow(2, $exp - 23);
if(($v >> 31) == 1){
    $res = -$res;
}
echo $res;[/code]
对这部分代码的理解的前提是需要了解ieee 754标准中浮点数表示法:http://c.biancheng.net/view/314.html
$v = hexdec($a) ;将字符串$a放到一个32bit的二进制串里:‭01000001010001011100001010001111‬
$x = ($v & ((1 << 23) - 1)) + (1 << 23) ($v >> 31 | 1);取出尾数,并在最左边添加1。
$exp = ($v >> 23 & 0xFF) - 127; 获取阶码。
$res = $x
pow(2, $exp - 23); 根据阶码移动小数点的位置,我觉得这里理解起来比较费劲,我的理解是$x可以理解为一个整数,pow(2,$exp-23)为该整数需要缩小的倍数。比如对于十进制数1000.00来说,缩小两倍,那么小数点就往左移两位。
后面用于判断符号的代码可用a?b:c三目运算符更简洁。
 

1 回复

php毫秒定时器,基于event扩展

six 回复了问题 • 3 人关注 • 2019-08-14 14:33 • 来自相关话题

4 回复

php毫秒定时器,也支持win

dazhaozhao 回复了问题 • 5 人关注 • 2019-08-13 14:52 • 来自相关话题

7 回复

php终极数据缓存,比redis、GlobalData等快200倍以上,极致性能

dingfei 发表了文章 • 7 个评论 • 3216 次浏览 • 2019-05-16 21:19 • 来自相关话题

一、效果:每秒读取2000万条。写入2200万条。cpu开销很小二、原理:1.将数据以数组方式存储在内存中,php进程需要数据时直接通过内存地址访问数据,没有任何IO开销以及CPU开销。 三、具体实现:1.利用linux的写时复制技术。运行php主 ...查看全部

一、效果:每秒读取2000万条。写入2200万条。cpu开销很小二、原理:1.将数据以数组方式存储在内存中,php进程需要数据时直接通过内存地址访问数据,没有任何IO开销以及CPU开销。
三、具体实现:1.利用linux的写时复制技术。运行php主进程一次性读取所有的数据保存到数组中,然后通过workerman监听端口。每次收到请求时:创建一个子进程去执行任务,执行完成之后子进程自动结束。
      2.子进程完全共享主进程的数组,不消耗内存。
四、代码[code] ini_set('memory_limit','5872M'); // 临时设置最大内存占用为5G
$a = [[[[]]]];
$start = microtime(true);
for ($i=0; $i<=200; $i++) {
for ($j=0; $j<=100; $j++) {
for ($k=0; $k<=1000; $k++) {
$a[$i][$j][$k] = '我是一条数据';
}
}
}
// 2000万数据,时间1秒,消耗内存720M。 相当于1000万-0.5秒-360M
echo microtime(true) - $start, "s。主进程写入完成,当前内存:" . (memory_get_usage() / 1024 / 1024) . "MB
";
$start = microtime(true);
for ($i=0; $i<=200; $i++) {
for ($j=0; $j<=100; $j++) {
for ($k=0; $k<=1000; $k++) {
$m = $a[$i][$j][$k] ;
}
}
}
echo microtime(true) - $start, "s。主进程读取完成,当前内存:" . (memory_get_usage() / 1024 / 1024) . "MB
";
$start = microtime(true);
$intNum= 1; // 进程总数
$pids= array(); // 进程PID数组
for($i= 0; $i<$intNum; $i++)
{
$pids[$i] = pcntl_fork();// 产生子进程,而且从当前行之下开试运行代码,而且继承父进程的所有变量
if($pids[$i] == -1){echo"couldn't fork". "\n";
}elseif(!$pids[$i]){
$m= 0;
$start = microtime(true);
for ($i=0; $i<=200; $i++) {
for ($j=0; $j<=100; $j++) {
for ($k=0; $k<=1000; $k++) {
$m = $a[$i][$j][$k] ;
}
}
}
echo microtime(true) - $start, "s。子进程读取完成,当前内存:" . (memory_get_usage() / 1024 / 1024) . "MB
";


            sleep(100);
exit(0);//子进程要exit否则会进行递归多进程,父进程不要exit否则终止多进程
}
}
echo microtime(true) - $start, "s。子进程创建完成,当前内存:" . (memory_get_usage() / 1024 / 1024) . "MB

";
sleep(200);[/code]
五:代码运行结果:
1.0883929729462s。主进程写入完成,当前内存:719.90679168701MB
0.66096806526184s。主进程读取完成,当前内存:719.90679168701MB
0.0097589492797852s。子进程创建完成,当前内存:719.90715026855MB
0.72923994064331s。子进程读取完成,当前内存:719.90715026855MB

0 回复

GatewayWorker分布式部署时的Gateway连接失败解决办法

caiqy 发表了文章 • 0 个评论 • 1667 次浏览 • 2019-05-16 10:26 • 来自相关话题

问题场景: 分布式部署gateway时,businessworker频繁从gateway查询数据(如 getSession, getOnlinexxx, getxxx),当用户量上升到一定程度会出现unable to connect to tcp: ...查看全部

问题场景:
分布式部署gateway时,businessworker频繁从gateway查询数据(如 getSession, getOnlinexxx, getxxx),当用户量上升到一定程度会出现unable to connect to tcp://xxxx 或者 can not to conect to tcp://xxxx,导致用户请求未处理,如果未在外层捕获异常会导致businessworker进程重启
 
原因猜测:
getxxx在Lib/Gateway.php中都是用的短链接,每一次请求都会创建新的连接,两台服务器之间建立连接频率过快 
解决办法:
起初发现netstats中存在几千个连接gateway内部端口的TIME_WAIT连接,就加大了TIME_WAIT最大容量,开启了tcp_tw_reuse,用户量上去后,TIME_WAIT不多了,但还是出现了上面的问题
最终通过修改Lib/Gateway.php的代码,使用长连接解决了问题
 

启动文件中增加如下代码
[code]Lib\Gateway::$persistentConnection = true;[/code]

0 回复

基于GatewayWorker+Vue所写的聊天室

回复

搬砖屌丝 发起了问题 • 2 人关注 • 2018-12-07 14:05 • 来自相关话题