swoole的server和client

swoole的server和client

一、server

server,顾名思义,就是服务端。我们平时接触比较多的无非就是nginx和apache。作为webServer,二者都是通过监听某端口对外提供服务,swoole的server也不例外同样需要绑定端口,同时能够提供给客户端相关的服务。

创建一个server对象

1.实例化Server对象

server的创建,只需要绑定要监听的ip和端口,如果ip指定为127.0.0.1,则表示客户端只能位于本机才能连接,其他计算机无法连接,如果需要所有的客户端都能连接则可以设置0.0.0.0,端口这里指定为9501,可以通过netstat查看下该端口是否被占用。如果该端口被占用,可更改为其他端口,如9502,9503等。

/*创建Server对象,监听 0.0.0.0:9501端口*/
$serv = new swoole_server("0.0.0.0", 9501);

2.设置运行时参数

在跑server之前,我们需要配置一下server,比如调几个人来提供服务(几个进程),以及是否是后台执行(守护进程)等等一些其它的配置。这个并非像fpm直接在文件内配置,我们可以在server创建后,通过set方法指定配置项。同时,这个配置项也有很多,比如说我们可以指定日志文件记录具体的错误信息等等,这个后续我们再深入学习,今天先了解一下worker进程数的配置,因为swoole是多进程的异步服务器所以需要设置工作进程数,提升服务器性能。我们可以指定配置项worker_num等于某个正整数。这个正整数设置多少合适呢?官方建议我们设置为CPU核数的1-4倍。因为我们开的进程越多,内存的占用也就更多,进程间切换也就需要耗费更多的资源。我这里设置开启两个worker进程。因为我笔记本有点卡。。。

/*服务端进行配置*/
$serv->set([
    'worker_num'=>2,//设置进程

]);

3.注册事件回调函数

swoole另外一个比较吸引人的地方,就是swoole_server是事件驱动的。我们在使用的过程中不需要关注底层是怎么实现的,底层是C写的php只是做了个传递的作用,所以只需要对底层相应的动作注册相应的回调,在回调函数中处理业务逻辑即可。
什么意思呢?我举个例子:
你启动了一个server,当客户端连接的时候(触发事件),你不需要关心它是怎么连接的,你就单纯的注册一个connect函数,做一些连接后的业务处理即可(执行业务)。类似于js的事件监听,比如触发了click事件,就会执行相应的闭包。
Swoole监听的事件

//监听客户端连接进入事件
$serv->on('connect', function ($server, $fd) {

      echo "Client: Connect.\n";
});

//监听数据接收事件
$serv->on('receive', function ($serv, $fd, $from_id, $data) {
    $serv->send($fd, "Server: ".$data);
});

//监听连接关闭事件
$serv->on('close', function ($serv, $fd) {
    echo "Client: Close.\n";
});

让我们来看看几种常见的事件回调。
参数$serv是我们一开始创建的swoole_server对象,
参数$fd是唯一标识,用于区分不同的客户端,同时该参数是1-1600万之间可以复用的整数。简单解释下复用:假设现在客户端1、2、3处于连接中,客户端4要连接的话$fd就是4,但是不巧的是客户端3连接不稳定,断掉了,客户端4连接到server的话,$fd就是3,这样看的话。更是让人摩拳擦掌啊,1600W个连接够用吗?单机业务百万连接,已经是很厉害了,不用担心监听客户端数据发送,触发回调了~

4.启动服务器

//启动服务器
$serv->start();

由于swoole_server只能运行在CLI模式下,所以不要试图通过浏览器进行访问,有听说过apache是基于nginx运行的吗,大家地位相同,只能配合,没有上下关系。

总体代码如下:

<?php

/*
1、实例化Server对象
2、设置运行时参数
3、注册事件回调函数
4、启动服务器
*/
//创建Server对象,监听 0.0.0.0:9501端口
$serv = new swoole_server("0.0.0.0", 9501);

//服务端进行配置
$serv->set([
    'worker_num'=>2,//设置进程

]);


//监听客户端连接进入事件
$serv->on('connect', function ($server, $fd) {

      echo "Client: Connect.\n";
});

//监听数据接收事件
$serv->on('receive', function ($serv, $fd, $from_id, $data) {
    $serv->send($fd, "Server: ".$data);
});

//监听连接关闭事件
$serv->on('close', function ($serv, $fd) {
    echo "Client: Close.\n";
});

