1.简介
网址:http://www.swoole.com/ 韩天峰
Swoole:面向生产环境的 PHP 异步网络通信引擎,Swoole 使用纯 C 语言编写,Swoole是PHP一个扩展的形式。
Swoole可以使 PHP开发人员编写高性能的异步并发 TCP、UDP、Unix Socket、HTTP,WebSocket 服务。
Swoole 可以广泛应用于互联网、移动通信、企业软件、云计算、网络游戏、物联网(IOT)、车联网、智能家居等领域。
注:这个几乎和nodejs一样的东西,但是它效率比nodejs高一点点(其实可以忽略不记),因为是纯c编写的,nodejs是对Chrome V8引擎进行了封装,v8是基于c++开发的。
http2.0默认支持websocket,未来肯定是潮流
rpc 远程过程调用(微服务)
2.下载和安装
swoole是一个PHP的扩展,所以安装的方式和安装其它的PHP扩展的方式一样。swoole不支持windows安装,没有windows扩展。 linux系统或Mac系统 Docker也是可以的
本篇操作的环境是 centos8,云上环境(腾讯云)、phpstorm
注:不管是安装redis还是memcache 都可以通过yum指令一键安装,下面是编译安装swoole的过程步骤。
1.下载地址
Github:
https://github.com/swoole/swoole-src/tags
php官方扩展库(推荐):
http://pecl.php.net/package/swoole
mkdir /src
cd /src
wget http://pecl.php.net/get/swoole-4.6.2.tgz
2.安装依赖环境
官网地址(查看依赖):
https://wiki.swoole.com/#/environment
2.1仅支持Linux,FreeBSD,MacOS,3类操作系统
2.2Linux内核版本2.3.32如centos必须6.6以上
uname -r
2.3对php版本的要求(根据安装不同的swoole版本有不同的要求)
http://pecl.php.net/package/swoole/4.6.2
这里我的php是通过yum方式安装的。
直接一条指令搞定,前提是你centos得用最新得8.x版本
yum -y install php
执行后默认就是7.2版本,好比centos7默认是5.4版本
2.4gcc4.4以上版本
gcc --version
没有的话就自行安装
yum install -y gcc-c++
ubuntu apt install -y g++
2.5cmake2.4以上版本
cmake --version
如果没有得话自定安装
yum install -y cmake
3.安装swoole
3.1解压swoole
#解压 那个-zxf 前面的中横线可写可不写 v一般不写,v是显示过程
tar zxf swoole-4.6.2.tgz
3.2观察目录结构
正常的在linux下安装源码编译安装无非就是三步走
./configure
make
make install
但是我们观察得出,swoole目录里面没有./configure
这个可执行文件,如何让它有呢?因为swoole是php的拓展,而在linux系统上编译安装php拓展要先通过phpize
初始化一次,但是通过yum安装php的话 phpize
这个可能不一定有,phpize
在php-dev这个开发包里面,如果yum安装后面没有指定这个开发包的话,就没有phpize这个命令
3.3检测php开发包是否安装
表示这个指令依赖的开发包
yum search php |grep dev
这个开发包是用来安装php的拓展的
yum install -y php-devel.x86_64
3.4初始化
3.5配置安装和环境检查
./configure --with-php-config=/usr/bin/php-config
php-config
就是因为上面安装了php-devel.x86_64
这个开发包后才有此命令
一路正常
3.6编译安装
make && make install
编译安装成功,且已经把swoole.so
文件复制到php的拓展目录下
3.7修改配置文件让其生效
我们发现默认swoole没有
yum方式安装php的配置文件目录/etc/php
yum方式不需要在php.ini修改,在php.d/
里面修改,里面每个拓展就对应一个配置文件
我们随便找个配置打开看看里面的内容
我们也模仿着弄一个20-swoole.ini
再次查看swoole模块
swoole
彻底安装完成
3.快速起步
官方手册:
https://wiki.swoole.com/#/consts
1.进程管理
swoole是常驻内存框架,可以做到防止内存泄漏的问题,因为比如有个进程一直不关闭,可能有个变量没有释放,久而久之可能产生泄露或是溢出的问题
swoole是一个多进程,多线程的服务
swoole启动的时候会分配一个master主进程,同时产生 manger进程,同时产生多个worker和taskworker进程
master主进程负责创建多个线程来接受和返回用户请求,同时生成一个manager进程,manager进程负责生成和管理N多个worker和task进程,worker和task进程是负责干活的
apache和nginx访问的时候会重新创建了一个进程进行访问,访问结束后http请求就会被apache或者nginx收回,但是swoole内存是常驻的,如果不释放内存,就会一直在内存中,因此manager
进程就是用来监控这些内存,同时生成一个新的进程,因为一切都是新的所以效率也会提高。
进程:比如电脑上的计算器.exe 和画图.exe 运行后两个就会产生各自的进程
比如这个就是两个不同的进程
进程和进程之间是无法通信的,想要通信可以共享内存区域
线程是通过进程创建出来的,所以线程和线程之间天生就是可以通信的
这个可能不是很好理解,但是如果学过c++的同学,肯定很清楚这些。
2.环境准备
开发我们不可能在linux系统上使用vi
或者vim
编辑器进行开发,因此我们需要本地开发,通过 ftp或者sftp上传代码到linux系统中
这里使用phpstorm提供的ftp来直接保存上传代码
2.1配置phpstorm支持ftp上传
首先我们在linux系统中随便找个目录来存放代码(因为我们是使用php做服务,这里不用像apache或者nginx一样配置虚拟主机指定位置)
重新创建一个php项目
配置phpstorm
需要证书,点击yes
找到刚才创建的目录
映射配置
新创建一个测试文件
手动上传
查看详细上传信息
查看服务器已经被成功上传
如何配置保存就自动上传呢?
线上立马就同步了
2.2让phpstorm更好的支持swoole开发
插件地址:
https://github.com/wudi/swoole-ide-helper
下载后放到项目目录下就行了
就会有提示了
3.创建tcp服务器
步骤:
3.1构建Server对象
3.2设置运行时参数
3.3注册事件回调函数
3.4启动服务
3.1创建好tcp服务
我们查看一下端口
netstat -tunpl
我们发现apache就是tcp
我们来学习搭建一个自己的tcp服务器,新建一个01_tpc_server.php
用于测试
官网的教程
$serv = new Swoole\Server('0.0.0.0', 9501, SWOOLE_PROCESS, SWOOLE_SOCK_TCP);
// 参数说明
$host参数用来指定监听的ip地址 0.0.0.0监听全部地址
$port监听的端口,如9501 0-1024之间,是系统默认保留的,所在建议从5000
$mode运行的模式 SWOOLE_PROCESS多进程模式(默认) SWOOLE_BASE基本模式
$sock_type指定Socket的类型 支持TCP、UDP等
这里其实就能看到默认的常量值
回调事件
我们随便看一个回调函数的参数例如Connect
//监听事件
// 连接tcp事件
//参数一:server对象
//参数二:客户端的ID号
//参数三:接收处理的线程ID
$server->on('Connect', function(swoole_server $server, int $fd, int $reactorId){
echo "有新的连接接入:".$fd."\n";
});
//参数一:server对象
//参数二:客户端的ID号
//参数三:接收处理的线程ID
//参数四:接收到的数据
$server->on('Receive', function(swoole_server $server, int $fd, int $reactor_id, string $data){
echo "接收到的数据为:".$data."\n";
});
//参数一:server对象
//参数二:客户端的ID号
//参数三:接收处理的线程ID
$server->on('Close', function(swoole_server $server, int $fd, int $reactorId){
echo $fd."离开了我们"."\n";
});
//启动服务
$server->start();
php执行该文件启动服务
发现我们刚才的服务被启动了
netstat -tunpl
查看进程
ps auxf | grep php
这个刚好符合上面的架构 woker进程是可以配置它的数量的
可以通过以下命令进行杀死
kill -9 pid
怎么配置看官网
$server->set([
//启动worker进程的数量
'worker_num' => 1,
]);
再次查看进程
3.2 telnet进行测试
创建好tcp服务后,需要用telnet进行测试
在linux上下载telnet
yum install -y telnet
#telnet 地址 端口
telnet 127.0.0.1 9501
回车进入,按下ctrl+]再次回车,就可以发内容,退出,按ctrl+] 输入 quit 退出
用telnet一连接 服务器哪里就有会提示
telnet退出也有反应
再次连接后,发现它不是从1开始计算,因为它想着你还可能再次连接,
因为1的内存还没释放,防止1还要再回来,这是linux内核的机制
服务端挂掉客户端也有反应
windows下也有telnet工具,可能默认没有开启,需要手动开启
注意此时这里是在本地需要通过远程的IP地址(腾讯云)连接
telnet 192.168.xxx.xxx 端口
但是这个windows上的这个telnet并不好用,因为它没输入任何一个字符它都会响应
而linux下的telnet则是按下回车才有反应
3.3tcp客户端链接tcp
客户端连接分为同步和异步,但是swoole4不再支持异步客户端,相应的需求完全可以用协程客户端代替
新建一个php文件02_tcp_client.php
<?php
//tcp客户端
$client = new \Swoole\Client(SWOOLE_SOCK_TCP);
//连接
//参数3是 超时时间
$client->connect('127.0.0.1','9501',10);
//发送数据
$client->send('大家好,我是新来的客户端');
//关闭
$client->close();
测试
执行02_tcp_client.php
测试服务器就会有反应
接收服务器端发送过来的数据
修改两个地方
服务端
客户端
测试
因为swoole是常驻内存,我们改动了代码,需要重新再次启动服务端
3.4原生php实现tcp客户端
新建03_php_tcp_client.php
测试
<?php
//原生php 发起一个 tcp
// socket 当作是网络中的一个文件
$socket = stream_socket_client('tcp://42.194.192.22:9501',$errno,$errstr,30);
//发送数据
fwrite($socket,"我是原生php的tcp客户端");
//接受数据
$buffer = fread($socket,9000);
//关闭
fclose($socket);
echo $buffer;
重启服务端
测试
浏览器端访问
3.5rpc
RPC,是一种远程调用方式(Remote Procedure Call),通过RPC我们可以像调用本地方法一样调用别的机器上的方法,用户将无感服务器与服务器之间的通讯。RPC在微服务当中起到相当大的作用,当然RPC不是微服务必须的一种方式,有别的方式也可以实现这种远程调用例如RESTful API就可以实现远程调用。如果有用过SOAP
那么你使用RPC将会觉得很类似,都是可以直接调用别的机器上的方法。
随着业务的发展我们的项目从简单的单体结构逐渐的演化成微服务结构,我们为什么要拆分成微服务呢?那我们来说说微服务和单体架构的优缺点。
3.5.1单体架构图示
简单点说,就好比是tp框架中每个控制器就对应着一个模块,所有的功能模块都在一个服务器上就完成。
3.5.2微服务架构图示
在2014年被提出,现在国内很多公司已经使用,微服务是一种架构设计,并不是说什么框架或者代替什么。微服务做的事情是按照项目颗粒度进行服务的拆分,把模块单独拿出来做成每一个单独的小项目。微服务的主要特点有:每一个功能模块是一个小项目、独立运行在不同进程或者机器上、不同功能可以又不同的人员开发独立开发不松耦合、独立部署不需要依赖整体项目就可以启动单个服务、分布式管理。每一个服务只要做好自己的事情就好了。在设计微服务的时候还需要考虑到数据库的问题,是所有微服务使用共同一个数据库还是每一个服务单个数据库
3.5.3单体架构优点
-
部署容易,如php写的项目,只要一个文件夹复制到支持php的环境就可以了,java只需要一个jar包
-
测试容易,我们整体项目只要改了一个地方马上就可以测试得出结果
-
负载均衡就可以解决,快速部署多个一模一样的项目在不同的机器运行分流
3.5.4单体架构的缺点
-
部署的问题,对于php来说这点还好,但是对于java的项目来说,我们需要重新打包整个项目耗费的时间是很长的
-
代码维护,由于所有的代码都写在一个项目里面,要想要修改某一个功能点那么需要对项目的整体逻辑和设计有较深的理解,否则代码耦合严重,导致维护难,特别对于新入职的员工来说这将是最容易出现问题的地方
-
开发效率低,随着项目需求的不断改变和新的功能新增,老旧的代码又不敢随便删除,导致整个项目变得笨重,这将会增加你阅读代码的时间
-
扩展性,在高并发的情况下,我们往往不是整个项目的每一个功能都处于高流量高请求的情况下的,很多时候都是某一个功能模块使用的人数比较多,在单体结构下我们没有办法针对单个功能实现分布式扩展,必须整个项目一起部署
3.5.5微服务优点
-
拆分业务,把整体大项目分割成不同小项目运行在不同进程或者机器上实现数据隔离
-
技术栈,每个服务可以由不同的团队或者开发者进行开发,外部调用人员不需要操心具体怎么实现的,只需要类似调用自己方法一样或者接口一样按照服务提供者给出来的参数传递即可
-
独立部署,每一个服务独立部署,部署一个服务不会影响整体项目,如果部署失败最多是这个服务的功能缺失,并不影响其他功能的使用
-
按需部署,针对不同的需求可以给不同的服务自由扩展服务器,根据服务的规模部署满足需求的实例
-
局部修改,当一个服务有新需求或者其他修改,不需要修改整体项目只要管好自己的服务就好了
3.5.6微服务缺点
-
运维,微服务由于把业务拆分得细,有可能部署在不同机器上,因此对于运维人员的管理来说,这部分的成本会加大
-
接口调整,微服务之间通过接口进行通信。如果修改某个微服务的API,可能所有使用了该接口的微服务都需要做调整;
-
重复劳动,很多服务可能都会使用到相同的功能。而这个功能并没有达到分解为一个微服务的程度,这个时候,可能各个服务都会开发这一功能,导致代码重复。
-
分布式,由于会把不同服务部署在不同机器上,那么对于这些服务的调用、容错、网络延迟、分布式事务等等都是一个很大的挑战,当然微服务不一定全部都是部署在不同服务器上
3.5.7RPC调用和RESTful API两者之间的区别
通过RPC或者RESTful API都可以实现微服务,区别就在于
-
TCP的支持保持连接,当调用服务的时候不需要每次都进行三次握手才实现。从性能和网络消耗来说RPC都具备了很好的优势。
-
RESTful API 基于HTTP的,也就是说每次调用服务都需要进行三次握手建立起通信才可以实现调用,当我们的并发量高的时候这就会浪费很多带宽资源
-
服务对外的话采用RESTful API会比RPC更具备优势,因此看自己团队的服务是对内还是对外
3.6rpc实现首页显示
新建一个rpm/demo.php
默认展示就是这样
用rpc方式实现首页展示,分别把头部和身体和尾部都分别用一个服务给展示出来
为每个结构创建一个php文件
每个结构文件中都开启服务然后对应的内容,注意:端口要不一样
依次是9501,9502,9503
为了方便测试,这里配置参数,设置成守护进程,这样开启服务的时候就不会占用黑窗口
开启前把之前所有的服务杀死
pkill php
都清理干净了,把服务开启
编写一个调用文件,来分别调用这个三个服务
<?php
$socket1 = stream_socket_client('tcp://xxx.xxx.xxx.xxx:9501',$errno,$errstr,30);
//发送数据
fwrite($socket1,"我是原生php的tcp客户端");
//接受数据
$header = fread($socket1,9000);
//关闭
fclose($socket1);
$socket2 = stream_socket_client('tcp://xxx.xxx.xxx.xxx:9502',$errno,$errstr,30);
//发送数据
fwrite($socket2,"我是原生php的tcp客户端");
//接受数据
$main = fread($socket2,9000);
//关闭
fclose($socket2);
$socket3 = stream_socket_client('tcp://xxx.xxx.xxx.xxx:9503',$errno,$errstr,30);
//发送数据
fwrite($socket3,"我是原生php的tcp客户端");
//接受数据
$footer = fread($socket3,9000);
//关闭
fclose($socket3);
echo $header;
echo $main;
echo $footer;
访问的效果还是一样的,但是实际上已经分别调用了三个服务
3.7tp实现rpc调用
thinkphp框架改名Order模拟一个模块服务,同时准备一个order.php
用于开启服务
找到线上框架的路径
修改框架路径,同时注意端口号
代码片段
```bash
<?php
//构建服务
$server = new \Swoole\Server('0.0.0.0', 9502);
//配置
$server->set(
[
//守护进程
'daemonize'=>1,
//启动worker进程的数量
'worker_num' => 1,
]
);
$server->on(
'Receive',
function (swoole_server $server, int $fd, int $reactor_id, string $data) {
static $import = true;
if ($import) {
define('APP_PATH', '/wwwdata/rpc/Order/application'); //框架目录
define('RPC_RUN', true);
$path = '/wwwdata/rpc/Order/thinkphp/base.php'; //base文件所在路径
include $path;
}
$import = false;
$app = new \think\App();
$ret = $app->run();
$server->send($fd, $ret);
}
);
//启动服务器
$server->start();
开启服务,开启前记得先把之前的服务全部杀死
```bash
pkill php
编写一个客户端tp_client.php
用于连接测试
<?php
$socket = stream_socket_client('tcp://xxx.xx.xxx.xxx:9502',$errno,$errstr,10);
//发送数据
fwrite($socket,"我是原生php的tcp客户端");
//接受数据
$body = fread($socket,9000);
//关闭
fclose($socket);
echo $body;
成功调用tp框架
现在这个是写死的,因为tp框架默认就是访问inex/index/index,我们如何访问不同的模块控制器和方法呢?那就需要传递参数,实际开发中,这种调用微服务的方式,不会通过地址栏去调用,而是规定首页需要调用哪些服务,商品页需要访问哪些服务等等,为了方便测试这里我们通过地址栏方式访问实现访问框架中不同的方法
在框架中新建一个方法
在客户端中发送path_info给服务器
就是发送这一段
服务器中接受传递给框架
在之前的代码上加上这一段就可了
$_REQUEST['argv_rpc'] = $data;
总结:在实际开发中,每个微服务应该返回json格式的数据,而不是输出html,这里只是为了学习测试。
4.搭建Web服务器
4.1关于性能
这是官网截图
https://wiki.swoole.com/#/question/swoole?id=%e6%80%a7%e8%83%bd%e9%97%ae%e9%a2%98
4.2构建web服务器
<?php
$http = new Swoole\Http\Server('0.0.0.0', 9501);
//配置(非必须)
$http->set(
[
//启动worker进程的数量
'worker_num' => 1,
]
);
//事件监听,这个和node.js一样的。也是request
$http->on('request', function ($request, $response) {
//可以修改状态码
$response->status(404);
//响应头,告诉浏览器是utf8,否则中文乱码
$response->header("Content-Type", "text/html; charset=utf-8");
//响应的内容
$response->end("你好,世界");
});
$http->start();
访问
使用ab工具进行压测,新建一个会话用于测试
先尝试ping ip能否通信
测试端口是否能通,因为端口可能被防火墙规则给挡住了
可以通过 telnet
工具进行测试
telnet: connect to address xx.xxx.xxx.xx: Connection refused
telnet:连接到地址xx.xxx.xxx.xx:连接被拒绝
当然我们用的最多的是 nc
指令,因为telnet指令还要退出一下,比较麻烦
# nc -v -w 10 %IP% -z %PORT%
-v 显示指令执行过程。
-w <超时秒数> 设置等待连线的时间。
-u 表示使用UDP协议
-z 使用0输入/输出模式,只在扫描通信端口时使用
要使用ab测试工具需要
yum install -y httpd-tools
ab -c100 -n1000 -k url地址
-c 并发的人数
-n 总的请求次数
-n 测试会话中所执行的请求个数,默认仅执行一个请求
-c 一次产生的请求个数,即同一时间发出多少个请求,默认为一次一个
-t 测试所进行的最大秒数,默认为无时间限制....其内部隐含值是[-n 50000],它可以使对服务器的测试限制在一个固定的总时间以内
-p 包含了需要POST的数据的文件
-T POST数据所使用的Content-type头信息
-v 设置显示信息的详细程度
-w 以HTML表格的形式输出结果,默认是白色背景的两列宽度的一张表
-i 以HTML表格的形式输出结果,默认是白色背景的两列宽度的一张表
-x 设置<table>属性的字符串,此属性被填入<table 这里>
-y 设置<tr>属性的字符串
-z 设置<td>属性的字符串
-C 对请求附加一个Cookie行,其典型形式是name=value的参数对,此参数可以重复
-H 对请求附加额外的头信息,此参数的典型形式是一个有效的头信息行,其中包含了以冒号分隔的字段和值的对(如"Accept-Encoding: zip/zop;8bit")
-A HTTP验证,用冒号:分隔传递用户名及密码
-P 无论服务器是否需要(即是否发送了401认证需求代码),此字符串都会被发送
-X 对请求使用代理服务器
-V 显示版本号并退出
-k 启用HTTP KeepAlive功能,即在一个HTTP会话中执行多个请求,默认为不启用KeepAlive功能
-d 不显示"percentage served within XX [ms] table"的消息(为以前的版本提供支持)
-S 不显示中值和标准背离值,且均值和中值为标准背离值的1到2倍时,也不显示警告或出错信息,默认会显示最小值/均值/最大值等(为以前的版本提供支持)
-g 把所有测试结果写入一个'gnuplot'或者TSV(以Tab分隔的)文件
-e 产生一个以逗号分隔的(CSV)文件,其中包含了处理每个相应百分比的请求所需要(从1%到100%)的相应百分比的(以微妙为单位)时间
-h 显示使用方法
-k 发送keep-alive指令到服务器端
4.3静态服务器
静态目录要配置正确
<?php
$http = new Swoole\Http\Server('0.0.0.0', 9501);
//配置(非必须)
$http->set(
[
//启动worker进程的数量
'worker_num' => 1,
//静态资源配置选项 项目中的静态资源:html htm css js 图片 视频 等
'document_root'=>'/wwwdata/web',
'enable_static_handler'=>true
]
);
//事件监听,这个和node.js一样的。也是request
$http->on('request', function ($request, $response) {
//可以修改状态码
$response->status(404);
//响应头,告诉浏览器是utf8,否则中文乱码
$response->header("Content-Type", "text/html; charset=utf-8");
//响应的内容
$response->end("你好,世界");
});
$http->start();
如果换成这样访问又访问不到了
只需要修改配置直接写到根目录即可
就好比你打开一个目录后,可以随意访问里面的目录或者文件一样
4.4动态服务器
如果动态解析php文件呢?
我们新建一个web/api.php
文件
<?php
$arr = ['id'=>1,'name'=>'Jack'];
echo json_encode($arr,JSON_UNESCAPED_UNICODE);
同时新建一个04_http_server.php
创建http服务
打印一下客户端的数据信息
返回结果
获取到几个重要的信息
我们知道 php是一个服务器端执行的语言,实际上其实就是客户端发送地址服务器接收地址,找到本地对应的php文件解析然后返回回去。
<?php
$http = new Swoole\Http\Server('0.0.0.0', 9501);
//配置(非必须)
$http->set(
[
//启动worker进程的数量
'worker_num' => 1,
//静态资源配置选项 项目中的静态资源:html htm css js 图片 视频 等
'document_root'=>'/wwwdata',
'enable_static_handler'=>true
]
);
//事件监听,这个和node.js一样的。也是request
$http->on('request', function ($request, $response) {
//查看客户端的信息
// var_dump($request);
//获取客户端请求的文件路径
$req_file = $request->server['request_uri'];
//指定真实的文件路径
$filepath = __DIR__.'/web/'.$req_file;
//状态码
$status = 404;
//返回数据 JSON_UNESCAPED_UNICODE:是php5.4提供的解决中文乱码问题
$ret = json_encode(['status'=>1000,'msg'=>'没数据'],JSON_UNESCAPED_UNICODE);
//判断文件是否存在
if(file_exists($filepath)){
//文件存在
$status = 200;
//为了不让结果返回到服务端上,把结果放到缓冲区
//开启缓冲区
ob_start();
include $filepath;
//读取缓冲区的数据
$ret = ob_get_contents();
//清空缓冲区
ob_clean();
}
//可以修改状态码
$response->status($status);
//响应头,告诉浏览器是utf8,否则中文乱码 这里返回的是json格式
$response->header("Content-Type", "application/json; charset=utf-8");
//响应的内容
$response->end($ret);
});
$http->start();
成功访问
接收客户端传递过来的数据
用apache的时候正常这样是可以接收到数据的
封装$_get $_post $_files数据的获取
$_GET = $request->get;
$_POST = $request->post;
$_FILES = $request->files;
成功接收到数据
5.搭建websocket服务
1.介绍
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
websocket解决服务器端与客户端即时通信的问题。
协议名:ws 加密通信 wss 通信成功 状态码 101
单工:对讲机
双工:打电话
http2.0收录WebSocket
2.浏览器支持
从ie11开始都支持
对于ie11以前的可以用下面这个库来解决
https://socket.io/
3.html5中websocketApi
var ws = new WebSocket("ws://localhost:9501");
3.1Websocket事件
事件 | 事件处理程序 | 描述 |
---|---|---|
open | Socket.onopen | 连接建立时触发 |
message | Socket.onmessage | 客户端接收服务端数据时触发 |
error | Socket.onerror | 通信发生错误时触发 |
close | Socket.onclose | 连接关闭时触发 |
3.2WebSocket 方法
方法 | 描述 |
---|---|
Socket.send() | 使用连接发送数据 |
Socket.close() | 关闭连接 |
3.3swoole实现websocket服务
WebSocket\Server 继承自 Http\Server
新建一个ws_server.php
<?php
//创建WebSocket Server对象,监听0.0.0.0:9502端口
$ws = new Swoole\WebSocket\Server('0.0.0.0', 9502);
//监听WebSocket连接打开事件
$ws->on('open', function ($ws, $request) {
$ws->push($request->fd, "欢迎连接我们的聊天室\n");
});
//监听WebSocket消息事件(必不可少)
$ws->on('message', function ($ws, $frame) {
//服务端给客户端发送消息
$ws->push($frame->fd, "server: {$frame->data}");
});
//监听WebSocket连接关闭事件
$ws->on('close', function ($ws, $fd) {
echo "client-{$fd} is closed\n";
});
$ws->start();
3.4mvp方式实现一个聊天功能
新建一个ws.html
html5文件用来连接websoket
<!doctype html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script>
//客户端连接websoket服务
const ws = new WebSocket('ws://xx.xxx.xxx.xx:9502');
//事件监听
//建立链接时的监听
ws.onopen = ()=>{
console.log("链接建立");
}
//接受消息事件
// ws.onmessage = evt =>{
// console.log(evt)
// }
//解构赋值
ws.onmessage = ({data}) =>{
console.log(data)
}
//关闭事件
ws.onclose = ()=>{
console.log("服务器断开了");
}
</script>
</body>
</html>
刷新页面就会触发事件
同时每刷新一次则会重新连接一次
修改ws.html
构建输入框用于发送信息
<div id="msglist"></div>
<input type="text" id="msg" placeholder="请输入消息内容">
<button id="send">发送消息</button>
//mvp方式 dom操作
//获取消息显示
let msglist = document.querySelector('#msglist');
//获取输入框
let msg = document.querySelector('#msg');
//获取点击按钮
let send = document.querySelector('#send');
//点击事件
send.onclick = ()=>{
//获取消息框中的内容
let data = msg.value;
//清空输入框
msg.value = '';
console.log(data);
}
向服务端发送消息
//发送消息到服务器
ws.send(data);
服务端响应数据
把服务器返回的内容显示到页面上去
//生成dom
let p = document.createElement('p');
//赋值
p.innerText = data;
//追加到msglist
msglist.appendChild(p);
全部ws.html
代码
<!doctype html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="msglist"></div>
<input type="text" id="msg" placeholder="请输入消息内容">
<button id="send">发送消息</button>
<script>
//mvp方式 dom操作
//获取消息显示
let msglist = document.querySelector('#msglist');
//获取输入框
let msg = document.querySelector('#msg');
//获取点击按钮
let send = document.querySelector('#send');
//点击事件
send.onclick = ()=>{
//获取消息框中的内容
let data = msg.value;
//清空输入框
msg.value = '';
// console.log(data);
//发送消息到服务器
ws.send(data);
}
//客户端连接websoket服务
const ws = new WebSocket('ws://42.194.192.22:9502');
//事件监听
//建立链接时的监听
ws.onopen = ()=>{
console.log("链接建立");
}
//接受消息事件
// ws.onmessage = evt =>{
// console.log(evt)
// }
//解构赋值
ws.onmessage = ({data}) =>{
//生成dom
let p = document.createElement('p');
//赋值
p.innerText = data;
//追加到msglist
msglist.appendChild(p);
}
//关闭事件
ws.onclose = ()=>{
console.log("服务器断开了");
}
</script>
</body>
</html>
3.5mvvm方式实现一个聊天室
新建一个caht/chat.php
来创建websoket
服务
然后为了美化,我们把准备好的静态聊天室的静态html文件拖进去
访问其中的web.html
引入vue
给发送按钮绑定事件,并发送消息给服务器
修改caht/chat.php
让其分辨是自己还是它人,并返回json格式数据
//客户端消息
$data = $frame->data;
$ret['data'] = $data;
//$ws->connections 所有已经连接过的客户端
//广播群发
foreach ($ws->connections as $client){
//判断是否是客户端自己本人
if($frame->fd == $client){
$ret['style'] = 'bubble me';
}else{
$ret['style'] = 'bubble you';
}
//传递json给客户端
@$ws->push($client, json_encode($ret,JSON_UNESCAPED_UNICODE));
在web.html
中接受服务器返回的数据赋值给vue中的msglist
消息列表数组
//接受消息事件
ws.onmessage = ({data}) =>{
//返回的是json字符串
let json = JSON.parse(data);
//使用vue提供的一个变异方法
app.msglist.push(json);
}
data:{
msg: "",
//消息列表
msglist:[]
},
遍历数据到页面上template
标签
<template v-for="item in msglist">
<div :class="item.style">
{{item.data}}
</div>
</template>
重新开启websoke
服务
开启两个标签用于测试
vue实现一下通过enter回车发送消息
web.html
完整代码
<!DOCTYPE html>
<html lang="zh" >
<head>
<meta charset="UTF-8">
<title>在线聊天室</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="css/reset.min.css">
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div class="wrapper">
<div class="container">
<div class="left">
<div class="top"> 在线人员 </div>
<ul class="people">
<li class="person" data-chat="person1">
<img src="img/thomas.jpg" alt="" />
<span class="name">张三</span>
<span class="time">10:09</span>
</li>
<li class="person" data-chat="person2">
<img src="img/dog.png" alt="" />
<span class="name">李四</span>
<span class="time">10:44</span>
</li>
<li class="person" data-chat="person3">
<img src="img/louis-ck.jpeg" alt="" />
<span class="name">王五</span>
<span class="time">10:50</span>
</li>
</ul>
</div>
<div class="right">
<div class="top"><span><span class="name">聊天室</span></span></div>
<div class="chat" data-chat="person2">
<template v-for="item in msglist">
<div :class="item.style">
{{item.data}}
</div>
</template>
</div>
<div class="write">
<input type="text" v-model="msg" placeholder="输入内容" @keydown.enter="send" />
<!-- vue修饰符-->
<a @click.prevent="send" class="write-link send"></a>
</div>
</div>
</div>
</div>
<script src="js/index.js"></script>
<script src="js/vue.js"></script>
<script>
//客户端连接websoket服务
const ws = new WebSocket('ws://xx.xxx.xxx.xx:9502');
//建立链接时的监听
ws.onopen = ()=>{
console.log("链接建立");
}
//接受消息事件
ws.onmessage = ({data}) =>{
//返回的是json字符串
let json = JSON.parse(data);
//使用vue提供的一个变异方法
app.msglist.push(json);
}
//实例化vue
const app = new Vue({
el:'.wrapper',
data:{
msg: "",
//消息列表
msglist:[]
},
methods:{
send(){
// evt.preventDefault();
ws.send(this.msg);
//发送完毕后把输入框数据清空
this.msg = '';
}
}
});
</script>
</body>
</html>
chat/chat.php
完整代码
<?php
//创建WebSocket Server对象,监听0.0.0.0:9502端口
$ws = new Swoole\WebSocket\Server('0.0.0.0', 9502);
//监听WebSocket连接打开事件
$ws->on(
'open',
function ($ws, $request) {
// $ws->push($request->fd, "欢迎连接我们的聊天室\n");
}
);
//监听WebSocket消息事件(必不可少)
$ws->on(
'message',
function ($ws, $frame) {
//客户端消息
$data = $frame->data;
$ret['data'] = $data;
//$ws->connections 所有已经连接过的客户端
//广播群发
foreach ($ws->connections as $client){
//判断是否是客户端自己本人
if($frame->fd == $client){
$ret['style'] = 'bubble me';
}else{
$ret['style'] = 'bubble you';
}
//传递json给客户端
@$ws->push($client, json_encode($ret,JSON_UNESCAPED_UNICODE));
}
}
);
//监听WebSocket连接关闭事件
$ws->on(
'close',
function ($ws, $fd) {
echo "client-{$fd} is closed\n";
}
);
$ws->start();