前言
最近在做一個項目,需要用到類似通訊,第一想法就是socket,然後一頓百度發現了swool這個東西,之前有在項目中使用過workman,心想應該是差不多的吧,但是後面才發現兩者其實還是有挺大差異的
安裝
workman是一個類似packages的東西,我們之間使用composer來安裝就可以了
swoole不一樣,他是官方插件,所以我們需要用平時安裝php插件的方法來安裝
使用
官方那邊有很詳細的文檔,我在這裏就不一個一個說了,就說我遇到的一些坑把
*多個線程之間的變量共享,不能使用global,而要用官方的swoole_table
我的項目裏面,是需要在一個server文件裏面啓動兩個端口監聽的,一個用來和瀏覽器溝通的websocket,一個是和內部溝通的普通TCP,而這兩者之間是需要數據交流的,我之前在使用workman的時候是這樣使用的
use Workerman\Worker;
use PHPSocketIO\SocketIO;
// 用戶組(記錄所有在線的用戶)
$userList = array();
$socket = new SocketIO(9527);
$socket->on('connection', function ($socket) {
// 聲明userList是全局變量
global $userList;
// 客戶端連接的時候用一個標識標識這個連接
$userId = $socket->handshake['query']['userId'];
// 加到數組裏面去
$userList[] = $userId;
});
// 當$sender_io啓動後監聽一個http端口,通過這個端口可以給任意web客戶端發送信息
$socket->on('workerStart', function () {
global $socket,$userList;
// 監聽一個http端口
$inner_http_worker = new Worker(9523);
// 當http客戶端發來數據時觸發
$inner_http_worker->onMessage = function ($http_connection, $data) {
global $allSocket;
$_POST = $_POST ? $_POST : $_GET;
$to = @$_POST['to'];
$toId = $userlist['to']
$name = @$_POST['name'];
$_POST['data'] = htmlspecialchars(@$_POST['data']);
// 有指定uid則向uid所在socket組發送數據
if ($to) {
$allSocket->to($toId)->emit($name, $_POST['data']);
// 否則向所有uid推送數據
} else {
$allSocket->emit($name, $_POST['data']);
}
return $http_connection->send('ok');
};
// 執行監聽
$inner_http_worker->listen();
});
上面的代碼可以看到,我在兩個不同的端口server裏面是可以用global通訊的
但是swool就不可以了,下面是swool的代碼
// 因爲有兩個線程之間無法溝通所以需要用這個來溝通
$table = new swoole_table(1024);
$table->column('userList', swoole_table::TYPE_STRING, 512);
$table->create();
function addUser($fd, $id)
{
global $table;
$userList = getUserList();
$userList[$id] = $fd;
$table->set('1', ['userList' => json_encode($userList)]);
print_r($id . '上線了');
return $userList;
}
function delUser($fd, $id)
{
global $table;
}
function getUserList()
{
global $table;
if ($table->exist('1')) {
$userList = json_decode($table->get('1')['userList'], true);
}
return $userList;
}
function getUser($id)
{
global $table;
$userList = getUserList();
return $userList[$id];
}
// 用於和外部客戶端溝通
$socketServer = new swoole_websocket_server("0.0.0.0", $params['swoole']['stockPort']);
// 用於和內部溝通
$tcpServer = $socketServer->addlistener("127.0.0.1", $params['swoole']['tcpPort'], SWOOLE_SOCK_TCP);
$socketServer->on('open', function (swoole_websocket_server $server, $request) {
echo "server: handshake success with fd{$request->fd}\n";
});
$socketServer->on('handshake', function (swoole_http_request $request, swoole_http_response $response) {
global $socketServer;
if (!isset($request->header['sec-websocket-key'])) {
//'Bad protocol implementation: it is not RFC6455.'
$response->end();
return false;
}
if (0 === preg_match('#^[+/0-9A-Za-z]{21}[AQgw]==$#', $request->header['sec-websocket-key'])
|| 16 !== strlen(base64_decode($request->header['sec-websocket-key']))
) {
//Header Sec-WebSocket-Key is illegal;
$response->end();
return false;
}
$key = base64_encode(sha1($request->header['sec-websocket-key']
. '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',
true));
$headers = array(
'Upgrade' => 'websocket',
'Connection' => 'Upgrade',
'Sec-WebSocket-Accept' => $key,
'Sec-WebSocket-Version' => '13',
'KeepAlive' => 'off',
);
foreach ($headers as $key => $val) {
$response->header($key, $val);
}
$response->status(101);
$response->end();
$fd = $request->fd;
$id = base64_decode($request->get['id']);
if ($fid = getUser($id)) {
// 這個用戶之前登陸過,發送消息給那個用戶讓其退出登錄
$socketServer->push($fid, 'logout');
}
addUser($fd, $id);
$socketServer->defer(function () use ($fd, $socketServer) {
$socketServer->push($fd, "hello, welcome\n");
});
return true;
});
$socketServer->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");
});
$socketServer->on('close', function ($ser, $fd) {
echo "client {$fd} closed\n";
});
$tcpServer->set([]);
// 接收到內部發送的請求
$tcpServer->on("receive", function ($serv, $fd, $threadId, $data) {
global $socketServer, $table;
print_r($data);
$data = json_decode($data, true);
$userId = $data['userId'];
$method = $data['method'];
$params = $data['params'];
$json = [
'method' => $method,
'params' => $params
];
$sendData = json_encode($json);
$fd1 = getUser($userId);
$socketServer->push($fd1, $sendData);
$serv->close($fd);
});
// 內部請求完畢以後關閉
$tcpServer->on("close", function ($ser, $fd) {
print_r($fd . '被關閉');
print_r('11');
});
$socketServer->start();
可以看到,我必須使用swoole_table這個東西來協助我
socket和websocket
從上面的代碼看,workman我用的是socket,之前我在swool的時候,看到有websocket的代碼示例,然後我在客戶端那邊還是用我之前的socket.io來鏈接,發現怎麼都不行,一直斷,後面我才知道,這兩者是不一樣的東西,socket和websocket的鏈接和api都是不一樣的,websocket必須使用new WebSocket來鏈接,下面這篇文章寫得還是不錯的