Coder Social home page Coder Social logo

grpc's Introduction

Swoole Logo
Swoole is an event-driven, asynchronous, coroutine-based concurrency library with high performance for PHP.

lib-swoole ext-swoole test-linux Frameworks Tests codecov

Twitter Discord Latest Release License Coverity Scan Build Status

⚙️ Quick Start

Run Swoole program by Docker

docker run --rm phpswoole/swoole "php --ri swoole"

For details on how to use it, see: How to Use This Image.

Documentation

https://wiki.swoole.com/

HTTP Service

$http = new Swoole\Http\Server('127.0.0.1', 9501);
$http->set(['hook_flags' => SWOOLE_HOOK_ALL]);

$http->on('request', function ($request, $response) {
    $result = [];
    Co::join([
        go(function () use (&$result) {
            $result['google'] = file_get_contents("https://www.google.com/");
        }),
        go(function () use (&$result) {
            $result['taobao'] = file_get_contents("https://www.taobao.com/");
        })
    ]);
    $response->end(json_encode($result));
});

$http->start();

Concurrency

Co\run(function() {
    Co\go(function() {
        while(1) {
            sleep(1);
            $fp = stream_socket_client("tcp://127.0.0.1:8000", $errno, $errstr, 30);
            echo fread($fp, 8192), PHP_EOL;
        }
    });

    Co\go(function() {
        $fp = stream_socket_server("tcp://0.0.0.0:8000", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN);
        while(1) {
            $conn = stream_socket_accept($fp);
            fwrite($conn, 'The local time is ' . date('n/j/Y g:i a'));
        }
    });

    Co\go(function() {
        $redis = new Redis();
        $redis->connect('127.0.0.1', 6379);
        while(true) {
            $redis->subscribe(['test'], function ($instance, $channelName, $message) {
                echo 'New redis message: '.$channelName, "==>", $message, PHP_EOL;
            });
        }
    });

    Co\go(function() {
        $redis = new Redis();
        $redis->connect('127.0.0.1', 6379);
        $count = 0;
        while(true) {
            sleep(2);
            $redis->publish('test','hello, world, count='.$count++);
        }
    });
});

Runtime Hook

Swoole hooks the blocking io function of PHP at the bottom layer and automatically converts it to a non-blocking function, so that these functions can be called concurrently in coroutines.

Supported extension/functions

  • ext-curl (Support symfony and guzzle)
  • ext-redis
  • ext-mysqli
  • ext-pdo_mysql
  • ext-pdo_pgsql
  • ext-pdo_sqlite
  • ext-pdo_oracle
  • ext-pdo_odbc
  • stream functions (e.g. stream_socket_client/stream_socket_server), Supports TCP/UDP/UDG/Unix/SSL/TLS/FileSystem API/Pipe
  • ext-sockets
  • ext-soap
  • sleep/usleep/time_sleep_until
  • proc_open
  • gethostbyname/shell_exec/exec
  • fread/fopen/fsockopen/fwrite/flock

🛠 Develop & Discussion

💎 Awesome Swoole

Project Awesome Swoole maintains a curated list of awesome things related to Swoole, including

  • Swoole-based frameworks and libraries.
  • Packages to integrate Swoole with popular PHP frameworks, including Laravel, Symfony, Slim, and Yii.
  • Books, videos, and other learning materials about Swoole.
  • Debugging, profiling, and testing tools for developing Swoole-based applications.
  • Coroutine-friendly packages and libraries.
  • Other Swoole related projects and resources.

✨ Event-based

The network layer in Swoole is event-based and takes full advantage of the underlying epoll/kqueue implementation, making it really easy to serve millions of requests.

Swoole 4.x uses a brand new engine kernel and now it has a full-time developer team, so we are entering an unprecedented period in PHP history which offers a unique possibility for rapid evolution in performance.

⚡ Coroutine

Swoole 4.x or later supports the built-in coroutine with high availability, and you can use fully synchronized code to implement asynchronous performance. PHP code without any additional keywords, the underlying automatic coroutine-scheduling.

Developers can understand coroutines as ultra-lightweight threads, and you can easily create thousands of coroutines in a single process.

MySQL

