swoole基础-swoole之websocket初识

什么是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服务器的创建同样非常简单,只需要我们处理好相应的回调即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?php

class WebSocketServer
{
private $_serv;

public function __construct()
{
$this->_serv = new swoole_websocket_server("127.0.0.1", 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();

来看看我们创建的这个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代码,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script>
var ws = new WebSocket('ws://127.0.0.1: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>

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

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

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

客户端先发送给server : This is websocket client.
server收到消息后,在onMessage回调内向客户端推送了 server received data :This is websocket client.
客户端在onmessage回调内收到server的信息,并在server发过来的消息的前面增加了 Client received a message: ,这才有了控制台上面展示的信息
整个过程,完美的交互,至此,客户端和服务端便建立了持久化的双向连接。二者可以互发消息。

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

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

1
2
3
4
5
6
7
8
<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>

界面如图所示,只看结果不要看效果呀,丑丑的界面

然后我们看看js操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script>
var ws = new WebSocket('ws://127.0.0.1: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>

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

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

1
2
3
4
5
6
7
public function onMessage($serv, $frame)
{
// 循环当前的所有连接,并把接收到的客户端信息全部发送
foreach ($serv->connections as $fd) {
$serv->push($fd, $frame->data);
}
}

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

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

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

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