Dai Chong's blog

基础介绍

 (1)Swoole是一个面向生产环境的 PHP 异步网络通信引擎,使 PHP 开发人员可以编写高性能的异步并发 TCP、UDP、Unix Socket、HTTP,WebSocket 服务。Swoole 可以广泛应用于互联网、移动通信、企业软件、云计算、网络游戏、物联网(IOT)、车联网、智能家居等领域。 使用 PHP + Swoole 作为网络通信框架,可以使企业 IT 研发团队的效率大大提升。—百度百科

 (2)WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。
WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。 —百度百科

 (3)那这篇的主题就是如何使用Swoole+WebSocket实现一个简易的聊天室。

  熟悉网络通信协议的同学肯定不会陌生。

功能需求及问题处理

 web端:
  (1)每次刷新都会生成一个唯一的ID(id值从1开始).
  (2)第一次进入网站时会要求用户设置昵称并会与ID进行绑定。
 问题点:
  (1)刷新页面后用户标识(ID)会重新生成,之前生成ID被弃用。
  (2)websocket生成了新的用户ID,但是跟现在的无法形成关联关系。

 server端:
 (1)当用户进入聊天室后,发送广播给所有人并加入聊天群组(使用redis存储)。
 (2)当用户退出直播间后,发送广播给所有人并清除该用户的记录。
 (3)用户每发送一次消息都要形成新的记录广播给所有人。
 (4)用户生成新的昵称后把昵称推送给他。

 web端问题处理方法:
 (1)浏览器刷新时提醒用户刷新即将重新获得新的身份。
 (2)用户连接成功后记录用户name,每次连接把这个name带上,清除之前该name的绑定关系,形成新的关系。

演示地址

聊天室演示

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
<?php
include "RedisManager.php";

$server = new Swoole\WebSocket\Server("0.0.0.0", 8877);

// 客户端连接
$server->on('open', function (Swoole\WebSocket\Server $server, $request) {
//连接成功把当前在线的用户返回
$user_list = RedisConnect::getRedis()->hGetAll('message:user');
alone($server, $request->fd, [
'type' => 'first',
'data' => $user_list
]);
});

// 接收客户端发送的消息
$server->on('message', function (Swoole\WebSocket\Server $server, $frame) {
$message_data = json_decode($frame->data, true);
$type = $message_data['type'];
$data = $message_data['user_name'];
if (isset($message_data['send_message'])) {
$user_message = $message_data['send_message'];
}
switch ($type) {
case 'save_user':
$new_name = '大厦单子' . $frame->fd . '号';
// 修改用户昵称
RedisConnect::getRedis()->hset('message:user', $frame->fd, $new_name);

// 把生成的用户昵称返回给他
alone($server, $frame->fd, [
'type' => 'new_name',
'name' => $new_name,
'id' => $frame->fd
]);

// 广播消息给其他用户
groupSending($server, [
'type' => 'open',
'name' => $new_name,
'id' => $frame->fd
]);
break;
case 'send_message':
$msg = [
'id' => $frame->fd,
'user_name' => $data,
'message' => $user_message,
'type' => 'message'
];
// 接受用户发送的消息
RedisConnect::getRedis()->lpush('message:user:say', $msg);
groupSending($server, $msg);
break;
}
});

// 退出聊天室
$server->on('close', function ($ser, $fd) {
$user = RedisConnect::getRedis()->hget('message:user', $fd);
RedisConnect::getRedis()->hdel('message:user', $fd);
$msg = [
'id' => $fd,
'user_name' => $user,
'message' => '退出聊天室',
'type' => 'close'
];
groupSending($ser, $msg, $fd);
});

// 开启服务
$server->start();

// 群发消息
function groupSending($server, $msg, $self = null)
{
foreach ($server->connections as $conn) {
if ($conn == $self) break; //不再推送给当前退出的用户
$server->push($conn, json_encode($msg));
}
}

