php swoole实现websocket功能

1.确保安装了swoole扩展。

2.撰写服务程序

<?php
//创建websocket服务器对象,监听0.0.0.0:9502端口
$ws = new swoole_websocket_server("0.0.0.0", 9502); //监听WebSocket连接打开事件
$ws->on('open', function ($ws, $request) {
echo "connection open: {$request->fd}\n";
$ws->push($request->fd, "hello, welcome\n");
}); //监听WebSocket消息事件
$ws->on('message', function ($ws, $frame) {
echo "Message:".$frame->data."\n";
foreach($ws->connections as $key => $fd) {
$ws->push($fd, "{$frame->data}");
}
}); //监听WebSocket连接关闭事件
$ws->on('close', function ($ws, $fd) {
echo "client-{$fd} is closed\n";
}); $ws->start();

3.开启服务

[root@localhost swooleTest]# php ws_serv.php

4.查看服务是否开启

[root@localhost swooleTest]# netstat -anp | grep :9502
tcp 0 0 0.0.0.0:9502 0.0.0.0:* LISTEN 3502/php

查看进程情况

[root@localhost swooleTest]# ps -ef | grep 3502
root 3502 2903 0 21:09 pts/1 00:00:00 php ws_serv.php
root 3503 3502 0 21:09 pts/1 00:00:00 php ws_serv.php

这个时候需要客户端连接测试了。

客户端可以是PHP,也可以是JS中的客户端。

下面通过JS连接websocket:

 <script>
var ws;
$(function(){
link();
send();
}) function link () {
ws = new WebSocket("ws://192.168.70.66:9502");//连接服务器
ws.onopen = function(event){
console.log(event);
alert('连接了');
};
ws.onmessage = function (event) {
alert(event.data);
}
ws.onclose = function(event){alert("已经与服务器断开连接\r\n当前连接状态:"+this.readyState);}; ws.onerror = function(event){alert("WebSocket异常!");};
} function send() {
ws.send("我是jq,我连接了。");
}
</script>

当执行客户端连接和发送消息的时候,服务端能够监听到。

[root@localhost swooleTest]# php ws_serv.php
connection open: 1
Message:我是jq,我连接了。

当其他客户端,发送消息的时候,服务端都能监听到。然后向其他在线的客户端发送消息。

下面是php客户端连接的情况:

<?php
$cli = new swoole_http_client('127.0.0.1', 9502);
$cli->setHeaders(['Trace-Id' => md5(time()),]);
$cli->on('message', function ($_cli, $frame) {
var_dump($frame);
});
$cli->upgrade('/', function ($cli) {
echo $cli->body;
$cli->push("hello world");
});
[root@localhost swooleTest]# php async_client.php
object(Swoole\WebSocket\Frame)#3 (3) {
["finish"]=>
bool(true)
["opcode"]=>
int(1)
["data"]=>
string(11) "hello world"
}

服务端也监听到了。

[root@localhost swooleTest]# php ws_serv.php
connection open: 1
Message:我是jq,我连接了。
connection open: 2
Message:hello world

客户端同样也能监听到其他客户端的情况。

[root@localhost swooleTest]# php async_client.php
object(Swoole\WebSocket\Frame)#3 (3) {
["finish"]=>
bool(true)
["opcode"]=>
int(1)
["data"]=>
string(11) "hello world"
}
object(Swoole\WebSocket\Frame)#3 (3) {
["finish"]=>
bool(true)
["opcode"]=>
int(1)
["data"]=>
string(26) "我是jq,我连接了。"
}

同步WebSocketClient.php,sync_client.php

<?php

