Preface: after understanding the concept should practice hand, or it is a giant baby

If there is a harvest, please add a small star, if there is no harvest, you can oppose to help inform against three

  • Code warehouse
  • Swoole 【 chat room 】
  • Online experience

The preparatory work

  • You need to look at Beginner Swoole [up] to understand the basic server WebSocket usage
  • The JS WebSocket client is simple to use

use

Command line 1
php src/websocket/run.php
Command line 2
cd public && php -S localhost:8000
# Client, open a few more to view the effectGo to http://localhost:8000/Copy the code

WebSocket

The official sample

$server = new swoole_websocket_server("0.0.0.0".9501);
$server->on('open'.function (swoole_websocket_server $server, $request) {
        echo "server: handshake success with fd{$request->fd}\n";
    });
$server->on('message'.function (swoole_websocket_server $server, $frame) {
        echo "receive from {$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}\n";
        $server->push($frame->fd, "this is server");
    });
$server->on('close'.function ($ser, $fd) {
        echo "client {$fd} closed\n";
    });
$server->on('request'.function (swoole_http_request $request, swoole_http_response $response) {
        global $server;// Call the external server
        $server->connections $server->connections $server->connections $server->connections
        foreach ($server->connections as $fd) {
            $server->push($fd, $request->get['message']); }}); $server->start();Copy the code

A:

  • Swoole_websocket_server inherits from Swoole_HTTP_server

    • With the onRequest callback set, the WebSocket server can also act as an HTTP server
    • The onRequest callback is not set. The WebSocket server returns an HTTP 400 error page after receiving an HTTP request
    • If you want to trigger all WebSocket pushes by receiving HTTP, you need to pay attention to the scope problem. For procedure, use global to reference swoole_webSocket_server. For object, you can set swoole_webSocket_server as a member property
  • function onOpen(swoole_websocket_server $svr, swoole_http_request $req);

    • This function is called back when the WebSocket client has established a connection to the server and completed the handshake.
    • $req is an Http request object that contains the handshake request information sent by the client
    • The onOpen event function can call push to send data to the client or call CLOSE to close the connection
    • The onOpen event callback is optional
  • function onMessage(swoole_websocket_server $server, swoole_websocket_frame $frame)

    • This function is called when the server receives a data frame from the client.
    • $frame is a Swoole_webSocket_frame object that contains the data frame information sent by the client
    • The onMessage callback must be set, otherwise the server will not start
    • The ping frame sent by the client does not trigger onMessage, and the underlying layer automatically replies to the Pong packet
  • Swoole_websocket_frame properties

    • $frame->fd, the socket ID of the client, using$server->pushUsed when pushing data
    • $frame->data, data content, can be text content or binary data, can be judged by opCode value
    • $frame->opcodeFor the OpCode type of WebSocket, refer to the WebSocket protocol standard document
    • $frame->finishA WebSocket request may be sent in multiple data frames. (The underlying mechanism is to automatically merge data frames, so don’t worry about receiving incomplete data frames.)

Chat room server example

Directory structure:

  • config
    • socket.php
  • src
    • websocket
      • Config.php
      • run.php
      • Websocketserver.php memory table version
      • Redis WsRedisServer. PHP version

Websocketserver.php memory table version


      
namespace App\WebSocket;

class WebSocketServer
{
    private $config;
    private $table;
    private $server;

    public function __construct(a)
    {
        // Memory tables are shared between processes. Redis can also be used instead
        $this->createTable();
        // Instantiate the configuration
        $this->config = Config::getInstance();
    }

    public function run(a)
    {
        $this->server = new \swoole_websocket_server(
            $this->config['socket'] ['host'].$this->config['socket'] ['port']);$this->server->on('open'[$this.'open']);
        $this->server->on('message'[$this.'message']);
        $this->server->on('close'[$this.'close']);

        $this->server->start();
    }

