前言:瞭解概念以後就應該練練手啦,否則就是巨嬰php
有收穫的話請加顆小星星,沒有收穫的話能夠 反對 沒有幫助 舉報三連html
# 命令行1
php src/websocket/run.php
# 命令行2
cd public && php -S localhost:8000
# 客戶端,多開幾個查看效果
訪問http://localhost:8000/
複製代碼
官方示例git
$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();
複製代碼
詳解:github
swoole_websocket_server 繼承自 swoole_http_serverweb
function onOpen(swoole_websocket_server $svr
, swoole_http_request $req
);redis
function onMessage(swoole_websocket_server $server
, swoole_websocket_frame $frame
)json
swoole_websocket_frame 屬性bootstrap
$frame->fd
,客戶端的socket id,使用$server->push
推送數據時須要用到$frame->data
,數據內容,能夠是文本內容也能夠是二進制數據,能夠經過opcode的值來判斷$frame->opcode
,WebSocket的OpCode類型,能夠參考WebSocket協議標準文檔$frame->finish
, 表示數據幀是否完整,一個WebSocket請求可能會分紅多個數據幀進行發送(底層已經實現了自動合併數據幀,如今不用擔憂接收到的數據幀不完整)目錄結構:bash
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();
// 初始化,主要是服務端本身關閉不會清空redis
foreach ($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}:*");
// 全部的key
foreach ($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上找完整的項目進行深刻學習