当万有引力号启动广播按钮,向宇宙发送三体星的坐标时,地球已经失去了任何侵略价值。三体人将所有在地球的资源全部撤走,但在临别时,安排了程圣母与云天明的远程会话。接下来,我们用swoft来实现他们两人的聊天功能。
光年之外的对话PHP果真是宇宙最强的语言,星际通话也能办到...
对swoft不了解的同学请看 swoole框架-swoft初体验
启动ws服务
☁ swoft [master] ⚡ sudo php bin/swoft ws:start
Password:
Server Information
************************************************************************************
* WS | host: 0.0.0.0, port: 80, type: 1, worker: 1, mode: 3 (http is Enabled)
* TCP | host: 0.0.0.0, port: 8099, type: 1, worker: 1 (Enabled)
************************************************************************************
Server has been started. (master PID: 3954, manager PID: 3955)
You can use CTRL + C to stop run.
创建http服务的聊天控制器
swoft 提供了生成控制器文件的命令行
☁ swoft [master] ⚡ php bin/swoft gen:controller chat --prefix /chat -y
Class data:
{
"name": "chat",
"suffix": "Controller",
"namespace": "App\\Controllers",
"className": "ChatController",
"prefix": "/chat",
"idVar": "{id}"
}
swoft/app/Controllers/ChatController.php
<?php
namespace App\Controllers;
use Swoft\Http\Server\Bean\Annotation\Controller;
use Swoft\Http\Server\Bean\Annotation\RequestMapping;
use Swoft\Http\Server\Bean\Annotation\RequestMethod;
/**
* Class ChatController
* @Controller(prefix="/chat")
* @package App\Controllers
*/
class ChatController{
/**
* @RequestMapping(route="/chat/{uid}", method=RequestMethod::GET)
* @param int $uid
* @return \Swoft\Http\Message\Server\Response|\think\response\View
*/
public function index(int $uid)
{
$users = [
1 => '程心',
2 => '云天明',
];
$receiveUid = $uid == 1 ? 2 : 1;
$userName = $users[$uid];
$data = compact('uid', 'userName', 'receiveUid');
return view('chat/index', $data);
}
}
创建视图文件
视图文件用vue.js搭建,对于vue.js不熟悉的同学,参见 实例学习vue.js目录
swoft/resources/views/chat/index.php
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/4.1.0/css/bootstrap.min.css">
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<style>
.container {
margin-top: 2%;
}
</style>
<body>
<div class="container">
<div id="app">
<h3 class="offset-md-3">{{ userName }}</h3>
<div class="alert alert-warning col-sm-7">
<strong>警告!</strong> 你们的聊天信息已经被全程监控...
</div>
<div v-for="(value, key) in list" :class="className(value.uid)" >
{{ value.content }}
</div>
<div>
<form action="" method="post" class="form-horizontal" role="form">
<div class="form-group">
<div class="col-sm-8">
<input type="text" class="form-control" placeholder="远在光年之外的你们,想聊些什么呢..." v-model="content" required="required">
</div>
</div>
<button type="submit" class="btn btn-info offset-md-6" @click.prevent="pushData">发送</button>
</form>
</div>
</div>
</div>
<script>
let ws = new WebSocket('ws://127.0.0.1/chat')
ws.onopen = function() {
let data = {
'sendUid': vm.$data.uid,
'type': 'bind',
'receiveUid': '',
}
// 将uid推送到服务端,与fd进行绑定
ws.send(JSON.stringify(data));
}
ws.onmessage = function(evt) {
let data = JSON.parse(evt.data)
if (data.content) {
vm.$data.list.push(data)
}
}
ws.onclose = function() {
console.log('连接关闭')
}
let vm = new Vue({
el: "#app",
data: {
list: [],
content: '',
uid: <?=$uid?>,
receiveUid: <?=$receiveUid?>,
userName: '<?=$userName?>',
},
methods: {
className(uid) {
if (uid === this.uid) {
return 'alert alert-success col-md-2'
} else {
return 'alert alert-danger col-md-2 offset-md-5'
}
},
pushData() {
let data = {
'sendUid': this.uid,
'receiveUid': this.receiveUid,
'content': this.content,
'type': 'chat'
}
ws.send(JSON.stringify(data));
this.content = ''
}
},
})
</script>
</body>
</html>
创建ws控制器
☁ swoft [master] ⚡ php bin/swoft gen:websocket chat --prefix /chat
Class data:
{
"name": "chat",
"suffix": "Controller",
"namespace": "App\\WebSocket",
"className": "ChatController",
"prefix": "/chat"
}
<?php
namespace App\WebSocket;
use Swoft\Http\Message\Server\Request;
use Swoft\Http\Message\Server\Response;
use Swoft\WebSocket\Server\Bean\Annotation\WebSocket;
use Swoft\WebSocket\Server\HandlerInterface;
use Swoole\WebSocket\Frame;
use Swoole\WebSocket\Server;
/**
* Class ChatController - This is an controller for handle websocket
* @package App\WebSocket
* @WebSocket("chat")
*/
class ChatController implements HandlerInterface
{
/**
* @param Request $request
* @param Response $response
* @return array
* [
* self::HANDSHAKE_OK,
* $response
* ]
*/
public function checkHandshake(Request $request, Response $response): array
{
return [self::HANDSHAKE_OK, $response];
}
/**
* @param Server $server
* @param Request $request
* @param int $fd
* @return mixed
*/
public function onOpen(Server $server, Request $request, int $fd)
{
}
/**
* @param Server $server
* @param Frame $frame
* @return mixed
*/
public function onMessage(Server $server, Frame $frame)
{
$fd = $frame->fd;
$data = json_decode($frame->data, true);
if ($data['type'] == 'bind') {
// 将uid与fd绑定
$server->bind($fd, $data['sendUid']);
}
$start_fd = 0;
while(true)
{
// 获取所有fd连接
$conn_list = $server->getClientList($start_fd, 10);
if ($conn_list === false or count($conn_list) === 0)
{
break;
}
$start_fd = end($conn_list);
foreach($conn_list as $v)
{
// 根据fd获取uid
$connection = $server->connection_info($v);
if (isset($connection['uid']) && in_array($connection['uid'], [$data['receiveUid'], $data['sendUid']])) {
if (isset($data['content'])) {
$response = [
'type' => 'response',
'content' => $data['content'],
'uid' => $data['receiveUid'],
];
if ($v != $fd) { // 避免重复发送给消息发起者的fd
$server->push($v, json_encode($response, true));
}
}
}
}
}
// 推送消息给客户端
\Swoft::$server->sendTo($fd, $frame->data);
}
/**
* @param Server $server
* @param int $fd
* @return mixed
*/
public function onClose(Server $server, int $fd)
{
// do something. eg. record log
}
}
结果演示
建议同时多开几个浏览器窗口,访问http://127.0.0.1/chat/1
或http://127.0.0.1/chat/2
切换窗口进行消息发送,观察其他窗口的数据变动
程心与云天明对话.gif如果觉得本文对你有所帮助,点个赞,或者赏杯咖啡钱,你的认可对我很重要
网友评论