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

1 创建一个server对象

  1. 1、实例化Server对象<br /> 2、设置运行时参数<br /> 3、注册事件回调函数<br /> 4、启动服务器<br /> <br />示例: <br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/1983805/1605704982904-db1c624d-f4fb-4f94-bb7e-a8d46d4cc3ba.png#align=left&display=inline&height=445&margin=%5Bobject%20Object%5D&name=image.png&originHeight=445&originWidth=628&size=51035&status=done&style=none&width=628)

server的创建,只需要绑定要监听的ip和端口,如果ip指定为127.0.0.1,则表示客户端只能位于本机才能连接,其他计算机无法连接,如果需要所有的客户端都能连接则可以设置0.0.0.0

端口这里指定为9501,可以通过netstat查看下该端口是否被占用。如果该端口被占用,可更改为其他端口,如9502,9503等。

2 配置

在享受swoole的server之前,同样我们需要配置一下server,比如调几个人来提供服务(几个进程),以及是否是后台执行(守护进程)等等一些其它的配置。

这个就需要我们做一些配置了,但是并非像fpm直接在文件内配置,我们可以在server创建后,通过set方法指定配置项。 同时,这个配置项也有很多,比如说我们可以指定日志文件记录具体的错误信息等等,你都可以在官网的手册上寻找有哪些配置项

这里我们首要说明一下worker进程数的配置,因为swoole是多进程的异步服务器所以需要设置工作进程数,提升服务器性能。
image.png

我们可以指定配置项worker_num等于某个正整数。这个正整数设置多少合适,即我要开多少个worker进程处理们的业务逻辑才好呢我?官方建议我们设置为CPU核数的1-4倍。因为我们开的进程越多,内存的占用也就更多,进程间切换也就需要耗费更多的资源。我们这里设置开启两个worker进程。默认该参数的值等于你机器的CPU核数。

3 事件驱动**

swoole另外一个比较吸引人的地方,就是swoole_server是事件驱动的。我们在使用的过程中不需要关注底层是怎么实现的,底层是C写的,php只是做了个传递的作用,所以只需要对底层相应的动作注册相应的回调,在回调函数中处理业务逻辑即可。

什么意思呢?我举个例子:

你启动了一个server,当客户端连接的时候(触发事件),你不需要关心它是怎么连接的,你就单纯的注册一个connect函数,做一些连接后的业务处理即可(执行业务)。类似于js的事件监听,比如触发了click事件,就会执行相应的闭包。

Swoole监听的事件:

image.png

几种常见的事件回调

connect 监听客户端连接,触发回调

image.png
参数$serv是我们一开始创建的swoole_server对象

参数$fd是唯一标识,用于区分不同的客户端,同时该参数是1-1600万之间可以复用的整数。

简单解释下复用:假设现在客户端1、2、3处于连接中,客户端4要连接的话$fd就是4,但是不巧的是客户端3连接不稳定,断掉了,客户端4连接到server的话,$fd就是3,这样看的话。1600W个连接够用吗?单机业务百万连接,已经是很厉害了,不用担心

receive 监听客户端数据发送,触发回调

image.png
worker进程内触发的。第三个参数$fromId指的是哪一个reactor线程,具体我们会在多进程模型当中详细分析,先忽略。

我们看第四个参数$data,这个参数就是服务端接受到的数据,注意是字符串或者二进制内容,注意我们在Receive回调内,调用了$serv的send方法,我们可以使用send方法,向client发起通知。

close 监听客户端关闭,触发回调

image.png
这个很简单,当客户端关闭,或者服务端主动关闭连接的时候会触发。

到此呢,我们基本上已经搭建到了一个高性能的server。我们只需要调用start方法启动server即可。
image.png

swoole server运行在cli模式下

由于swoole_server只能运行在CLI模式下,所以不要试图通过浏览器进行访问,浏览器是HTTP方式请求

我们在命令行下面执行,tese.php就是刚刚服务器的方法
image.png
我们平时执行完一个指令,执行完就结束了,但是现在的情况正好相反,当前程序一直处于执行中的状态,并没有退出终端。因为swoole的server是常驻内存运行的,所以如果
修改了代码,需要ctrl+c中断,重新运行才行。

在我们第一步初始化server所填写的ip和端口,也就是说server现在正在监听9501端口提供服务。
我们新开一个终端,查看端口发现确实如此。
image.png
server启动好了能干什么呢?常见的网络编程模式都是client-server的,也就是说我们还需要模拟一个客户端与之交互。

二 同步client跟异步client

