commit 49ea1bb75d5c704e002917b178ee7cd8323cbc1e Author: jhx <133451314@qq.com> Date: Fri Aug 18 11:34:58 2023 +0800 upload diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..688d850 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea +composer.lock +vendor \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..b7b7d80 --- /dev/null +++ b/composer.json @@ -0,0 +1,15 @@ +{ + "name": "woon/workerman-rpc", + "require": { + "workerman/workerman": "^4.0", + "illuminate/support": "^8.60", + "ext-json": "*" + }, + "autoload": { + "psr-4": { + "Rpc\\": "src", + "Protocols\\": "protocols", + "Test\\": "test" + } + } +} diff --git a/docker/.env b/docker/.env new file mode 100644 index 0000000..0ef7bce --- /dev/null +++ b/docker/.env @@ -0,0 +1,12 @@ +### set local app dir there ####### +HOST_APP_DIR=../ +CONTAINER_APP_DIR=/usr/src/myapp +HOST_PORT=8001 +CONTAINER_PORT=8001 +INSTALL_COMPOSER=true +COMPOSER_INSTALLER=https://install.phpcomposer.com/installer +COMPOSER_MIRROR=https://mirrors.aliyun.com/composer/ +INSTALL_LIB_EVENT=true +CHANGE_AlPINE_SOURCE=true +CONTAINER_PACKAGE_URL=mirrors.aliyun.com +CONTAINER_NAME=workerman-rpc \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..49b0779 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,77 @@ +FROM php:7.4-cli-alpine3.13 +# Set working dir. +ARG CONTAINER_APP_DIR + +ARG timezone + +ENV TIMEZONE=${timezone:-"Asia/Shanghai"} \ + APP_ENV=prod \ + SCAN_CACHEABLE=(true) + + +# Change alpinelinux source. +# Reference: https://github.com/laradock/laradock +ARG CHANGE_AlPINE_SOURCE +ARG CONTAINER_PACKAGE_URL +RUN if [ $CHANGE_AlPINE_SOURCE ] ; then sed -i "s/dl-cdn.alpinelinux.org/${CONTAINER_PACKAGE_URL}/g" /etc/apk/repositories ; fi + +RUN uname -a && \ + apk update + +RUN docker-php-ext-install sockets pcntl && \ + docker-php-ext-install pdo_mysql + +RUN apk add --no-cache pcre-dev $PHPIZE_DEPS && \ + pecl install redis && \ + docker-php-ext-enable redis + +ARG INSTALL_LIB_EVENT +RUN if [ ${INSTALL_LIB_EVENT} = true ]; then \ + apk add libevent-dev openssl-dev libressl-dev && \ + pecl install event && \ + echo extension=event.so > /usr/local/etc/php/conf.d/event.ini \ +;fi + +# update +RUN set -ex \ + # show php version and extensions + && php -v \ + && php -m \ + # ---------- some config ---------- + && cd /usr/local/etc/php \ + # - config PHP + && { \ + echo "upload_max_filesize=128M"; \ + echo "post_max_size=128M"; \ + echo "memory_limit=1G"; \ + echo "date.timezone=${TIMEZONE}"; \ + } | tee conf.d/99_overrides.ini \ + # - config timezone + && ln -sf /usr/share/zoneinfo/${TIMEZONE} /etc/localtime \ + && echo "${TIMEZONE}" > /etc/timezone \ + # ---------- clear works ---------- + && rm -rf /var/cache/apk/* /tmp/* /usr/share/man \ + && echo -e "\033[42;37m Build Completed :).\033[0m\n" + +# Install composer and set mirror. +# Reference: https://pkg.phpcomposer.com/#how-to-install-composer +# Referencr: https://developer.aliyun.com/composer +ARG INSTALL_COMPOSER +ARG COMPOSER_INSTALLER +ARG COMPOSER_MIRROR +RUN if [ ${INSTALL_COMPOSER} = true ]; then \ + php -r "copy('${COMPOSER_INSTALLER}', 'composer-setup.php');" && \ + php composer-setup.php && \ + php -r "unlink('composer-setup.php');" && \ + mv composer.phar /usr/local/bin/composer && \ + composer config -g repo.packagist composer ${COMPOSER_MIRROR} \ +;fi + + +RUN apk add git zip vim bash + +# Expose port +ARG CONTAINER_PORT +EXPOSE ${CONTAINER_PORT} + +WORKDIR ${CONTAINER_APP_DIR} diff --git a/docker/Readme.md b/docker/Readme.md new file mode 100644 index 0000000..aa62e41 --- /dev/null +++ b/docker/Readme.md @@ -0,0 +1,24 @@ +# Set environment variables +Copy file **env-example** to **.env**, and change the item what you need. + +Change the **HOST_APP_DIR** env item, ensure where your webman app is. + +# Build image +Ensure you have install docker and docker-compose first. + +You can build you image by execute follow command: + +> **docker-compose up -d webman** + +# Run +Enter the container: +> **docker-compose exec webman bash** + +If you havn't init composer, you can run this command: +> **composer install** + +Run in front: +> **php start.php start** + +Run in backend: +> **php start.php start -d** \ No newline at end of file diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml new file mode 100644 index 0000000..ae084ea --- /dev/null +++ b/docker/docker-compose.yaml @@ -0,0 +1,28 @@ +version: '3' +services: + workerman-rpc: + build: + context: ./ + args: + - CONTAINER_PORT=${CONTAINER_PORT} + - CONTAINER_APP_DIR=${CONTAINER_APP_DIR} + - INSTALL_COMPOSER=${INSTALL_COMPOSER} + - COMPOSER_INSTALLER=${COMPOSER_INSTALLER} + - COMPOSER_MIRROR=${COMPOSER_MIRROR} + - INSTALL_LIB_EVENT=${INSTALL_LIB_EVENT} + - CONTAINER_PACKAGE_URL=${CONTAINER_PACKAGE_URL} + - CHANGE_AlPINE_SOURCE=${CHANGE_AlPINE_SOURCE} + ports: + - "9501:9501" + volumes: + - ${HOST_APP_DIR}:${CONTAINER_APP_DIR} + container_name: ${CONTAINER_NAME} + networks: + - network + stdin_open: true + tty: true + extra_hosts: + - "host.docker.internal:host-gateway" + +networks: + network: diff --git a/docker/env-example b/docker/env-example new file mode 100644 index 0000000..6311fce --- /dev/null +++ b/docker/env-example @@ -0,0 +1,11 @@ +### set local app dir there ####### +HOST_APP_DIR=../webman +CONTAINER_APP_DIR=/usr/src/myapp +HOST_PORT=8787 +CONTAINER_PORT=8787 +INSTALL_COMPOSER=true +COMPOSER_INSTALLER=https://install.phpcomposer.com/installer +COMPOSER_MIRROR=https://mirrors.aliyun.com/composer/ +INSTALL_LIB_EVENT=true +CHANGE_UBUNTU_SOURCE=true +UBUNTU_SOURCE=tsinghua \ No newline at end of file diff --git a/docker/event-3.0.5.tgz b/docker/event-3.0.5.tgz new file mode 100644 index 0000000..98cb576 Binary files /dev/null and b/docker/event-3.0.5.tgz differ diff --git a/docker/redis-5.3.4.tgz b/docker/redis-5.3.4.tgz new file mode 100644 index 0000000..6643f00 Binary files /dev/null and b/docker/redis-5.3.4.tgz differ diff --git a/docker/sources.sh b/docker/sources.sh new file mode 100644 index 0000000..eef35d4 --- /dev/null +++ b/docker/sources.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# Reference: https://github.com/laradock/laradock +set -xe; +if type "tee" 2>/dev/null && [ -n "${UBUNTU_SOURCE}" ]; then + SOURCE_PATH="/etc/apt/sources.list" + cp ${SOURCE_PATH} ${SOURCE_PATH}.bak && rm -rf ${SOURCE_PATH} + case "${UBUNTU_SOURCE}" in + "aliyun") + tee ${SOURCE_PATH} <<-'EOF' +deb http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse +deb http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse +deb http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse +deb http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse +deb http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse +deb-src http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse +deb-src http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse +deb-src http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse +deb-src http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse +deb-src http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse +EOF +;; + "tsinghua") + tee ${SOURCE_PATH} <<-'EOF' +deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic main restricted universe multiverse +deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic main restricted universe multiverse +deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-updates main restricted universe multiverse +deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-updates main restricted universe multiverse +deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-backports main restricted universe multiverse +deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-backports main restricted universe multiverse +deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-security main restricted universe multiverse +deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-security main restricted universe multiverse +deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-proposed main restricted universe multiverse +deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu/ bionic-proposed main restricted universe multiverse +EOF +;; + *) + echo "There is no ubuntu source detected!" + exit 1;; + esac +fi diff --git a/protocols/Json.php b/protocols/Json.php new file mode 100644 index 0000000..5d756ab --- /dev/null +++ b/protocols/Json.php @@ -0,0 +1,36 @@ +server = $server; + $this->className = $className; + } + + /** + * 防止对象实例被克隆 + * + * @return void + */ + private function __clone() + { + } + + /** + * 防止被反序列化 + * + * @return void + */ + private function __wakeup() + { + } + + public static function instance($server, $className) + { + if (!isset(self::$instances[$server])) { + self::$instances[$server][$className] = new self($server, $className); + } + + return self::$instances[$server][$className]; + } + + public function address($addressArray) + { + $this->addressArray = $addressArray; + return $this; + } + + public function __call($method, $arguments) + { + // 判断是否是异步发送 + if (0 === strpos($method, self::ASYNC_SEND_PREFIX)) { + $realMethod = substr($method, strlen(self::ASYNC_SEND_PREFIX)); + $instanceKey = $realMethod . serialize($arguments); + if (isset($this->$asyncInstances[$instanceKey])) { + throw new \Exception($this->className . "->$method(" . implode(',', $arguments) . ") have already been called"); + } + $this->asyncInstances[$instanceKey] = new self($this->server, $this->className); + return $this->asyncInstances[$instanceKey]->sendData($realMethod, $arguments); + } + // 如果是异步接受数据 + if (0 === strpos($method, self::ASYNC_RECV_PREFIX)) { + $realMethod = substr($method, strlen(self::ASYNC_RECV_PREFIX)); + $instanceKey = $realMethod . serialize($arguments); + if (!isset($this->asyncInstances[$instanceKey])) { + throw new \Exception($this->server . "@" . $this->className . "->asend_$realMethod(" . implode(',', $arguments) . ") have not been called"); + } + $tmp = $this->asyncInstances[$instanceKey]; + unset($this->asyncInstances[$instanceKey]); + return $tmp->recvData(); + } + // 同步发送接收 + $this->sendData($method, $arguments); + return $this->recvData(); + } + + public function sendData($method, $arguments) + { + $this->openConnection(); + + $binData = Json::encode(array( + 'class' => $this->className, + 'method' => $method, + 'param_array' => $arguments, + 'user' => $this->user, + 'passwd' => $this->passwd + )); + + if (fwrite($this->connection, $binData) !== strlen($binData)) { + throw new \Exception('Can not send data'); + } + return true; + } + + public function recvData() + { + $ret = fgets($this->connection); + $this->closeConnection(); + if (!$ret) { + throw new \Exception("recvData empty"); + } + return Json::decode($ret); + } + + protected function openConnection() + { + $addressArr = $this->addressArray; + if (empty($addressArr)) { + throw new \Exception("addressArr empty"); + } + + $address = $addressArr[array_rand($addressArr)]; + + $addressArr = explode('@', $address); + + if (isset($addressArr[1])) { + $account = explode(':', $addressArr[1]); + $this->user = Arr::get($account, 0, ''); + $this->passwd = Arr::get($account, 1, ''); + } + + $this->connection = stream_socket_client($address, $errNo, $errMsg); + if (!$this->connection) { + throw new \Exception("can not connect to $address , $errNo:$errMsg"); + } + stream_set_blocking($this->connection, true); + stream_set_timeout($this->connection, $this->timeout); + } + + protected function closeConnection() + { + fclose($this->connection); + $this->connection = null; + } +} \ No newline at end of file diff --git a/src/RpcServer.php b/src/RpcServer.php new file mode 100644 index 0000000..e10e15b --- /dev/null +++ b/src/RpcServer.php @@ -0,0 +1,101 @@ +config; + } + + /** + * @param mixed $config + */ + public function setConfig($config) + { + $this->config = $config; + return $this; + } + + public function createWorker() + { + $socketName = Arr::get($this->getConfig(), 'socket_name', 'json://0.0.0.0:9501'); + $worker = new Worker($socketName); + + $worker->count = Arr::get($this->getConfig(), 'process_count', 1); + + $worker->name = Arr::get($this->getConfig(), 'worker_name', 'rpc_server'); + + $worker->onMessage = function ($connection, $data) { + + if (empty($data['class']) || empty($data['method']) || !isset($data['param_array'])) { + // 发送数据给客户端,请求包错误 + return $connection->send(['code' => 400, 'msg' => 'bad request', 'data' => null]); + } + + $servicePath = Arr::get($this->getConfig(), 'service_path'); + $class = Arr::get($data, 'class'); + $method = Arr::get($data, 'method'); + $paramArray = Arr::get($data, 'param_array'); + + // 账号密码 + $account = Arr::get($this->getConfig(), 'account', []); + if (!empty($account)) { + $user = Arr::get($data, 'user'); + $passwd = Arr::get($data, 'passwd'); + if (empty($user) || empty($passwd) || !array_key_exists($user, $account) || ($passwd != Arr::get($account, $user))) { + return $connection->send(['code' => 400, 'msg' => 'user or passwd is error', 'data' => null]); + } + } + + $class = "{$servicePath}\\{$class}"; + + // 判断类对应文件是否载入 + if (class_exists($class) && method_exists($class, $method)) { + try { + $class = new $class; + $res = call_user_func_array([$class, $method], $paramArray); + return $connection->send(['code' => 0, 'msg' => 'ok', 'data' => $res]); + } catch (\Exception $e) { + $code = $e->getCode() ? $e->getCode() : 500; + return $connection->send(['code' => $code, 'msg' => $e->getMessage(), 'data' => $e]); + } + } + + $code = 404; + $msg = "class $class or method $method not found"; + // 发送数据给客户端 类不存在 + return $connection->send(['code' => $code, 'msg' => $msg, 'data' => null]); + + }; + + return $this; + } + + public function run() + { + Worker::runAll(); + } + + +} \ No newline at end of file diff --git a/test/Client.php b/test/Client.php new file mode 100644 index 0000000..c9c35a6 --- /dev/null +++ b/test/Client.php @@ -0,0 +1,30 @@ +address($addressArray); +$retSync = $TestClient->test('test_1'); + + +$retAsync = $TestClient->asend_test('test_2'); +$retAsync = $TestClient->arecv_test('test_2'); + + +var_dump($retSync); +var_dump($retAsync); + + +$userClient = \Rpc\RpcClient::instance('test', 'UserService'); +$userClient = $userClient->address($addressArray); +$retSync = $userClient->test('test_1'); + +var_dump($retSync); \ No newline at end of file diff --git a/test/Server.php b/test/Server.php new file mode 100644 index 0000000..83bac89 --- /dev/null +++ b/test/Server.php @@ -0,0 +1,18 @@ + "Test\\Service", + 'process_count' => 10, + 'account' => [ + 'user1' => 'passwd1' + ] +]; + + +(new \Rpc\RpcServer())->setConfig($config)->createWorker()->run(); \ No newline at end of file diff --git a/test/service/TestService.php b/test/service/TestService.php new file mode 100644 index 0000000..cadc03e --- /dev/null +++ b/test/service/TestService.php @@ -0,0 +1,17 @@ +