// 单独发消息
function alone($server, $fd, $msg)
{
$server->push($fd, json_encode($msg));
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>聊天室</title>
<style>
*{margin:0;padding:0}.box{width:602px;height:500px;margin:20px auto}.body{width:500px;height:500px;border:1px solid #333;padding:20px;box-sizing:border-box;float:right}.title{text-align:center;margin-top:10px}.msg{margin-top:20px;width:100%;text-align:left}.message_box{width:100%;height:90%;margin-top:10px;font-size:12px;overflow:hidden;overflow-y:auto}.name{font-weight:bold}.right{text-align:right}.prompt{width:600px;margin:0 auto}.live{width:100px;height:500px;float:left;border:1px solid #333;font-size:12px;text-align:center}.live-box{height:100%;overflow:hidden;overflow-y:auto}.live-box p{margin-top:10px}
</style>
</head>
<body>
<div class="box">
<div class="body">
<div class="title">+++++++++++聊天室++++++++++++</div>
<div class="message_box" id="test">
</div>
</div>
<div class="live">
<div class="live-title">在线人数</div>
<div class="live-box">
</div>
</div>
</div>
<div class="prompt">
<input type="text" name="say">
<input type="button" value="发送" id="send">
<!-- <input type="button" value="增加消息" id="add"> -->
</div>

</body>
<script src="jquery.js"></script>
<script type="text/javascript">
window.onbeforeunload = function (event) {
window.localStorage.clear();
};
$(function () {
window.user_name = '';
// 获取在线用户
if (!("WebSocket" in window)) {
alert("您的浏览器不支持 WebSocket!");
return false;
}
// 连接
let ws = new WebSocket("ws://49.235.172.110:8877");
//连接成功
ws.onopen = function (event) {
// 修改昵称
let user_info = {
'type': 'save_user',
'user_name': '',
};
ws.send(JSON.stringify(user_info));
console.log('连接成功', '修改昵称成功');
};
ws.onmessage = function (evt) {
let received_msg = JSON.parse(evt.data);
let type = received_msg['type'];
switch (type) {
case 'message':
add_msg(received_msg);
break;
case 'close':
$(".live-box").find('p[user=' + received_msg['id'] + ']').remove();
break;
case 'connect':
console.log(received_msg);
break;
case 'new_name':
user_name = received_msg['name'];
break;
case 'open':
$('.live-box').append('<p user="' + received_msg['id'] + '">' + received_msg['name'] + '</p>');
break;
case 'first':
let data = received_msg['data'];
let str = '';
$.each(data, function (k, v) {
if (isNaN(v)) {
str += '<p user="' + k + '">' + v + '</p>';
}
})
$('.live-box').append(str);
break;
}
}
// 发送消息
$("#send").click(function () {
send_msg();
})

$("input[name=say]").bind("keydown", function (e) {
var theEvent = e || window.event;
var code = theEvent.keyCode || theEvent.which || theEvent.charCode;
if (code == 13) {
send_msg();
}
});

// 发送消息
function send_msg() {
var message = $('input[name=say]').val();
if (message !== ' ') {
var arr = {
'type': 'send_message',
'user_name': user_name,
'send_message': message
};
ws.send(JSON.stringify(arr));
}
$('input[name=say]').val(' ');
var ele = document.getElementById("test");
if (ele.scrollHeight > ele.clientHeight) {
ele.scrollTop = ele.scrollHeight + 100;
}
}

// 增加消息
function add_msg(data) {
let str = '<div class="msg">';
str += '<span class="name">' + data['user_name'] + '</span>:';
str += '<span class="text">' + data['message'] + '</span></div>';
$('.message_box').append(str);
}
});
</script>
</html>

总结

 这篇文章只是简单的介绍前后端如何实现通信,很多的细节问题没有进行处理。
 UI比较low,这里大家只看代码就好了…
 更多关于Swoole的功能还待探索…敬请期待…


 评论