swoole 第14章 websocket初识

2021-06-09

一、什么是websocket

websocket != socket。

我猜有些人一看标题websocket就联想到socket,其实二者之间并没多大关系,这就好比javascript和java,千万不要混淆了。

那websocket是什么呢?

websocket是一个协议,它仅仅就是一个协议而已,跟我们所了解的http协议、https协议、ftp协议等等一样,都是一种单纯的协议。

websocket是一种怎样的协议呢?换句话说它有什么特点呢?

相对于Http这种非持久连接而言,websocket协议是一种持久化连接,它是一种独立的,基于TCP的协议。基于websocket,我们可以实现客户端和服务端双向通信。

你肯定听说过服务器推技术,在websocket出现之前,为了解决此类问题,常用的解决方法有轮询和long pull,这两种技术都是客户端和服务端建立源源不断的HTTP连接,非常消耗带宽和服务器资源。

websocket是双向持久连接,客户端和服务端只需要第一次建立连接即可实现双向通信,说到这里,你肯定明白我们学习websocket要做什么了。没错,基于websocket,我们可以做一些通讯,推送相关的服务。

swoole内置的websocket服务器,异步非阻塞多进程,牛逼的swoole!

二、创建websocket服务器

我们看一下在swoole中如何创建websocket服务器。

swoole的websocket服务器的创建同样非常简单,只需要我们处理好相应的回调即可。

<span style="font-size: 16px;"><?php
class WebSocketServer
{
    private $_serv;
    public function __construct()
    {
        $this->_serv = new swoole_websocket_server("0.0.0.0", 9501);
        $this->_serv->set([
            "worker_num" => 1,
        ]);
        $this->_serv->on("open", [$this, "onOpen"]);
        $this->_serv->on("message", [$this, "onMessage"]);
        $this->_serv->on("close", [$this, "onClose"]);
    }
    /**
     * @param $serv
     * @param $request
     */
    public function onOpen($serv, $request)
    {
        echo "server: handshake success with fd{$request->fd}.\n";
    }
    /**
     * @param $serv
     * @param $frame
     */
    public function onMessage($serv, $frame)
    {
        $serv->push($frame->fd, "server received data :{$frame->data}");
    }
    public function onClose($serv, $fd)
    {
        echo "client {$fd} closed.\n";
    }
    public function start()
    {
        $this->_serv->start();
    }
}
$server = new WebSocketServer;
$server->start();
</span>

来看看我们创建的这个websocket服务器,我们介绍两个回调

  • onOpen回调:客户端与服务端建立连接的时候将触发该回调,回调的第二个参数是swoole_http_request对象,包括了http握手的一些信息,比如GET\COOKIE等
  • onMessage回调:这个是服务端收到客户端信息后回调,在该回调内我们调用了swoole_websocket_server::push方法向客户端推送了数据,注意哦,push的第一个参数只能是websocket客户端的标识

对应的,swoole是否也有提供websocket客户端呢?有,不过我们不准备使用,浏览器天生内置js版的websocket客户端,简单方便好用!当然,不包括低版本的IE浏览器,原因你懂得。

在js中,有一套操作websocket的API,我们可以用这个API创建websocket对象,建立与websocket服务端的连接,并且可以向server发送消息,接收server的消息,当然也少不了关闭连接的操作。我们看一个例子

首先我们创建一个index.html文件,写一段js代码,代码如下:

<span style="font-size: 16px;"><script>
    var ws = new WebSocket("ws://139.199.201.210:9501");
    ws.onopen = function(event) {
        // 发送消息
        ws.send("This is websocket client.");
    };

    // 监听消息
    ws.onmessage = function(event) {
        console.log("Client received a message: ", event.data);
    };
    ws.onclose = function(event) {
        console.log("Client has closed.\n", event);
    };
</script>
</span>

js代码写好之后,回车之前记得先在CLI下把websocket服务器运行起来,不然哪能连接的上呢。

二者都准备完善之后,打开控制台,刷新下浏览器,我们看到控制台输出的结果