//启动服务器
$serv->start();

我们来运行一下看看效果如何:

为什么没有反应呢?原来swoole的server是常驻内存运行的,所以如果修改了代码,需要ctrl+c中断,重新运行才行。

我们打开另一个窗口看一下:

接下看一下客户端

二、client

参数说明

默认的swoole的server是可以提供tcp/udp socket请求协议,然后根据请求数据,执行相应的逻辑在PHP中,我们常用socket函数来创建TCP连接,用CURL库来创建Http连接。同样的,为了简化操作,Swoole也提供了同样的Client类用于实现客户端的功能,并且增加了异步非阻塞的模式,让用户在客户端也能使用事件循环。

swoole_client的构造函数如下所示:

/**
 * swoole_client构造函数
 *
 * @param int $sock_type 指定socket的类型,支持TCP/UDP、TCP6/UDP64种
 * @param int $sync_type SWOOLE_SOCK_SYNC/SWOOLE_SOCK_ASYNC  同步/异步
 * @param string $connectionKey 链接的编号,用于长连接复用
 */
public function __construct($sock_type, $sync_type = SWOOLE_SOCK_SYNC, $connectionKey = '')
{
}

第一个参数:
SWOOLE_SOCK_TCP 创建tcp socket
SWOOLE_SOCK_TCP6 创建tcp ipv6 socket
SWOOLE_SOCK_UDP 创建udp socket
SWOOLE_SOCK_UDP6 创建udp ipv6 socket

第二个参数表示是同步还是异步
SWOOLE_SOCK_SYNC 同步客户端
SWOOLE_SOCK_ASYNC 异步客户端
第三个参数(暂不了解)
用于长连接的Key,默认使用IP:PORT作为key。相同key的连接会被复用。

提一下网络协议

什么是网络协议?

网络协议为计算机网络中进行数据交换而建立的规则、标准或约定的集合。

网络编程还是要看一下的,之前看过几本书但是无奈没时间整理,希望后续有时间好好整理一下。要不忘得快啊!

TCP和UDP介绍

  • TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接

  • TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付

  • tcp通过校验和,重传控制,序号标识,滑动窗口、确认应答实现可靠传输。如丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。

  • UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高的通信或广播通信。

  • TCP对系统资源要求较多,UDP对系统资源要求较少。

    跑题了,,,继续搞客户端:

1.同步client

新建一个同步客户端

<?php

// 创建一个同步非阻塞客户端tcp socket
// 第一个参数是表示socket的类型,有下面四种类型选择,这里选则tcp socket就好
/*
SWOOLE_SOCK_TCP 创建tcp socket
SWOOLE_SOCK_TCP6 创建tcp ipv6 socket
SWOOLE_SOCK_UDP 创建udp socket
SWOOLE_SOCK_UDP6 创建udp ipv6 socket
*/

// 第二个参数是同步还是异步
/*
SWOOLE_SOCK_SYNC 同步客户端
SWOOLE_SOCK_ASYNC 异步客户端
*/

$client = new swoole_client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_SYNC);

// 随后建立连接,连接失败直接退出并打印错误码
$client->connect('127.0.0.1', 9501,6) || exit("connect failed. Error: {$client->errCode}\n");

//向服务端发送数据
$client->send("我要连接服务器");

//从服务端接收数据
$response = $client->recv();

// 输出接受到的数据
echo $response . PHP_EOL;

// 关闭连接
$client->close();

echo '我能不能执行';


同步client是同步阻塞的。一整套connect->send()->rev()->close()是同步进行的。如果需要大量的数据处理,后台不能在规定的时间内返回数据会导致接收超时,并且因为是同步执行所以需要等待后台数据的返回.

运行效果如下:

同步异步概念

swoole是既支持全异步,也支持同步,同步跟异步的概念,我们需要了解:

同步与异步的重点在消息通知的方式上,也就是调用结果通知的方式。

同步: 当一个同步调用发出去后,调用者要一直等待调用结果的通知后,才能进行后续的执行。

异步:当一个异步调用发出去后,调用者不能立即得到调用结果的返回。

生活中的例子:

生活中的例子:

同步买奶茶:小明点单交钱,然后等着拿奶茶;

异步买奶茶:小明点单交钱,店员给小明一个小票,等小明奶茶做好了,再来取。

2.异步client