默认的swoole的server是可以提供tcp/udp socket请求协议,然后根据请求数据,执行相应的逻辑

在PHP中,我们常用socket函数来创建TCP连接,用CURL库来创建Http连接。同样的,为了简化操作,Swoole也提供了同样的Client类用于实现客户端的功能,并且增加了异步非阻塞的模式,让用户在客户端也能使用事件循环。

swoole_client的构造函数如下所示:

swoole_client->__construct($sock_type,SWOOLE_SOCK_SYNC, $key);

第一个参数 $sock_type:
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
用于长连接的Key,默认使用IP:PORT作为key。相同key的连接会被复用

1 新建一个同步客户端

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

2 异步客户端(废除)

4.3被移除,但是可能有其它的设备或者是语言是长连接,并且用来演示心跳检测

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

3 心跳检测

心跳是什么?

顾名思义,心跳是判断一个事物生还是死的一个标准,在swoole里,心跳是指用来判断一个连接是正常还是断开的

为什么要心跳?

心跳的目的其实是通过判断客户端是否存活,从而回收fd,
系统为什么要回收fd,因为fd资源是有限的,所以必需重复利用

心跳作用主要有两个:

1、客户端定时给服务端发送点数据,防止连接由于长时间没有通讯而被某些节点的防火墙关闭导致连接断开的情况。

2、服务端可以通过心跳来判断客户端是否在线,如果客户端在规定时间内没有发来任何数据,就认为客户端下线。这样可以检测到客户端由于极端情况(断电、断网等)下线的事件。

心跳在swoole里的实现?

swoole会在主进程独立起一个心跳线程,通过定时轮询所有的连接,来判断连接的生死,所以swoole的心跳不会堵塞任何业务逻辑。
image.png设置完成了之后,你会发现设置了定时检测之后,如果客户端没在规定的时间之内发送数据就会关闭。

heartbeat_idle_time: 连接最大的空闲时间 (如果最后一个心跳包的时间与当前时间之差超过这个值,则认为该连接失效)

heartbeat_check_interval: 服务器定时检测在线列表的时间

配置建议

建议 heartbeat_idle_time 为 heartbeat_check_interval 的两倍多一点。
这个两倍是为了进行容错,允许丢一个包而多一点是考虑到网络的延时。

为什么需要心跳包?客户端如何维持心跳?

在从客户端到服务器的一条巨大的链路中会经过无数的路由器,其中每一个路由器都有可能会有检测到多少秒时间内无数据包则自动关闭连接的这种节能机制,为了让这个可能会出现的节能机制失效,

客户端可以设置一个定时器,每隔固定的时间都发一个随机字符的一字节的数据包,通常我们把这种数据包就叫做心跳包。

实战案例

客户端设备发送一个请求,服务端接收,
根据业务逻辑的需求,服务端A需要发送一个请求到服务端B获取数据,再返回给服务端A,服务端A返回给客户端A。

服务端A既是客户端也是服务器

image.png

什么是网络协议?

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

协议分层

TCP/IP协议族是一个四层协议系统,自底而上分別是数据链路层、网络层、传输层和应用层。每一层完成不同的功能,且通过若干协议来实现,上层协议使用下层协议提供的服务
image.png
image.png

数据链路层

数据链路层实现了网卡接口的网络驱动程序,以处理数据在物理媒介上的传输。

数据链路层两个常用的协议是ARP协议(Address Resolve Protocol,地址解析协议)和RARP协议(Reverse Address Resolve Protocol,逆地址解析协议).它们实现了 IP地址和机器物理地址(MAC地址)之间的相互转换,那处理的流程是怎么样的呢?

当数据交换时上层网络层使用IP地址寻找一台机器,而数据链路层使用物理地址(Mac地址)寻找一台机器,因此网络层必须先将目标标机器的IP地址转化成其物理地址,才能使用数据链路提供的服务,这就 ARP协议的用途

网络层

网络层实现数据包的选路和转发,网络层最核心的协议是IP协议(Internet Protocol,因特网协议)。IP协议根据数据包的目的IP地址来决定如何投递它。

网络层另外一个重要的协议足ICMP协议(Internet Control Message Protocol»因特网控制制报文协议)。它是IP协议的重要要补充,主要用于检测网络连接,比如可以通过发送报文来检测目标是否可达

传输层

网络层当中的IP协议为了解决传输当中的路径选择的问题,只需要按照此路径传输数据即可,传输层利用udp或者tcp协议以及网络层提供的路径信息为基础完成实际的数据传输,所以称为传输层