01.png

结果可能不是很明显,单独来看

  • 客户端先发送给server : This is websocket client.
  • server收到消息后,在onMessage回调内向客户端推送了 server received data :This is websocket client.
  • 客户端在onmessage回调内收到server的信息,并在server发过来的消息的前面增加了 Client received a message: ,这才有了控制台上面展示的信息

整个过程,完美的交互,至此,客户端和服务端便建立了持久化的双向连接。二者可以互发消息。

有些同学可能没转过弯来,这样就互通了,可以双向交互了?我们把js代码稍稍完善一下,做一个界面上的交互

创建一个文本框、一个点击发送的按钮和一个用于展示消息的div

<span style="font-size: 16px;"><div>
    <textarea name="content" id="content" cols="30" rows="10"></textarea>
    <button onclick="send();">发送</button>
</div>
<div class="list" style="border: solid 1px #ccc; margin-top: 10px;">
    <ul id="ul">
    </ul>
</div>
</span>

01.png

然后我们看看js操作

<span style="font-size: 16px;"><script>
var ws = new WebSocket("ws://139.199.201.210:9501");
ws.onopen = function(event) {
    ws.send("This is websocket client.");
};
ws.onmessage = function(event) {
    var data = event.data;
    var ul = document.getElementById("ul");
    var li = document.createElement("li");
    li.innerHTML = data;
    ul.appendChild(li);
};
ws.onclose = function(event) {
    console.log("Client has closed.\n", event);
};
function send() {
    var obj = document.getElementById("content");
    var content = obj.value;
    ws.send(content);
}
</script>
</span>

简单的说一下client处理的过程:连接上server之后,ws就发送了一条消息,然后onmessage回调中,会把接收自server的消息追加显示在我们创建的div#ul上,当我们在文本框输入消息内容的时候,点击发送,send方法会被调用,结果就是这个内容会被发送到server。

注:SSL的server,如果你的swoole安装的时候未配置ssl,那么必须重新编译安装。

<span style="font-size: 16px;">$this->_serv = new swoole_websocket_server("0.0.0.0", 9501,SWOOLE_PROCESS, SWOOLE_SOCK_TCP | SWOOLE_SSL);
$this->_serv->set([
    "worker_num" => 1,
    "ssl_cert_file" => __DIR__."/source/test.lulublog.cn.crt",
    "ssl_key_file" => __DIR__."/source/test.lulublog.cn.key",
]);
</span>

注:SSL 客户端连接

<span style="font-size: 16px;">var ws = new WebSocket("wss://test.lulublog.cn:9501");
</span>

我们看服务端做了哪些改动,server在前面的基础之上,只对onMessage回调做了修改,修改后的代码如下

<span style="font-size: 16px;">public function onMessage($serv, $frame)
{
    // 循环当前的所有连接,并把接收到的客户端信息全部发送
    foreach ($serv->connections as $fd) {
        $serv->push($fd, $frame->data);
    }
}
</span>

还记得我们之前讲的swoole_server的一系列属性吗,比如setting,worker_id。今天我们再讲一个swoole_server::connections属性,这个属性是一个迭代器对象,记录着当前server所有的连接fd,所以我们这里循环所有的fd,并把客户端接收的消息给每一个客户端。

swoole_server::connections——此属性在1.7.16以上版本可用连接迭代器依赖pcre库(不是PHP的pcre扩展),未安装pcre库无法使用此功能,安装完成后重新编译swoole(可能需要安装新版本)。然后使用php --ri swoole查看swoole扩展相关信息中是否有pcre => enabled。

<span style="font-size: 16px;">yum install -y pcre pcre-devel
</span>

为了对演示的结果看的更明显一些,我们同时打开两个客户端页面操作,看动图

01.gif

再往大了说,我们这个是不是可以扩展为二者的聊天了呢?

有兴趣的可以先捣鼓捣鼓,没兴趣的也可以捣鼓捣鼓了,因为后面我们基于websocket的实例可能不是聊天,而是web通知,敬请期待。

阅读 313