    public function open(\swoole_websocket_server $server, \swoole_http_request $request)
    {
        $user = [
            'fd' => $request->fd,
            'name'= >$this->config['socket'] ['name'][array_rand($this->config['socket'] ['name'])] . $request->fd,
            'avatar'= >$this->config['socket'] ['avatar'][array_rand($this->config['socket'] ['avatar']]]];// Insert the memory table
        $this->table->set($request->fd, $user);

        $server->push($request->fd, json_encode(
                array_merge(['user' => $user], ['all'= >$this->allUser()], ['type'= >'openSuccess'))); }private function allUser(a)
    {
        $users = [];
        foreach ($this->table as $row) {
            $users[] = $row;
        }
        return $users;
    }

    public function message(\swoole_websocket_server $server, \swoole_websocket_frame $frame)
    {
        $this->pushMessage($server, $frame->data, 'message', $frame->fd);
    }

    /** * Push message **@param \swoole_websocket_server $server
     * @param string $message
     * @param string $type
     * @param int $fd
     */
    private function pushMessage(\swoole_websocket_server $server, string $message, string $type, int $fd)
    {
        $message = htmlspecialchars($message);
        $datetime = date('Y-m-d H:i:s', time());
        $user = $this->table->get($fd);

        foreach ($this->table as $item) {
            // Don't send it yourself
            if ($item['fd'] == $fd) {
                continue;
            }

            $server->push($item['fd'], json_encode([
                'type' => $type,
                'message' => $message,
                'datetime' => $datetime,
                'user'=> $user ])); }}/** * when the client is closed **@param \swoole_websocket_server $server
     * @param int $fd
     */
    public function close(\swoole_websocket_server $server, int $fd)
    {
        $user = $this->table->get($fd);
        $this->pushMessage($server, "{$user['name']}".'close', $fd);
        $this->table->del($fd);
    }

    /** * Create memory table */
    private function createTable(a)
    {
        $this->table = new \swoole_table(1024);
        $this->table->column('fd', \swoole_table::TYPE_INT);
        $this->table->column('name', \swoole_table::TYPE_STRING, 255);
        $this->table->column('avatar', \swoole_table::TYPE_STRING, 255);
        $this->table->create(); }}Copy the code

Redis WsRedisServer. PHP version


      
namespace App\WebSocket;

use Predis\Client;

/** * use redis instead of table and store history chat ** Class WsRedisServer *@package App\WebSocket
 */
class WsRedisServer
{
    private $config;
    private $server;
    private $client;
    private $key = "socket:user";

    public function __construct(a)
    {
        // Instantiate the configuration
        $this->config = Config::getInstance();
        // redis
        $this->initRedis();
        // Initialize redis. The server will not clear redis if it closes
        foreach ($this->allUser() as $item) {
            $this->client->hdel("{$this->key}:{$item['fd']}"['fd'.'name'.'avatar']); }}public function run(a)
    {
        $this->server = new \swoole_websocket_server(
            $this->config['socket'] ['host'].$this->config['socket'] ['port']);$this->server->on('open'[$this.'open']);
        $this->server->on('message'[$this.'message']);
        $this->server->on('close'[$this.'close']);

        $this->server->start();
    }

    public function open(\swoole_websocket_server $server, \swoole_http_request $request)
    {
        $user = [
            'fd' => $request->fd,
            'name'= >$this->config['socket'] ['name'][array_rand($this->config['socket'] ['name'])] . $request->fd,
            'avatar'= >$this->config['socket'] ['avatar'][array_rand($this->config['socket'] ['avatar']]]];/ / in the redis
        $this->client->hmset("{$this->key}:{$user['fd']}", $user);

        // Push to everyone, including yourself
        foreach ($this->allUser() as $item) {
            $server->push($item['fd'], json_encode([
                'user' => $user,
                'all'= >$this->allUser(),
                'type'= >'openSuccess'])); }}private function allUser(a)
    {
        $users = [];
        $keys = $this->client->keys("{$this->key}:*");
        // All keys
        foreach ($keys as $k => $item) {
            $users[$k]['fd'] = $this->client->hget($item, 'fd');
            $users[$k]['name'] = $this->client->hget($item, 'name');
            $users[$k]['avatar'] = $this->client->hget($item, 'avatar');
        }
        return $users;
    }