Concurrency 10K requests to read data from MySQL takes only 0.2s!

$s = microtime(true);
Co\run(function() {
    for ($c = 100; $c--;) {
        go(function () {
            $mysql = new Swoole\Coroutine\MySQL;
            $mysql->connect([
                'host' => '127.0.0.1',
                'user' => 'root',
                'password' => 'root',
                'database' => 'test'
            ]);
            $statement = $mysql->prepare('SELECT * FROM `user`');
            for ($n = 100; $n--;) {
                $result = $statement->execute();
                assert(count($result) > 0);
            }
        });
    }
});
echo 'use ' . (microtime(true) - $s) . ' s';

Mixed server

You can create multiple services on the single event loop: TCP, HTTP, Websocket and HTTP2, and easily handle thousands of requests.

function tcp_pack(string $data): string
{
    return pack('N', strlen($data)) . $data;
}
function tcp_unpack(string $data): string
{
    return substr($data, 4, unpack('N', substr($data, 0, 4))[1]);
}
$tcp_options = [
    'open_length_check' => true,
    'package_length_type' => 'N',
    'package_length_offset' => 0,
    'package_body_offset' => 4
];
$server = new Swoole\WebSocket\Server('127.0.0.1', 9501, SWOOLE_BASE);
$server->set(['open_http2_protocol' => true]);
// http && http2
$server->on('request', function (Swoole\Http\Request $request, Swoole\Http\Response $response) {
    $response->end('Hello ' . $request->rawcontent());
});
// websocket
$server->on('message', function (Swoole\WebSocket\Server $server, Swoole\WebSocket\Frame $frame) {
    $server->push($frame->fd, 'Hello ' . $frame->data);
});
// tcp
$tcp_server = $server->listen('127.0.0.1', 9502, SWOOLE_TCP);
$tcp_server->set($tcp_options);
$tcp_server->on('receive', function (Swoole\Server $server, int $fd, int $reactor_id, string $data) {
    $server->send($fd, tcp_pack('Hello ' . tcp_unpack($data)));
});
$server->start();

Coroutine clients

Whether you DNS query or send requests or receive responses, all of these are scheduled by coroutine automatically.

go(function () {
    // http
    $http_client = new Swoole\Coroutine\Http\Client('127.0.0.1', 9501);
    assert($http_client->post('/', 'Swoole Http'));
    var_dump($http_client->body);
    // websocket
    $http_client->upgrade('/');
    $http_client->push('Swoole Websocket');
    var_dump($http_client->recv()->data);
});
go(function () {
    // http2
    $http2_client = new Swoole\Coroutine\Http2\Client('localhost', 9501);
    $http2_client->connect();
    $http2_request = new Swoole\Http2\Request;
    $http2_request->method = 'POST';
    $http2_request->data = 'Swoole Http2';
    $http2_client->send($http2_request);
    $http2_response = $http2_client->recv();
    var_dump($http2_response->data);
});
go(function () use ($tcp_options) {
    // tcp
    $tcp_client = new Swoole\Coroutine\Client(SWOOLE_TCP);
    $tcp_client->set($tcp_options);
    $tcp_client->connect('127.0.0.1', 9502);
    $tcp_client->send(tcp_pack('Swoole Tcp'));
    var_dump(tcp_unpack($tcp_client->recv()));
});

Channel

Channel is the only way for exchanging data between coroutines, the development combination of the Coroutine + Channel is the famous CSP programming model.

In Swoole development, Channel is usually used for implementing connection pool or scheduling coroutine concurrent.

The simplest example of a connection pool

In the following example, we have a thousand concurrently requests to redis. Normally, this has exceeded the maximum number of Redis connections setting and will throw a connection exception, but the connection pool based on Channel can perfectly schedule requests. We don't have to worry about connection overload.

class RedisPool
{
    /**@var \Swoole\Coroutine\Channel */
    protected $pool;

    /**
     * RedisPool constructor.
     * @param int $size max connections
     */
    public function __construct(int $size = 100)
    {
        $this->pool = new \Swoole\Coroutine\Channel($size);
        for ($i = 0; $i < $size; $i++) {
            $redis = new \Swoole\Coroutine\Redis();
            $res = $redis->connect('127.0.0.1', 6379);
            if ($res == false) {
                throw new \RuntimeException("failed to connect redis server.");
            } else {
                $this->put($redis);
            }
        }
    }