当设定swoole_client为异步模式后,swoole_client就不能使用recv方法了,而需要通过on方法提供指定的回调函数,然后在回调函数当中处理,也就是小明等待奶茶做好了异步通知,消息发送跟接收并不是同步运行的。

<?php
//异步tcp客户端

$client = new swoole_client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC);

//连接服务端
$client->on("connect", function(swoole_client $cli) {
        $cli->send("异步客户端数据");
});

//接收到服务端发送的消息时触发的
$client->on('receive', function ($cli, $data) {
         echo $data;
});

$client->on('error', function ($cli) {
});

//监听连接关闭事件,客服端关闭,或者服务器主动关闭
$client->on('close', function ($cli) {

});

//先绑定事件之后随后建立连接,连接失败直接退出并打印错误码
$client->connect('127.0.0.1', 9501) || exit("connect failed. Error: {$client->errCode}\n");


echo '我能不能执行';

这里我突然脑洞大开有个想法:下篇文章我们来实现一下: 客户端设备发送一个请求,服务端接收,根据业务逻辑的需求服务端A需要发送一个请求到服务端B获取数据,再返回给服务端A,服务端A返回给客户端A。服务端A既是客户端也是服务器,服务端A要发送请求到服务端B,然后服务端B返回消息给服务端A。今天太晚了,明晚再弄这个。最后收尾整理一下tcp粘包处理问题:

三、tcp粘包处理

TCP通信特点

  • TCP 是流式协议没有消息边界,客户端向服务器端发送一次数据,可能会被服务器端分成多次收到。客户端向服务器端发送多少数据。服务器端可能一次全部收到。
  • 保证传输的可靠性,顺序。
  • TCP拥有拥塞控制,所以数据包可能会延后发送。

没有消息边界:
可以理解为水在一个水管里的流动,我们不知道哪段数据是一个我们需要的完整数据。

收发有缓冲区:
比如:当水从一端流到了另一端,我们在收数据的时候,不可能每来一滴水就处理一次,这个缓冲区就相当于有了一个水桶,再接了一定的水之后内核再给数据交到用户空间,这样可以大大提升性能。

什么是 TCP 粘包?

TCP 粘包是指发送方发送的若干包数据 到 接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。

TCP 出现粘包的原因?

发送方:发送方需要等缓冲区满才发送出去,造成粘包

接收方:接收方不及时接收缓冲区的包,造成多个包接收

模拟下产生问题的场景,下面的结果确实是出现了粘包的问题

Swoole怎么处理粘包

EOF 结束协议

通过约定结束符,来确定包数据是否发送完毕

开启open_eof_check=true,并用package_eof来设置一个完整数据结尾字符,同时设置自动拆分open_eof_split

举个例子:

注意:

1、要保证业务数据里不能出现package_eof设置的字符,否则将导致数据错误了。

2、可以手动拆包,去掉open_eof_split,自行 explode("\r\n", $data),然后循环发送

固定包头+包体协议

这种方式也非常常见,原理是通过约定数据流的前几个字节来表示一个完整的数据有多长,从第一个数据到达之后,先通过读取固定的几个字节,解出数据包的长度,然后按这个长度继续取出后面的数据,依次循环。

案例:、

相关配置:
open_length_check:打开包长检测特性
package_length_type:长度字段的类型,固定包头中用一个4字节或2字节表示包体长度。
package_length_offset:从第几个字节开始是长度,比如包头长度为120字节,第10个字节为长度值,这里填入9(从0开始计数)
~:从第几个字节开始计算长度,比如包头为长度为120字节,第10个字节为长度值,包体长度为1000。如果长度包含包头,这里填入0,如果不包含包头,这里填入120
package_max_length:最大允许的包长度。因为在一个请求包完整接收前,需要将所有数据保存在内存中,所以需要做保护。避免内存占用过大。

package_length_type 长度值的类型
长度值的类型,接受一个字符参数,与php的pack函数一致。目前swoole支持10种类型:
c:有符号、1字节
C:无符号、1字节
s:有符号、主机字节序、2字节
S:无符号、主机字节序、2字节
n:无符号、网络字节序、2字节 (常用)
N:无符号、网络字节序、4字节 (常用)
l:有符号、主机字节序、4字节(小写L)
L:无符号、主机字节序、4字节(大写L)
v:无符号、小端字节序、2字节
V:无符号、小端字节序、4字节

posted @ 2019-09-20 22:46  风暴松鼠  阅读(640)  评论(0)    收藏  举报