    public function message(\swoole_websocket_server $server, \swoole_websocket_frame $frame)
    {
        $this->pushMessage($server, $frame->data, 'message', $frame->fd);
    }

    /** * Push message **@param \swoole_websocket_server $server
     * @param string $message
     * @param string $type
     * @param int $fd
     */
    private function pushMessage(\swoole_websocket_server $server, string $message, string $type, int $fd)
    {
        $message = htmlspecialchars($message);
        $datetime = date('Y-m-d H:i:s', time());
        $user['fd'] = $this->client->hget("{$this->key}:{$fd}".'fd');
        $user['name'] = $this->client->hget("{$this->key}:{$fd}".'name');
        $user['avatar'] = $this->client->hget("{$this->key}:{$fd}".'avatar');

        foreach ($this->allUser() as $item) {
            // Don't send it yourself
            if ($item['fd'] == $fd) {
                continue;
            }

            $is_push = $server->push($item['fd'], json_encode([
                'type' => $type,
                'message' => $message,
                'datetime' => $datetime,
                'user' => $user
            ]));
            // Delete the failed push
            if(! $is_push) {$this->client->hdel("{$this->key}:{$item['fd']}"['fd'.'name'.'avatar']); }}}/** * when the client is closed **@param \swoole_websocket_server $server
     * @param int $fd
     */
    public function close(\swoole_websocket_server $server, int $fd)
    {
        $user['fd'] = $this->client->hget("{$this->key}:{$fd}".'fd');
        $user['name'] = $this->client->hget("{$this->key}:{$fd}".'name');
        $user['avatar'] = $this->client->hget("{$this->key}:{$fd}".'avatar');
        $this->pushMessage($server, "{$user['name']}".'close', $fd);
        $this->client->hdel("{$this->key}:{$fd}"['fd'.'name'.'avatar']);
    }

    /** * Initialize redis */
    private function initRedis(a)
    {
        $this->client = new Client([
            'scheme'= >$this->config['socket'] ['redis'] ['scheme'].'host'= >$this->config['socket'] ['redis'] ['host'].'port'= >$this->config['socket'] ['redis'] ['port']]); }}Copy the code

config.php


      
namespace App\WebSocket;

class Config implements \ArrayAccess
{
    private $path;
    private $config;
    private static $instance;

    public function __construct(a)
    {
        $this->path = __DIR__ . '/.. /.. /config/';
    }

    // Singleton mode
    public static function getInstance(a)
    {
        if (!self::$instance) {
            self::$instance = new self(a); }return self::$instance;
    }

    public function offsetSet($offset, $value)
    {
        / / castration
    }

    public function offsetGet($offset)
    {
        if (empty($this->config)) {
            $this->config[$offset] = require $this->path . $offset . ".php";
        }
        return $this->config[$offset];
    }

    public function offsetExists($offset)
    {
        return isset($this->config[$offset]);
    }

    public function offsetUnset($offset)
    {
        / / castration
    }

    // Disable cloning
    final private function __clone(a){}}Copy the code

config/socket.php


      
return [
    'host'= >'0.0.0.0'.'port'= >9501.'redis'= > ['scheme'= >'tcp'.'host'= >'0.0.0.0'.'port'= >6380].'avatar'= > ['./images/avatar/1.jpg'.'./images/avatar/2.jpg'.'./images/avatar/3.jpg'.'./images/avatar/4.jpg'.'./images/avatar/5.jpg'.'./images/avatar/6.jpg'].'name'= > ['kobe Bryant.'库里'.'KD'.'KG'.'Jordan'.'Duncan'.'green'.'Thompson'.'Iguodala'.'McGrady'.'Allen Iverson'.'Kawaii'.'Paul']].Copy the code

run.php


      
require __DIR__ . '/.. /bootstrap.php';

$server = new App\WebSocket\WebSocketServer();

$server->run();
Copy the code

conclusion

Full example: chat room

After learning found that the so-called chat room in life is in fact so, of course, this is just a simple demo, many functions have not been implemented, want to further study can go to Github to find a complete project for in-depth study

reference

  • swoole
  • PHP + Swoole implementation of simple chat rooms