    public function get(): \Swoole\Coroutine\Redis
    {
        return $this->pool->pop();
    }

    public function put(\Swoole\Coroutine\Redis $redis)
    {
        $this->pool->push($redis);
    }

    public function close(): void
    {
        $this->pool->close();
        $this->pool = null;
    }
}

go(function () {
    $pool = new RedisPool();
    // max concurrency num is more than max connections
    // but it's no problem, channel will help you with scheduling
    for ($c = 0; $c < 1000; $c++) {
        go(function () use ($pool, $c) {
            for ($n = 0; $n < 100; $n++) {
                $redis = $pool->get();
                assert($redis->set("awesome-{$c}-{$n}", 'swoole'));
                assert($redis->get("awesome-{$c}-{$n}") === 'swoole');
                assert($redis->delete("awesome-{$c}-{$n}"));
                $pool->put($redis);
            }
        });
    }
});

Producer and consumers

Some Swoole's clients implement the defer mode for concurrency, but you can still implement it flexible with a combination of coroutines and channels.

go(function () {
    // User: I need you to bring me some information back.
    // Channel: OK! I will be responsible for scheduling.
    $channel = new Swoole\Coroutine\Channel;
    go(function () use ($channel) {
        // Coroutine A: Ok! I will show you the github addr info
        $addr_info = Co::getaddrinfo('github.com');
        $channel->push(['A', json_encode($addr_info, JSON_PRETTY_PRINT)]);
    });
    go(function () use ($channel) {
        // Coroutine B: Ok! I will show you what your code look like
        $mirror = Co::readFile(__FILE__);
        $channel->push(['B', $mirror]);
    });
    go(function () use ($channel) {
        // Coroutine C: Ok! I will show you the date
        $channel->push(['C', date(DATE_W3C)]);
    });
    for ($i = 3; $i--;) {
        list($id, $data) = $channel->pop();
        echo "From {$id}:\n {$data}\n";
    }
    // User: Amazing, I got every information at earliest time!
});

Timer

$id = Swoole\Timer::tick(100, function () {
    echo "⚙️ Do something...\n";
});
Swoole\Timer::after(500, function () use ($id) {
    Swoole\Timer::clear($id);
    echo "⏰ Done\n";
});
Swoole\Timer::after(1000, function () use ($id) {
    if (!Swoole\Timer::exists($id)) {
        echo "✅ All right!\n";
    }
});

The way of coroutine

go(function () {
    $i = 0;
    while (true) {
        Co::sleep(0.1);
        echo "📝 Do something...\n";
        if (++$i === 5) {
            echo "🛎 Done\n";
            break;
        }
    }
    echo "🎉 All right!\n";
});

🔥 Amazing runtime hooks

As of Swoole v4.1.0, we added the ability to transform synchronous PHP network libraries into co-routine libraries using a single line of code.

Simply call the Swoole\Runtime::enableCoroutine() method at the top of your script. In the sample below we connect to php-redis and concurrently read 10k requests in 0.1s:

Swoole\Runtime::enableCoroutine();
$s = microtime(true);
Co\run(function() {
    for ($c = 100; $c--;) {
        go(function () {
            ($redis = new Redis)->connect('127.0.0.1', 6379);
            for ($n = 100; $n--;) {
                assert($redis->get('awesome') === 'swoole');
            }
        });
    }
});
echo 'use ' . (microtime(true) - $s) . ' s';

By calling this method, the Swoole kernel replaces ZendVM stream function pointers. If you use php_stream based extensions, all socket operations can be dynamically converted to be asynchronous IO scheduled by coroutine at runtime!

How many things you can do in 1s?

Sleep 10K times, read, write, check and delete files 10K times, use PDO and MySQLi to communicate with the database 10K times, create a TCP server and multiple clients to communicate with each other 10K times, create a UDP server and multiple clients to communicate with each other 10K times... Everything works well in one process!

Just see what the Swoole brings, just imagine...

