服务端:
server.php
<?php
/**
* Date: 2020/8/28
* Time: 3:28 下午
*/
class chat
{
const HOST = '0.0.0.0';//ip地址 0.0.0.0代表接受所有ip的访问
const PART = 81;//端口号
private $server = null;//单例存放websocket_server对象
public function __construct()
{
//实例化swoole_websocket_server并存储在我们Chat类中的属性上,达到单例的设计
$this->server = new swoole_websocket_server(self::HOST, self::PART);
// 建立连接后进行握手。WebSocket 服务器会自动进行 handshake 握手的过程,如果用户希望自己进行握手处理,可以设置 onHandShake 事件回调函数。
// $this->server->on('handshake', [$this, 'onHandshake']);
//监听连接事件
$this->server->on('open', [$this, 'onOpen']);
//监听接收消息事件
$this->server->on('message', [$this, 'onMessage']);
//监听关闭事件
$this->server->on('close', [$this, 'onClose']);
//设置允许访问静态文件
$this->server->set([
'worker_num' => 2, //开启2个worker进程
'max_request' => 4, //每个worker进程 max_request设置为4次
'task_worker_num' => 4, //开启4个task进程
'dispatch_mode' => 4, //数据包分发策略 - IP分配
'daemonize' => false, //守护进程(true/false)
'document_root' => '/swoole/chat2',//这里传入客户端文件存放地址
'enable_static_handler' => true//允许访问静态文件
]);
//异步防止阻塞当前进程 必须先设置 task_worker_num 必须设置 Server 的 onTask 和 onFinish 事件回调函数。
$this->server->on("Task", [$this, 'onTask']);
//task 进程中完成时, task 进程会通过 finish() 方法将任务处理的结果发送给 worker 进程。
$this->server->on("Finish", [$this, 'onFinish']);
//开启服务
$this->server->start();
}
/**
*
* @param \Swoole\Http\Request $request
* @param \Swoole\Http\Response $response
*
* @return bool
*/
public function onHandshake($request, $response)
{
// websocket握手连接算法验证
$secWebSocketKey = $request->header['sec-websocket-key'];
$patten = '#^[+/0-9A-Za-z]{21}[AQgw]==$#';
if (0 === preg_match($patten, $secWebSocketKey) || 16 !== strlen(base64_decode($secWebSocketKey))) {
$response->end();
return false;
}
echo $request->header['sec-websocket-key'];
$key = base64_encode(
sha1(
$request->header['sec-websocket-key'] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',
true
)
);
$headers = [
'Upgrade' => 'websocket',
'Connection' => 'Upgrade',
'Sec-WebSocket-Accept' => $key,
'Sec-WebSocket-Version' => '13',
];
// WebSocket connection to 'ws://127.0.0.1:9502/'
// failed: Error during WebSocket handshake:
// Response must not include 'Sec-WebSocket-Protocol' header if not present in request: websocket
if (isset($request->header['sec-websocket-protocol'])) {
$headers['Sec-WebSocket-Protocol'] = $request->header['sec-websocket-protocol'];
}
foreach ($headers as $key => $val) {
$response->header($key, $val);
}
$response->status(101);
$response->end();
}
/**
* 连接成功回调函数---如果系统调用了handshake函数,就不会触发这个
*
* @param Swoole\WebSocket\Server $server
* @param Swoole\Http\Request $request
*
* $request 是一个 HTTP 请求对象,包含了客户端发来的握手请求信息
* onOpen 事件函数中可以调用 push 向客户端发送数据或者调用 close 关闭连接
* onOpen 事件回调是可选的
*/
public function onOpen($server,$request)
{
$this->server->task([
'type' => 'online',
'fd' => $request->fd
]);
// foreach ($server->connections as $fd) {
// if ($request->fd !== $fd) {
// $server->push($fd, json_encode(['no' => $request->fd, 'msg' => '上线了,快来聊天吧!']));
// }
// }
}
/**
* 接收到信息的回调函数
*
* @param Swoole\WebSocket\Server $server
* @param Swoole\WebSocket\Frame $frame
* 属性 说明
$frame->fd 客户端的 socket id,使用 $server->push 推送数据时需要用到
$frame->data 数据内容,可以是文本内容也可以是二进制数据,可以通过 opcode 的值来判断
$frame->opcode WebSocket 的 OpCode 类型,可以参考 WebSocket 协议标准文档
$frame->finish 表示数据帧是否完整,一个 WebSocket 请求可能会分成多个数据帧进行发送(底层已经实现了自动合并数据帧,现在不用担心接收到的数据帧不完整)
*/
public function onMessage($server, $frame)
{
echo $frame->fd . '来了,说:' . $frame->data . PHP_EOL;//打印到我们终端
if($frame->data==10){
$server->push($frame->fd, json_encode(['no' => $frame->fd, 'msg' => '不要发"10",谢谢!']));
$this->server->disconnect($frame->fd,403,'不要发"10",谢谢!');
}
$this->server->task([
'type' => 'message',
'msg' => $frame->data,
'fd' => $frame->fd
]);
// foreach ($server->connections as $fd) {
// $server->push($fd, json_encode(['no' => $frame->fd, 'msg' => $frame->data]));
// }
}
/**
* 异步task
*
* @param Swoole\WebSocket\Server $server
* @param $task_id //从 0-42 亿的整数,在当前进程内是唯一的
* @param $from_id
* @param $data
*/
public function onTask($server, $task_id, $from_id, $data)
{
echo "#{$server->worker_id} onTask: [PID={$server->worker_pid}]: task_id={$task_id}: from_id={$from_id}".PHP_EOL;
$msg = '';
switch ($data['type']) {
case 'online':
$msg = '我上线了,快来聊天吧!';
break;
case 'message':
$msg = $data['msg'];
break;
default:
$msg = '这是神马操作!';
break;
}
foreach ($server->connections as $fd) {
$connectionInfo = $server->connection_info($fd);
if ($connectionInfo['websocket_status'] == 3) {
$server->push($fd, json_encode(['no' => $data['fd'], 'msg' => $msg])); //长度最大不得超过2M
}
}
$server->finish($data);
}
/**
* 此回调函数在 worker 进程被调用,当 worker 进程投递的任务在 task 进程中完成时,
* task 进程会通过 Swoole\Server->finish() 方法将任务处理的结果发送给 worker 进程。
* @param Swoole\WebSocket\Server $server
* @param $task_id
* @param $data
*/
public function onFinish($server,$task_id, $data)
{
}
/**
* 断开连接回调函数
*
* @param Swoole\WebSocket\Server $server
* @param int $fdUser
*/
public function onClose($server, $fdUser)
{
// var_dump($this->server->exist(10));
foreach ($server->connections as $fd) {
if($fd!==$fdUser){
$server->push($fd, json_encode(['no' => $fdUser, 'msg' => '我走啦!']));
}
}
}
}
$obj = new Chat();
客户端
client.html
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>swoole-websocket聊天室</title>
<script src="http://libs.baidu.com/jquery/1.9.1/jquery.min.js"></script>
</head>
<body>
<textarea class="log" style="width: 100%; height: 500px;">
=======swoole-websocket聊天室======
</textarea>
<input type="button" value="连接" onClick="link()">
<input type="button" value="断开" onClick="dis()">
<input type="text" id="text">
<input type="button" value="发送" onClick="send()">
<script>
function link(){
var url='ws://127.0.0.1:81';
socket=new WebSocket(url);
socket.onopen=function(){log1('连接成功')}
socket.onmessage=function(msg){log(msg.data);console.log(msg);}
socket.onclose=function(){log1('断开连接')}
}
function dis(){
socket.close();
socket=null;
}
function log1(var1) {
$('.log').append(var1+'\r\n');
}
function log(var1){
var v=$.parseJSON(var1)
$('.log').append('用户'+v['no']+'说:'+v['msg']+'\r\n');
}
function send(){
var text=$('#text').val();
socket.send(text);
}
function send2(){
var json = JSON.stringify({'type':'php','msg':$('#text2').attr('value')})
socket.send(json);
}
</script>
</body>
</html>
启动
php server.php