class WebSocketClient
{
const VERSION = '0.1.4'; const TOKEN_LENGHT = 16;
const TYPE_ID_WELCOME = 0;
const TYPE_ID_PREFIX = 1;
const TYPE_ID_CALL = 2;
const TYPE_ID_CALLRESULT = 3;
const TYPE_ID_ERROR = 4;
const TYPE_ID_SUBSCRIBE = 5;
const TYPE_ID_UNSUBSCRIBE = 6;
const TYPE_ID_PUBLISH = 7;
const TYPE_ID_EVENT = 8; const OPCODE_CONTINUATION_FRAME = 0x0;
const OPCODE_TEXT_FRAME = 0x1;
const OPCODE_BINARY_FRAME = 0x2;
const OPCODE_CONNECTION_CLOSE = 0x8;
const OPCODE_PING = 0x9;
const OPCODE_PONG = 0xa; const CLOSE_NORMAL = 1000;
const CLOSE_GOING_AWAY = 1001;
const CLOSE_PROTOCOL_ERROR = 1002;
const CLOSE_DATA_ERROR = 1003;
const CLOSE_STATUS_ERROR = 1005;
const CLOSE_ABNORMAL = 1006;
const CLOSE_MESSAGE_ERROR = 1007;
const CLOSE_POLICY_ERROR = 1008;
const CLOSE_MESSAGE_TOO_BIG = 1009;
const CLOSE_EXTENSION_MISSING = 1010;
const CLOSE_SERVER_ERROR = 1011;
const CLOSE_TLS = 1015; private $key;
private $host;
private $port;
private $path;
/**
* @var swoole_client
*/
private $socket;
private $buffer = '';
private $origin = null;
/**
* @var bool
*/
private $connected = false; public $returnData = false; /**
* @param string $host
* @param int $port
* @param string $path
*/
function __construct($host = '127.0.0.1', $port = 8080, $path = '/', $origin = null)
{
$this->host = $host;
$this->port = $port;
$this->path = $path;
$this->origin = $origin;
$this->key = $this->generateToken(self::TOKEN_LENGHT);
} /**
* Disconnect on destruct
*/
function __destruct()
{
$this->disconnect();
} /**
* Connect client to server
*
* @return $this
*/
public function connect()
{
$this->socket = new \swoole_client(SWOOLE_SOCK_TCP);
if (!$this->socket->connect($this->host, $this->port))
{
return false;
}
$this->socket->send($this->createHeader());
return $this->recv();
} public function getSocket()
{
return $this->socket;
} /**
* Disconnect from server
*/
public function disconnect()
{
$this->connected = false;
$this->socket->close();
} public function close($code = self::CLOSE_NORMAL, $reason = '')
{
$data = pack('n', $code) . $reason;
return $this->socket->send(swoole_websocket_server::pack($data, self::OPCODE_CONNECTION_CLOSE, true));
} public function recv()
{
$data = $this->socket->recv();
if ($data === false)
{
echo "Error: {$this->socket->errMsg}";
return false;
}
$this->buffer .= $data;
$recv_data = $this->parseData($this->buffer);
if ($recv_data)
{
$this->buffer = '';
return $recv_data;
}
else
{
return false;
}
} /**
* @param string $data
* @param string $type
* @param bool $masked
* @return bool
*/
public function send($data, $type = 'text', $masked = false)
{
switch($type)
{
case 'text':
$_type = WEBSOCKET_OPCODE_TEXT;
break;
case 'binary':
case 'bin':
$_type = WEBSOCKET_OPCODE_BINARY;
break;
case 'ping':
$_type = WEBSOCKET_OPCODE_PING;
break;
default:
return false;
}
return $this->socket->send(swoole_websocket_server::pack($data, $_type, true, $masked));
} /**
* Parse received data
*
* @param $response
*/
private function parseData($response)
{
if (!$this->connected)
{
$response = $this->parseIncomingRaw($response);
if (isset($response['Sec-Websocket-Accept'])
&& base64_encode(pack('H*', sha1($this->key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'))) === $response['Sec-Websocket-Accept']
)
{
$this->connected = true;
return true;
}
else
{
throw new \Exception("error response key.");
}
} $frame = swoole_websocket_server::unpack($response);
if ($frame)
{
return $this->returnData ? $frame->data : $frame;
}
else
{
throw new \Exception("swoole_websocket_server::unpack failed.");
}
} /**
* Create header for websocket client
*
* @return string
*/
private function createHeader()
{
$host = $this->host;
if ($host === '127.0.0.1' || $host === '0.0.0.0')
{
$host = 'localhost';
}
return "GET {$this->path} HTTP/1.1" . "\r\n" .
"Origin: {$this->origin}" . "\r\n" .
"Host: {$host}:{$this->port}" . "\r\n" .
"Sec-WebSocket-Key: {$this->key}" . "\r\n" .
"User-Agent: PHPWebSocketClient/" . self::VERSION . "\r\n" .
"Upgrade: websocket" . "\r\n" .
"Connection: Upgrade" . "\r\n" .
"Sec-WebSocket-Protocol: wamp" . "\r\n" .
"Sec-WebSocket-Version: 13" . "\r\n" . "\r\n";
} /**
* Parse raw incoming data
*
* @param $header
*
* @return array
*/
private function parseIncomingRaw($header)
{
$retval = array();
$content = "";
$fields = explode("\r\n", preg_replace('/\x0D\x0A[\x09\x20]+/', ' ', $header));
foreach ($fields as $field)
{
if (preg_match('/([^:]+): (.+)/m', $field, $match))
{
$match[1] = preg_replace_callback('/(?<=^|[\x09\x20\x2D])./',
function ($matches)
{
return strtoupper($matches[0]);
},
strtolower(trim($match[1])));
if (isset($retval[$match[1]]))
{
$retval[$match[1]] = array($retval[$match[1]], $match[2]);
}
else
{
$retval[$match[1]] = trim($match[2]);
}
}
else
{
if (preg_match('!HTTP/1\.\d (\d)* .!', $field))
{
$retval["status"] = $field;
}
else
{
$content .= $field . "\r\n";
}
}
}
$retval['content'] = $content;
return $retval;
} /**
* Generate token
*
* @param int $length
*
* @return string
*/
private function generateToken($length)
{
$characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"§$%&/()=[]{}';
$useChars = array();
// select some random chars:
for ($i = 0; $i < $length; $i++)
{
$useChars[] = $characters[mt_rand(0, strlen($characters) - 1)];
}
// Add numbers
array_push($useChars, rand(0, 9), rand(0, 9), rand(0, 9));
shuffle($useChars);
$randomString = trim(implode('', $useChars));
$randomString = substr($randomString, 0, self::TOKEN_LENGHT);
return base64_encode($randomString);
} /**
* Generate token
*
* @param int $length
*
* @return string
*/
public function generateAlphaNumToken($length)
{
$characters = str_split('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789');
srand((float)microtime() * 1000000);
$token = '';
do
{
shuffle($characters);
$token .= $characters[mt_rand(0, (count($characters) - 1))];
} while (strlen($token) < $length);
return $token;
}
}
<?php
$opt = getopt("c:n:k:");
print_r($opt);
if (empty($opt['n']))
{
echo "examples: php sync_client.php -n 10000" . PHP_EOL;
return;
}
$count = $opt['n'];
require __DIR__ . "/WebSocketClient.php";
$host = '127.0.0.1';
$prot = 9502;
$client = new WebSocketClient($host, $prot);
$data = $client->connect();
//echo $data;
$data = "data";
for ($i = 0; $i < $count; $i++)
{
$client->send("hello swoole, number:" . $i . " data:" . $data);
echo "send over!" . PHP_EOL;
}
echo PHP_EOL . "======" . PHP_EOL;
sleep(1);
echo 'finish' . PHP_EOL;

具体的案例可以参考:[案例]

PHP - Swoole websocket理解的更多相关文章

  1. php开发面试题---php 对swoole的理解

    php开发面试题---php 对swoole的理解 一.总结 一句话总结: 以战养学,实例驱动 swoole是披着PHP外衣的C程序:其实就是c.java那些语言里面的高阶功能:比如 持久连接.异步通 ...

  2. Swoole WebSocket 的应用

    目录 概述 代码 小结 概述 这是关于 Swoole 学习的第三篇文章:Swoole WebSocket 的应用. 第二篇:Swoole Task 的应用 第一篇:Swoole Timer 的应用 什 ...

  3. swoole webSocket 聊天室示例

    swoole1.7.9增加了内置的WebSocket服务器支持,通过几行PHP代码就可以写出一个异步非阻塞多进程的WebSocket服务器. 基于swoole websocket的用户上下线通知,在线 ...

  4. swoole websocket服务推送

    用过workerman, 两个字"好用",对于swoole最近有时间也研究研究 swoole的websocket 很好实现 如官网 https://wiki.swoole.com/ ...

  5. swoole+websocket+redis实现一对一聊天

    如同web端的QQ和微信一样,这是一个web端的聊天程序. 环境:ubuntu + php + swoole扩展 + redis + mysql Redis 实现每个连接websocket的服务都唯一 ...

  6. PHP swoole websocket协议上机指南

    这一上机实验的开发环境用的是jetbrain公司的phpstorm 上级步骤如下: 创建websocket服务端 <?php $server = ); function onopen($serv ...

  7. php+swoole+websocket

    //创建websocket服务器对象,监听0.0.0.0:9502端口 $ws = new swoole_websocket_server("0.0.0.0", 9502); // ...

  8. swoole webSocket服务

    socket.php <?php //创建websocket服务器对象,监听0.0.0.0:9502端口 $ws = ); //监听WebSocket连接打开事件 (刚打开的时候会给客户端发送 ...

  9. swoole WebSocket 消息推送

    server.php <?php //连接本地的 Redis 服务 $redis = new Redis(); $redis->connect('127.0.0.1', 6379); $r ...

随机推荐

  1. python递归列出目录及其子目录下所有文件

    python递归列出目录及其子目录下所有文件 一.前言 函数的递归,简单来说,就是函数内部调用自己 先举个小例子,求阶乘 def factorial(n): if n == 0: return 1 e ...

  2. 报错 IllegalArgumentException occurred calling getter of cn.itcast.entity.Customer.cid

    我碰到这个问题的时候,没有数据类型不匹配的情况,也没有表达无法向action传递数据的情况,一直报这样的错误,意思就是无法使用Customer中的get方法来赋值.完整报错如下所示: HTTP Sta ...

  3. js 格式化时间日期函数小结2

    方法一: // 对Date的扩展,将 Date 转化为指定格式的String // 月(M).日(d).小时(h).分(m).秒(s).季度(q) 可以用 1-2 个占位符,  // 年(y)可以用  ...

  4. python函数语法学习

    Python函数 定义函数 在Python中,定义一个函数用def语句,一次写出函数名.括号中的参数和冒号:,函数返回使用return语句. def myDef(x): if x >= 0: r ...

  5. C++(十五) — sizeof 运算符

    1.基本数据类型 sizeof 是一个关键字,它是一个编译时运算符,用于判断变量或数据类型的字节大小. sizeof 运算符可用于获取类.结构.共用体和其他用户自定义数据类型的大小. 使用 sizeo ...

  6. ES6相关文章

    1.https://www.cnblogs.com/xiaotanke/p/7448383.html (export ,export default 和 import 区别 以及用法)

  7. Forms and Reports Developer 10g Certified on Windows 10 for EBS 12.x

    Forms Developer 10g and Reports Developer 10g are now certified on Windows 10 desktops for E-Busines ...

  8. 【MAF】MAF插件框架简介

    引言    Managed Add-In Framework是一个插件式框架.它有两大作用,一个是解耦,插件和宿主由7个管道组成,两边都有适配器Adapter管道,能最大程度地降低插件和宿主的耦合度: ...

  9. hdu1845

    题解: 只要输出n/2即可 代码: #include<cstdio> #include<cmath> #include<cstring> #include<a ...

  10. button出现投影

    style="?android:attr/borderlessButtonStyle" xml中加上这属性