Swoole\Runtime::enableCoroutine();
$s = microtime(true);
Co\run(function() {
    // i just want to sleep...
    for ($c = 100; $c--;) {
        go(function () {
            for ($n = 100; $n--;) {
                usleep(1000);
            }
        });
    }

    // 10K file read and write
    for ($c = 100; $c--;) {
        go(function () use ($c) {
            $tmp_filename = "/tmp/test-{$c}.php";
            for ($n = 100; $n--;) {
                $self = file_get_contents(__FILE__);
                file_put_contents($tmp_filename, $self);
                assert(file_get_contents($tmp_filename) === $self);
            }
            unlink($tmp_filename);
        });
    }

    // 10K pdo and mysqli read
    for ($c = 50; $c--;) {
        go(function () {
            $pdo = new PDO('mysql:host=127.0.0.1;dbname=test;charset=utf8', 'root', 'root');
            $statement = $pdo->prepare('SELECT * FROM `user`');
            for ($n = 100; $n--;) {
                $statement->execute();
                assert(count($statement->fetchAll()) > 0);
            }
        });
    }
    for ($c = 50; $c--;) {
        go(function () {
            $mysqli = new Mysqli('127.0.0.1', 'root', 'root', 'test');
            $statement = $mysqli->prepare('SELECT `id` FROM `user`');
            for ($n = 100; $n--;) {
                $statement->bind_result($id);
                $statement->execute();
                $statement->fetch();
                assert($id > 0);
            }
        });
    }

    // php_stream tcp server & client with 12.8K requests in single process
    function tcp_pack(string $data): string
    {
        return pack('n', strlen($data)) . $data;
    }

    function tcp_length(string $head): int
    {
        return unpack('n', $head)[1];
    }

    go(function () {
        $ctx = stream_context_create(['socket' => ['so_reuseaddr' => true, 'backlog' => 128]]);
        $socket = stream_socket_server(
            'tcp://0.0.0.0:9502',
            $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctx
        );
        if (!$socket) {
            echo "$errstr ($errno)\n";
        } else {
            $i = 0;
            while ($conn = stream_socket_accept($socket, 1)) {
                stream_set_timeout($conn, 5);
                for ($n = 100; $n--;) {
                    $data = fread($conn, tcp_length(fread($conn, 2)));
                    assert($data === "Hello Swoole Server #{$n}!");
                    fwrite($conn, tcp_pack("Hello Swoole Client #{$n}!"));
                }
                if (++$i === 128) {
                    fclose($socket);
                    break;
                }
            }
        }
    });
    for ($c = 128; $c--;) {
        go(function () {
            $fp = stream_socket_client("tcp://127.0.0.1:9502", $errno, $errstr, 1);
            if (!$fp) {
                echo "$errstr ($errno)\n";
            } else {
                stream_set_timeout($fp, 5);
                for ($n = 100; $n--;) {
                    fwrite($fp, tcp_pack("Hello Swoole Server #{$n}!"));
                    $data = fread($fp, tcp_length(fread($fp, 2)));
                    assert($data === "Hello Swoole Client #{$n}!");
                }
                fclose($fp);
            }
        });
    }

    // udp server & client with 12.8K requests in single process
    go(function () {
        $socket = new Swoole\Coroutine\Socket(AF_INET, SOCK_DGRAM, 0);
        $socket->bind('127.0.0.1', 9503);
        $client_map = [];
        for ($c = 128; $c--;) {
            for ($n = 0; $n < 100; $n++) {
                $recv = $socket->recvfrom($peer);
                $client_uid = "{$peer['address']}:{$peer['port']}";
                $id = $client_map[$client_uid] = ($client_map[$client_uid] ?? -1) + 1;
                assert($recv === "Client: Hello #{$id}!");
                $socket->sendto($peer['address'], $peer['port'], "Server: Hello #{$id}!");
            }
        }
        $socket->close();
    });
    for ($c = 128; $c--;) {
        go(function () {
            $fp = stream_socket_client("udp://127.0.0.1:9503", $errno, $errstr, 1);
            if (!$fp) {
                echo "$errstr ($errno)\n";
            } else {
                for ($n = 0; $n < 100; $n++) {
                    fwrite($fp, "Client: Hello #{$n}!");
                    $recv = fread($fp, 1024);
                    list($address, $port) = explode(':', (stream_socket_get_name($fp, true)));
                    assert($address === '127.0.0.1' && (int)$port === 9503);
                    assert($recv === "Server: Hello #{$n}!");
                }
                fclose($fp);
            }
        });
    }
});
echo 'use ' . (microtime(true) - $s) . ' s';

