前言:了解了概念之后就应该练练手啦,不然就是语言的巨人,行动的矮子啦

  • 代码仓库

  • 实战 swoole【聊天室】

  • 在线体验

准备工作

  • 需要先看初识 swoole【上】,了解基本的服务端 WebSocket 使用

  • js WebSocket 客户端简单使用

使用

# 命令行1
php src/websocket/run.php
# 命令行2
cd public && php -S localhost:8000
# 客户端,多开几个查看效果
访问http://localhost:8000/

WebSocket

官方示例

$server = new swoole_websocket_server("0.0.0.0", 9501);
$server->on('open', function (swoole_websocket_server $server, $request) {echo "server: handshake success with fd{$request->fd}\n";});
$server->on('message', function (swoole_websocket_server $server, $frame) {echo "receive from {$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}\n";$server->push($frame->fd, "this is server");});
$server->on('close', function ($ser, $fd) {echo "client {$fd} closed\n";});
$server->on('request', function (swoole_http_request $request, swoole_http_response $response) {global $server;//调用外部的server// $server->connections 遍历所有websocket连接用户的fd,给所有用户推送foreach ($server->connections as $fd) {$server->push($fd, $request->get['message']);}});
$server->start();

详解:

  • swoole_websocket_server 继承自 swoole_http_server

    • 设置了 onRequest 回调,websocket 服务器也可以同时作为 http 服务器

    • 未设置 onRequest 回调,websocket 服务器收到 http 请求后会返回 http 400 错误页面

    • 如果想通过接收 http 触发所有 websocket 的推送,需要注意作用域的问题,面向过程请使用 global 对 swoole_websocket_server 进行引用,面向对象可以把 swoole_websocket_server 设置成一个成员属性

  • function onOpen(swoole_websocket_server $svr, swoole_http_request $req);

    • 当 WebSocket 客户端与服务器建立连接并完成握手后会回调此函数。

    • $req 是一个 Http 请求对象,包含了客户端发来的握手请求信息

    • onOpen 事件函数中可以调用 push 向客户端发送数据或者调用 close 关闭连接

    • onOpen 事件回调是可选的

  • function onMessage(swoole_websocket_server $server, swoole_websocket_frame $frame)

    • 当服务器收到来自客户端的数据帧时会回调此函数。

    • $frame 是 swoole_websocket_frame 对象,包含了客户端发来的数据帧信息

    • onMessage 回调必须被设置,未设置服务器将无法启动

    • 客户端发送的 ping 帧不会触发 onMessage,底层会自动回复 pong 包

  • swoole_websocket_frame 属性

    • $frame->fd,客户端的 socket id,使用 $server->push 推送数据时需要用到

    • $frame->data,数据内容,可以是文本内容也可以是二进制数据,可以通过 opcode 的值来判断

    • $frame->opcode,WebSocket 的 OpCode 类型,可以参考 WebSocket 协议标准文档

    • $frame->finish, 表示数据帧是否完整,一个 WebSocket 请求可能会分成多个数据帧进行发送(底层已经实现了自动合并数据帧,现在不用担心接收到的数据帧不完整)

聊天室服务端示例

目录结构:

  • config

    • socket.php
  • src

    • websocket

      • Config.php

      • run.php

      • WebSocketServer.php 内存表版本

      • WsRedisServer.php redis 版本

WebSocketServer.php 内存表版本

<?php
namespace App\WebSocket;class WebSocketServer
{private $config;private $table;private $server;public function __construct(){// 内存表 实现进程间共享数据,也可以使用redis替代$this->createTable();// 实例化配置$this->config = Config::getInstance();}public function run(){$this->server = new \swoole_websocket_server($this->config['socket']['host'],$this->config['socket']['port']);$this->server->on('open', [$this, 'open']);$this->server->on('message', [$this, 'message']);$this->server->on('close', [$this, 'close']);$this->server->start();}public function open(\swoole_websocket_server $server, \swoole_http_request $request){$user = ['fd' => $request->fd,'name' => $this->config['socket']['name'][array_rand($this->config['socket']['name'])] . $request->fd,'avatar' => $this->config['socket']['avatar'][array_rand($this->config['socket']['avatar'])]];// 放入内存表$this->table->set($request->fd, $user);$server->push($request->fd, json_encode(array_merge(['user' => $user], ['all' => $this->allUser()], ['type' => 'openSuccess'])));}private function allUser(){$users = [];foreach ($this->table as $row) {$users[] = $row;}return $users;}public function message(\swoole_websocket_server $server, \swoole_websocket_frame $frame){$this->pushMessage($server, $frame->data, 'message', $frame->fd);}/*** 推送消息** @param \swoole_websocket_server $server* @param string $message* @param string $type* @param int $fd*/private function pushMessage(\swoole_websocket_server $server, string $message, string $type, int $fd){$message = htmlspecialchars($message);$datetime = date('Y-m-d H:i:s', time());$user = $this->table->get($fd);foreach ($this->table as $item) {// 自己不用发送if ($item['fd'] == $fd) {continue;}$server->push($item['fd'], json_encode(['type' => $type,'message' => $message,'datetime' => $datetime,'user' => $user]));}}/*** 客户端关闭的时候** @param \swoole_websocket_server $server* @param int $fd*/public function close(\swoole_websocket_server $server, int $fd){$user = $this->table->get($fd);$this->pushMessage($server, "{$user['name']}离开聊天室", 'close', $fd);$this->table->del($fd);}/*** 创建内存表*/private function createTable(){$this->table = new \swoole_table(1024);$this->table->column('fd', \swoole_table::TYPE_INT);$this->table->column('name', \swoole_table::TYPE_STRING, 255);$this->table->column('avatar', \swoole_table::TYPE_STRING, 255);$this->table->create();}
}

WsRedisServer.php redis 版本

<?php
namespace App\WebSocket;use Predis\Client;/*** 使用redis代替table,并存储历史聊天记录** Class WsRedisServer* @package App\WebSocket*/
class WsRedisServer
{private $config;private $server;private $client;private $key = "socket:user";public function __construct(){// 实例化配置$this->config = Config::getInstance();// redis$this->initRedis();// 初始化,主要是服务端自己关闭不会清空redisforeach ($this->allUser() as $item) {$this->client->hdel("{$this->key}:{$item['fd']}", ['fd', 'name', 'avatar']);}}public function run(){$this->server = new \swoole_websocket_server($this->config['socket']['host'],$this->config['socket']['port']);$this->server->on('open', [$this, 'open']);$this->server->on('message', [$this, 'message']);$this->server->on('close', [$this, 'close']);$this->server->start();}public function open(\swoole_websocket_server $server, \swoole_http_request $request){$user = ['fd' => $request->fd,'name' => $this->config['socket']['name'][array_rand($this->config['socket']['name'])] . $request->fd,'avatar' => $this->config['socket']['avatar'][array_rand($this->config['socket']['avatar'])]];// 放入redis$this->client->hmset("{$this->key}:{$user['fd']}", $user);// 给每个人推送,包括自己foreach ($this->allUser() as $item) {$server->push($item['fd'], json_encode(['user' => $user,'all' => $this->allUser(),'type' => 'openSuccess']));}}private function allUser(){$users = [];$keys = $this->client->keys("{$this->key}:*");// 所有的keyforeach ($keys as $k => $item) {$users[$k]['fd'] = $this->client->hget($item, 'fd');$users[$k]['name'] = $this->client->hget($item, 'name');$users[$k]['avatar'] = $this->client->hget($item, 'avatar');}return $users;}public function message(\swoole_websocket_server $server, \swoole_websocket_frame $frame){$this->pushMessage($server, $frame->data, 'message', $frame->fd);}/*** 推送消息** @param \swoole_websocket_server $server* @param string $message* @param string $type* @param int $fd*/private function pushMessage(\swoole_websocket_server $server, string $message, string $type, int $fd){$message = htmlspecialchars($message);$datetime = date('Y-m-d H:i:s', time());$user['fd'] = $this->client->hget("{$this->key}:{$fd}", 'fd');$user['name'] = $this->client->hget("{$this->key}:{$fd}", 'name');$user['avatar'] = $this->client->hget("{$this->key}:{$fd}", 'avatar');foreach ($this->allUser() as $item) {// 自己不用发送if ($item['fd'] == $fd) {continue;}$is_push = $server->push($item['fd'], json_encode(['type' => $type,'message' => $message,'datetime' => $datetime,'user' => $user]));// 删除失败的推送if (!$is_push) {$this->client->hdel("{$this->key}:{$item['fd']}", ['fd', 'name', 'avatar']);}}}/*** 客户端关闭的时候** @param \swoole_websocket_server $server* @param int $fd*/public function close(\swoole_websocket_server $server, int $fd){$user['fd'] = $this->client->hget("{$this->key}:{$fd}", 'fd');$user['name'] = $this->client->hget("{$this->key}:{$fd}", 'name');$user['avatar'] = $this->client->hget("{$this->key}:{$fd}", 'avatar');$this->pushMessage($server, "{$user['name']}离开聊天室", 'close', $fd);$this->client->hdel("{$this->key}:{$fd}", ['fd', 'name', 'avatar']);}/*** 初始化redis*/private function initRedis(){$this->client = new Client(['scheme' => $this->config['socket']['redis']['scheme'],'host' => $this->config['socket']['redis']['host'],'port' => $this->config['socket']['redis']['port'],]);}
}

config.php

<?php
namespace App\WebSocket;class Config implements \ArrayAccess
{private $path;private $config;private static $instance;public function __construct(){$this->path = __DIR__ . '/../../config/';}// 单例模式public static function getInstance(){if (!self::$instance) {self::$instance = new self();}return self::$instance;}public function offsetSet($offset, $value){// 阉割}public function offsetGet($offset){if (empty($this->config)) {$this->config[$offset] = require $this->path . $offset . ".php";}return $this->config[$offset];}public function offsetExists($offset){return isset($this->config[$offset]);}public function offsetUnset($offset){// 阉割}// 禁止克隆final private function __clone(){}
}

config/socket.php

<?php
return ['host' => '0.0.0.0','port' => 9501,'redis' => ['scheme' => 'tcp','host' => '0.0.0.0','port' => 6380],'avatar' => ['./images/avatar/1.jpg','./images/avatar/2.jpg','./images/avatar/3.jpg','./images/avatar/4.jpg','./images/avatar/5.jpg','./images/avatar/6.jpg'],'name' => ['科比','库里','KD','KG','乔丹','邓肯','格林','汤普森','伊戈达拉','麦迪','艾弗森','卡哇伊','保罗']
];

run.php

<?php
require __DIR__ . '/../bootstrap.php';$server = new App\WebSocket\WebSocketServer();$server->run();

总结

完整示例:聊天室

学完后发现生活中所谓的聊天室其实也不过如此,当然这只是简单的 demo,很多功能都没有实现,想进一步学习的话可以去 github 上找完整的项目进行深入学习

参考

  • swoole
  • PHP + Swoole 实现的简单聊天室

更多学习内容可以访问【对标大厂】精品PHP架构师教程目录大全,只要你能看完保证薪资上升一个台阶(持续更新)

以上内容希望帮助到大家,很多PHPer在进阶的时候总会遇到一些问题和瓶颈,业务代码写多了没有方向感,不知道该从那里入手去提升,对此我整理了一些资料,包括但不限于:分布式架构、高可扩展、高性能、高并发、服务器性能调优、TP6,laravel,YII2,Redis,Swoole、Swoft、Kafka、Mysql优化、shell脚本、Docker、微服务、Nginx等多个知识点高级进阶干货需要的可以免费分享给大家,需要的可以加入我的PHP技术交流群953224940

进阶PHP月薪30k>>>架构师成长路线【视频、面试文档免费获取】

swoole 项目实战——实现简单聊天室相关推荐

  1. SpringBoot +WebSocket实现简单聊天室功能实例

    SpringBoot +WebSocket实现简单聊天室功能实例) 一.代码来源 二.依赖下载 三.数据库准备(sql) 数据库建表并插入sql 四.resources文件配置 application ...

  2. java web利用mvc结构实现简单聊天室功能

    简单聊天室采用各种内部对象不适用数据库实现. 一个聊天室要实现的基本功能是:         1.用户登录进入聊天室, 2.用户发言 3.用户可以看见别人发言 刚才算是简单的需求分析了,现在就应该是进 ...

  3. node.js入门 - 2.创建一个简单聊天室

    这篇文章将通过开发一个简单聊天室的方式,介绍node.js的net模块. 一.第一版,只向客户端发送信息   我们先实现一个简单的版本,代码如下: var net=require('net'); va ...

  4. jsp项目开发案例_Laravel 中使用 swoole 项目实战开发案例一 (建立 swoole 和前端通信)life...

    1 开发需要环境 工欲善其事,必先利其器.在正式开发之前我们检查好需要安装的拓展,不要开发中发现这些问题,打断思路影响我们的开发效率. 安装 swoole 拓展包 安装 redis 拓展包 安装 la ...

  5. jsp项目开发案例_Laravel中使用swoole项目实战开发案例一 (建立swoole和前端通信)

    Laravel中使用swoole项目实战开发案例二(后端主动分场景给界面推送消息) 工欲善其事,必先利其器.在正式开发之前我们检查好需要安装的拓展,不要开发中发现这些问题,打断思路影响我们的开发效率. ...

  6. 利用socket.io+nodejs打造简单聊天室

    代码地址如下: http://www.demodashi.com/demo/11579.html 界面展示: 首先展示demo的结果界面,只是简单消息的发送和接收,包括发送文字和发送图片. ws说明: ...

  7. Asp.Net SignalR - 简单聊天室实现

    简单聊天室 使用持久链接类我们就可以做一些即时通讯的应用了,我使用Group做了一个简单的聊天室,先上图技术细节下面再讲 可以加入聊天室.创建聊天室.发送消息,下面就说说我是如何通过Group做出来的 ...

  8. java jsp聊天系统_java web实现简单聊天室

    目标 servlet.jsp实现简单聊天室,用户通过浏览器登录后进入聊天室,可发送消息进行群聊,点击聊天信息框中的用户名可实现拍一拍功能. 基础知识 数据的存取 setAttribute / getA ...

  9. c聊天室系统asp ajax,利用AJAX和ASP.NET实现简单聊天室

    利用AJAX和ASP.NET实现简单聊天室 我的第一个简单的Chatroom是用ASP3.0写成的.那无外乎有二个TextBox,他们发送消息 给程序变量然后显示在一个每秒刷新的页面上.在那个时代,一 ...

最新文章

  1. 面试官问我,使用Dubbo有没有遇到一些坑?我笑了。
  2. 记录一下CUDNN配置
  3. JavaScript---设计模式之观察者模式
  4. 数据结构——顺序栈和链式栈的简单实现和解析(C语言版)
  5. linux默认的https端口,如何在Ubuntu 18.04 Bionic Beaver Linux上拒绝除HTTP端口80和HTTPS端口443之外的所有传入端口...
  6. jop怎么读音英语怎么说_“跨年”英语怎么说?
  7. Java笔记-AES加解密(PKCS7padding可用)
  8. 阅面携手英特尔发布“繁星”系列产品,推动机器视觉迈入AI芯片新纪元
  9. iOS开发UI篇—推荐两个好用的Xcode插件(提供下载链接)
  10. SpringBoot时间格式化
  11. 二项分布的期望方差证明
  12. TypeScript完全解读(26课时)_9.TypeScript完全解读-TS中的类
  13. AMD显卡在WIN10环境下刷新BIOS
  14. 自动驾驶各大传感器介绍-硬件篇
  15. 利用 Python 实现简单的基于用户的商品推荐模型
  16. [iuud8]ios不同版本下推送本地通知
  17. BUAA 1489
  18. 快速删除大文件利器 node 包 rimraf
  19. 面向民航的航空数据链协议解析应用研究
  20. 数学建模用python分析gdp_数学建模·中国GDP趋势分析与预测

热门文章

  1. 记录一下springboot的返回时间参数差八小时 而且默认为时间戳
  2. 带你走进准确率高于 99.7% 的智能鉴黄功能
  3. JavaScript正则表达式:正则表达式中的替换
  4. 帕雷托法则(常说的二八法则)
  5. Alcohol 120%刻录教程:软件介绍
  6. Cocos Creator源码和教程分享
  7. spark load时的jackson报错
  8. 社区少年宫计算机活动总结,常丰小学少年宫计算机小组活动总结.docx
  9. 架构师花费近一年时间整理出来的Java核心知识,直击优秀开源框架灵魂
  10. 毕业设计-基于微信小程序的智能查寝系统