TCP和UDP对比介绍

1、TCP面向连接(如打电话要先拨号建立连接);
UDP是无连接的,即发送数据之前不需要建立连接
2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;
UDP尽最大努力交付,即不保证可靠交付
3、TCP通过校验和,重传控制,序号标识,滑动窗口、确认应答实现可靠传输。如丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。
UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高的通信或广播通信。
4、TCP对系统资源要求较多,UDP对系统资源要求较少。
image.png

应用层

应用层负责处理应用程序的逻辑。数据链路层、网络层和传输层负责处理网络通讯细节,这部分必须既稳定又高效,因此它们都在内核空间中实现,应用层则负责在用户户空间实现,因为它负责处理众多逻辑。

应用层协议举例:
FTP文件传输协议是 TCP/IP网络上两台计算机传送文件的协议
SMTP(SimpleMailTransferProtocol)即简单邮件传输协议

TCP粘包处理

https://wiki.swoole.com/#/learn?id=tcp%e7%b2%98%e5%8c%85%e9%97%ae%e9%a2%98
TCP通信特点
1. TCP 是流式协议没有消息边界,客户端向服务器端发送一次数据,可能会被服务器端分成多次收到。客户端向服务器端发送多少数据。服务器端可能一次全部收到。

2.保证传输的可靠性,顺序。

3.TCP拥有拥塞控制,所以数据包可能会延后发送。

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

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

什么是 TCP 粘包?

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

TCP 出现粘包的原因?

发送方:发送方需要等缓冲区满才发送出去,造成粘包
接收方:接收方不及时接收缓冲区的包,造成多个包接收

下图为tcp协议在传输数据的过程:
image.png
每个 socket 被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区。
write()/send() 并不立即向网络中传输数据,而是先将数据写入缓冲区中,再由TCP协议将数据从缓冲区发送到目标机器。一旦将数据写入到缓冲区,函数就可以成功返回,不管它们有没有到达目标机器,也不管它们何时被发送到网络,这些都是TCP协议负责的事情。

TCP协议独立于 write()/send() 函数,数据有可能刚被写入缓冲区就发送到网络,也可能在缓冲区中不断积压,多次写入的数据被一次性发送到网络,这取决于当时的网络情况、当前线程是否空闲等诸多因素,不由程序员控制。

模拟下产生问题的场景

1、粘包问题
2、不要send完后立即close((https://wiki.swoole.com/wiki/page/313.html)
image.png
服务端在发送数据的时候,因为客户端可能由于网络的原因,没有及时接收到数据,然后连接关闭,导致的问题
image.png
image.png
对于任何的可靠的消息发送来讲,一定要有一个消息的确认机制、重试机制(IM(websockt))

Swoole怎么处理粘包

EOF 结束协议

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

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

举个例子:
image.png

注意:
1、要保证业务数据里不能出现package_eof设置的字符,否则将导致数据错误了。
2、可以手动拆包,去掉open_eof_split,自行 explode(“\r\n”, $data),然后循环发送
image.png

固定包头+包体协议

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

案例:
image.png

相关配置:
open_length_check:打开包长检测特性

package_max_length:最大允许的包长度。因为在一个请求包完整接收前,需要将所有数据保存在内存中,所以需要做保护。避免内存占用过大。

package_length_type:长度字段的类型,固定包头中用一个4字节或2字节表示包体长度。
package_length_offset:从第几个字节开始是长度,比如包头长度为120字节,第10个字节为长度值,这里填入9(从0开始计数)
package_body_offset:从第几个字节开始计算长度,比如包头为长度为120字节,第10个字节为长度值,包体长度为1000。如果长度包含包头,这里填入0,如果不包含包头,这里填入120


注意:
如果没有按照固定的打包方式去打包数据或者数据包太大都会出现如下错误
image.png

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字节

字节序的理解

计算机硬件有两种储存数据的方式:大端字节序(big endian)和小端字节序(little endian)。
大端字节序:高位字节在前,低位字节在后,这是人类读写数值的方法。
小端字节序:低位字节在前,高位字节在后,即以0x1122形式储存。

为什么要有字节序呢?
计算机电路先处理低位字节,效率比较高,因为计算都是从低位开始的。所以,计算机的内部处理都是小端字节序。

不过,人类还是习惯读写大端字节序。所以,除了计算机的内部处理,其他的场合几乎都是大端字节序,比如网络传输和文件储存。

所以我们要确定通信双方交流的信息单元应该以什么样的顺序进行传送。如果达不成一致的规则,计算机的通信与存储将会无法进行