⌛️ Installation

As with any open source project, Swoole always provides the most reliable stability and the most powerful features in the latest released version. Please ensure as much as possible that you are using the latest version.

Compiling requirements

  • Linux, OS X or Cygwin, WSL
  • PHP 7.2.0 or later (The higher the version, the better the performance.)
  • GCC 4.8 or later

1. Install via PECL (beginners)

pecl install swoole

2. Install from source (recommended)

Please download the source packages from Releases or:

git clone https://github.com/swoole/swoole-src.git && \
cd swoole-src

Compile and install at the source folder:

phpize && \
./configure && \
make && make install

Enable extension in PHP

After compiling and installing to the system successfully, you have to add a new line extension=swoole.so to php.ini to enable Swoole extension.

Extra compiler configurations

for example: ./configure --enable-openssl --enable-sockets

  • --enable-openssl or --with-openssl-dir=DIR
  • --enable-sockets
  • --enable-mysqlnd (need mysqlnd, it just for supporting $mysql->escape method)
  • --enable-swoole-curl

Upgrade

⚠️ If you upgrade from source, don't forget to make clean before you upgrade your swoole

  1. pecl upgrade swoole
  2. cd swoole-src && git pull && make clean && make && sudo make install
  3. if you change your PHP version, please re-run phpize clean && phpize then try to compile

Major change since version 4.3.0

Async clients and API are moved to a separate PHP extension swoole_async since version 4.3.0, install swoole_async:

git clone https://github.com/swoole/ext-async.git
cd ext-async
phpize
./configure
make -j 4
sudo make install

Enable it by adding a new line extension=swoole_async.so to php.ini.

🍭 Benchmark

  • On the open source Techempower Web Framework benchmarks Swoole used MySQL database benchmark to rank first, and all performance tests ranked in the first echelon.
  • You can just run Benchmark Script to quickly test the maximum QPS of Swoole-HTTP-Server on your machine.

🔰️ Security issues

Security issues should be reported privately, via email, to the Swoole develop team [email protected]. You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message.

🖊️ Contribution

Your contribution to Swoole development is very welcome!

You may contribute in the following ways:

❤️ Contributors

This project exists thanks to all the people who contribute. [Contributors].

🎙️ Official Evangelist

Demin has been playing with PHP since 2000, focusing on building high-performance, secure web services. He is an occasional conference speaker on PHP and Swoole, and has been working for companies in the states like eBay, Visa and Glu Mobile for years. You may find Demin on Twitter or GitHub.

📃 License

Apache License Version 2.0 see http://www.apache.org/licenses/LICENSE-2.0.html

grpc's People

Contributors

cragonnyunt avatar huangzhhui avatar inhere avatar matyhtf avatar reasno avatar sy-records avatar twose avatar woody712 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

grpc's Issues

Not sure how to properly install

I'm trying to install this one, but I run into issues (probably due to mis-translation)

Anyhow, it seems to say that I should install Protobuf and grpc_php_plugin, but I should not enable these plugins? Does it mean I should not add the extensions to the conf.d folder of PHP?

Also, when I tried to run generate_grpc.sh I get the following error:

program not found or is not executable
--grpc_out: protoc-gen-grpc: Plugin failed with status code 1.

Please let me know what I should do to fix this.

Client 复用同个连接,出现 rpc error: code = Unknown desc =

问题

客户端复用 ClientConn 后,无法再次调用 RPC 方法成功

server

直接使用 greeter_server.php

client

直接改造 greeter_client.go,如下:

package main

import (
	"log"
	"os"

	"golang.org/x/net/context"
	"google.golang.org/grpc"
	pb "google.golang.org/grpc/examples/helloworld/helloworld"
)

const (
	address     = "localhost:50051"
	defaultName = "world"
)

func main() {
	// Set up a connection to the server.
	conn, err := grpc.Dial(address, grpc.WithInsecure())
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	defer conn.Close()
	c := pb.NewGreeterClient(conn)

	// Contact the server and print out its response.
	name := defaultName
	if len(os.Args) > 1 {
		name = os.Args[1]
	}
	r, err := c.SayHello(context.Background(), &pb.HelloRequest{Name: name})
	if err != nil {
		log.Fatalf("could not greet1: %v", err)
	}
	log.Printf("Greeting: %s", r.Message)

	r, err = c.SayHello(context.Background(), &pb.HelloRequest{Name: name})
	if err != nil {
		log.Fatalf("could not greet2: %v", err)
	}
	log.Printf("Greeting: %s", r.Message)
}

输出结果

$ go run greeter_client.go
2019/04/18 14:18:06 Greeting: Hello world
2019/04/18 14:18:06 could not greet2: rpc error: code = Unknown desc = 
exit status 1

并发量大的时候经常发生错误

显示:
Fatal error: Uncaught Swoole\Error: Socket#33 has already been bound to another coroutine#852, writing of the same socket in coroutine#853 at the same time is not allowed in

src目录的helloword能删除么

文件路径: src/Grpc/Helloworld

建议保持包的简洁性,如果使用该包作为客户端时,采用grpc-go包的helloworld.proto时会产生冲突

Swoole/grpc 无法调用node官方grpc-server

Swoole版本4.5.2

启动example/grpc/greater_client.php, 调用官方node grpc server,无响应,卡死。node server未进入响应逻辑。

@huanghantao

node

/*
 *
 * Copyright 2015 gRPC authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

var PROTO_PATH = __dirname + '/grpc/helloworld.proto';

var grpc = require('grpc');
var protoLoader = require('@grpc/proto-loader');
var packageDefinition = protoLoader.loadSync(
    PROTO_PATH,
    {keepCase: true,
        longs: String,
        enums: String,
        defaults: true,
        oneofs: true
    });
var hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld;

/**
 * Implements the SayHello RPC method.
 */
function sayHello(call, callback) {
    callback(null, {message: 'Hello Node Server - ' + call.request.name});
}

/**
 * Starts an RPC server that receives requests for the Greeter service at the
 * sample server port
 */
function main() {
    var server = new grpc.Server();
    server.addService(hello_proto.Greeter.service, {sayHello: sayHello});
    server.bind('0.0.0.0:9502', grpc.ServerCredentials.createInsecure());
    server.start();
}

main();

php

<?php

use Helloworld\GreeterClient;
use Helloworld\HelloRequest;

require __DIR__ . '/../../vendor/autoload.php';

$name = !empty($argv[1]) ? $argv[1] : 'Swoole';

Swoole\Coroutine::create(function () use ($name) {
    $greeterClient = new GreeterClient('127.0.0.1:9502');
    $request = new HelloRequest();
    $request->setName($name);
    [$reply] = $greeterClient->SayHello($request);
    $message = $reply->getMessage();
    echo "{$message}\n";
    $greeterClient->close();
});

PHP gRPC Client 调用 Swoole PHP Server 出现 Status Code 为 2

PHP 版本

$ php -v
PHP 7.2.11 (cli) (built: Oct 21 2018 18:28:44) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.2.11, Copyright (c) 1999-2018, by Zend Technologies

Swoole 版本

$ php --ri swoole

swoole

swoole support => enabled
Version => 4.2.3
Author => Swoole Group[email: [email protected]]
coroutine => enabled
kqueue => enabled
rwlock => enabled
sockets => enabled
openssl => OpenSSL 1.0.2p  14 Aug 2018
http2 => 1.34.0
pcre => enabled
zlib => enabled
brotli => enabled

Directive => Local Value => Master Value
swoole.enable_coroutine => On => On
swoole.aio_thread_num => 2 => 2
swoole.display_errors => On => On
swoole.use_namespace => On => On
swoole.use_shortname => On => On
swoole.fast_serialize => Off => Off
swoole.unixsock_buffer_size => 8388608 => 8388608

背景

1、Server 端:基于 Swoole 的 swoole_http_server 搭建的 gRPC Server
2、Client 端:原生 gRPC 官方提供的 PHP Client

场景

在调用 PHP Client 请求 PHP Server 时,在初次请求中。第一个调用是正常 status code,但是第二个调用就返回 status code 为 2。但是接下来多次请求均正常反馈(不重启 server 的情况下,重启的话又会出现)

返回

第一个调用

stdClass Object
(
    [metadata] => Array
        (
        )

    [code] => 0
    [details] => 
)

$reply 和 $status 均为正常值

第二个调用

stdClass Object
(
    [metadata] => Array
        (
            [server] => Array
                (
                    [0] => swoole-http-server
                )

            [content-encoding] => Array
                (
                    [0] => gzip
                )

        )

    [code] => 2
    [details] => 
)

$reply 为正常值,$status->code 为 2,存在问题!

问题

我的 2个 Client 调用,两个分别单独调用都是正常的。但是一旦都打开,就会出现问题。并且出现问题的那个响应结果 metadata,一定会出现:

 [server] => Array
(
    [0] => swoole-http-server
)
...

只有在初次调用下,存在多个 Client 请求,才会出现这个问题!

第二次一模一样的代码调用就又正常了。考虑到表现上出现了 swoole 的标识符。怀疑是否 swoole 内部做了什么初始化动作,导致 gRPC Status Code 出现异常。但接下来又正常了....

php8.2兼容

在 openStream 函数中动态添加了$userPipelineRead属性导致php8.2不兼容

    public function openStream(string $path, $data = '', string $method = '', bool $use_pipeline_read = false): int
    {
        $request = new Request;
        if ($method) {
            $request->method = $method;
        } else {
            if (!$data) {
                $request->method = 'GET';
            } else {
                $request->method = 'POST';
            }
        }
        $request->path = $path;
        if ($data) {
            $request->data = $data;
        }
        $request->pipeline = true;
        if ($use_pipeline_read) {
            if (SWOOLE_VERSION_ID < 40503) {
                throw new InvalidArgumentException('Require Swoole version >= 4.5.3');
            }
            $request->usePipelineRead = true;
        }

        return $this->send($request);
    }

报错

Uncaught ErrorException: Creation of dynamic property Grpc\Request::$usePipelineRead is deprecated in /Users/nashgao/Desktop/project/space/dependencies/lib/space-utils/vendor/swoole/etcd-client/src/Grpc/Client.php:296

我这边不确定这个$usePipelineRead属性是放在\Grpc\Request中还是\Swoole\Http2\Request 希望可以兼容一下

It doesn't work in swoole v4.3.2

I used this client in v4.2.9, everything is OK.

And I updated swoole to v4.3.2, the client can not work.

`
$kvClient = new \Etcdserverpb\KVClient($host);

$kvClient->start();

$rangeRequest = new \Etcdserverpb\RangeRequest();

$rangeRequest->setKey('key');

$rangeRequest->setRangeEnd($this->addOneBit('key'));

$results = $kvClient->Range($rangeRequest);

$rangeResponse = $results[0];
`
there is no respone.

grpc/greeter_server.php: [WARNING] swSSL_accept: bad SSL client

您好 @twose ,我正在实现 SSL 相关的功能,但是出现了问题完全阻塞了我的开发

  1. server.php:
$http = new swoole_http_server('127.0.0.1', 50051, SWOOLE_PROCESS, SWOOLE_SOCK_TCP | SWOOLE_SSL);
$http->set([
    'log_level' => SWOOLE_LOG_INFO,
    'trace_flags' => 0,
    'worker_num' => 1,
    'open_http2_protocol' => true,
    'ssl_cert_file' => './certs/server.pem',
    'ssl_key_file' => './certs/server-key.pem',
]);

$http->on('workerStart', function (swoole_http_server $server) {
    echo "nghttp -v https://127.0.0.1:{$server->port}\n";
});
$http->on('request', function (swoole_http_request $request, swoole_http_response $response) {
...
}
  1. client.php:
use \User\UserClient;
use \User\ListRequest;

require __DIR__ . '/../vendor/autoload.php';

$name = !empty($argv[1]) ? $argv[1] : 'Swoole User';

go(function () use ($name) {
    $credentials = Grpc\ChannelCredentials::createSsl(
        file_get_contents('../certs/ca.pem')
    );
    $userClient = new UserClient('127.0.0.1:50051', [
        'credentials' => $credentials,
    ]);
    $userClient->start();
    $request = new ListRequest();
    ...

    list($reply, $status) = $userClient->List($request);
    $message = $reply->getResponseName();
    echo "{$message}\n";
    $userClient->close();
});
  1. 场景回放:
    server.php 运行成功,client.php 执行后,server 端出现报错
$ php server.php
nghttp -v https://127.0.0.1:50051
[2018-10-20 15:15:27 #66954.0]	WARNING	swSSL_accept: bad SSL client[127.0.0.1:55810].
  1. 证书

我代码中的 ./certs/server.pem./certs/server-key.pem 在纯 Go Client/Server 中使用正常,PHP Client 是在官方 demo 中看到使用 ca.pem 即可

  1. 疑问:

Requirement 中有明确提到 不要启用grpc的php扩展

但是在 grpc-client 的包内并不包含 Grpc\ChannelCredentials 等等类,因此无法调用 SSL 方面的凭证处理,最后我被迫启动了 php grpc 的扩展来达到调用 ChannelCredentials 类的目的

但是又遇到了 bad SSL client 的问题,无法内调成功!

想请问 @twose 是否有建议或如何解决?或是我哪里做错了?

GRPC断线重连问题

服务端意外断开链接后,客户端会卡死
跟踪代码,会以 client.php 里的 start 函数进入死循环,
看到swoole文档中说:
4.x 协程版本后,connected 属性不再会实时更新,isConnect 方法不再可靠

有什么解决办法吗

关于 grpc/greeter_server.php 的一些疑问

@twose 又来打扰您了,我看了 grpc/greeter_server.php, 有了以下的疑问想要请教您 🤔

--

在示例代码中,是直接指定 SayHelloHelloRequestHelloReply 去响应的。而在真实的案例中,一个 server 端,是会承载多个 service,也就承载着对应的多个 rpc 接口

是需要判断对应是来自哪个 service,哪个 rpc 接口方法。像 Go 的 gRPC Server 的话,是会将其注册到内部的服务管理中,然后进行各种内部服务发现判断

那么这个 demo 显然就无法处理,想请问 twose 对此有没有什么好的实现方案或建议?

莫非是在 swoole_http_request $request 中能够取到对应的 service 和 rpc 方法接口名称,然后做映射吗?

$http->on('request', function (swoole_http_request $request, swoole_http_response $response) use ($http) {
    /**@var $request_message HelloRequest */
    $request_message = Grpc\Parser::deserializeMessage([HelloRequest::class, null], $request->rawcontent());
    if ($request_message) {
        $response_message = new HelloReply();
        $response_message->setMessage('Hello ' . $request_message->getName());
        $response->header('content-type', 'application/grpc');
        $response->header('trailer','grpc-status, grpc-message');
        $trailer = [
            "grpc-status" => "0",
            "grpc-message" => ""
        ];
        foreach ($trailer as $trailer_name => $trailer_value) {
            $response->trailer($trailer_name, $trailer_value);
        }
        $response->end(Grpc\Parser::serializeMessage($response_message));
    } else {
        $response->end('failed');
    }
});

感谢!

解析报错PHP Fatal error: Uncaught Google\Protobuf\Internal\GPBDecodeException: Error occurred during parsing: Unexpected wire type.

解析复杂grpc对象报错PHP Fatal error: Uncaught Google\Protobuf\Internal\GPBDecodeException: Error occurred during parsing: Unexpected wire type.
propto定义大概如下:

service UserService {
rpc GetUserList (UserListRequest) returns (UserListReply) {}
}
message User {
int64 id = 1; // 自增ID
int64 uid = 2; // UID
string user_name = 3; // 积分名称
}
message UserListRequest {
}
message UserListReply {
repeated User list = 1; //用户列表
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.