項目背景:
最近由於公司的業務需求,需要用到聊天功能。而且有比較多的個性化需求需要定制。之前使用別人的聊天組件是基於微擎的。如果要移植到普通的H5在邏輯修改還有定制上存在比較多的困難。為此只能克服困難,自己搭建一個吧
什么是Workerman?
Workerman是一款 開源 高性能異步 PHP socket即時通訊框架 。支持高並發,超高穩定性,被廣泛的用於手機app、移動通訊,微信小程序,手游服務端、網絡游戲、PHP聊天室、硬件通訊、智能家居、車聯網、物聯網等領域的開發。 支持TCP長連接,支持Websocket、HTTP等協議,支持自定義協議。擁有異步Mysql、異步Redis、異步Http、MQTT物聯網客戶端、異步消息隊列等眾多高性能組件。
開始實戰吧!
1.第一步我們先把workerman里需要用到的擴展composer下來吧
1
2
3
|
"workerman/gateway-worker": "^3.0",
"workerman/gatewayclient": "^3.0",
"workerman/workerman": "^3.5",
|
2.第二步我們到官方網站把demo全部下載下來,然后放到我們項目中的目錄
圖片中我就把整個項目都放在了HTTP/Controller/Workerman中。
3.第三步我們需要把把以下3個文件的引用部分修改為以下。不然會報路徑錯誤
start_businessworker,start_gateway,start_register
1
|
require_once
__DIR__ .
'/../../../../../vendor/autoload.php'
;
|
4.修改完成后我們就可以在liunx直接運行對應的啟動文件
1
|
php start.php start -d
|
如果你是在window下就雙擊start_for_win.bat運行
5.運行成功后,你就應該可以看到以下的界面
到此我們搭建基於workerman的通信環境就已經完成。接下來我們就可以根據自己的項目需求進行開發。在此向大家重點說明。我們所有的聊天是邏輯都在目錄中的Events.php進行修改。
---------------------------------華麗分割線---------------------------------------------------
下面我給大家貼一下我編寫的部分份代碼。
Event.php
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
|
<?php
/**
* This file is part of workerman.
*
* Licensed under The MIT License
* For full copyright and license information, please see the MIT-LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @author walkor<walkor@workerman.net>
* @copyright walkor<walkor@workerman.net>
* @link http://www.workerman.net/
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
/**
* 用於檢測業務代碼死循環或者長時間阻塞等問題
* 如果發現業務卡死,可以將下面declare打開(去掉//注釋),並執行php start.php reload
* 然后觀察一段時間workerman.log看是否有process_timeout異常
*/
//declare(ticks=1);
/**
* 聊天主邏輯
* 主要是處理 onMessage onClose
*/
use
\GatewayWorker\Lib\Gateway;
class
Events
{
/**
* 作者:何志偉
* 當客戶端連接上來的時候
* 創建時間:2018/10/25
* @param $client_id 此ID為gatewayworker 自動生成ID
*/
public
static
function
onConnect(
$client_id
)
{
Gateway::sendToClient(
$client_id
, json_encode(
array
(
'type'
=>
'init'
,
'client_id'
=>
$client_id
)));
}
/**
* 有消息時
* @param int $client_id
* @param mixed $message
*/
public
static
function
onMessage(
$client_id
,
$message
)
{
// debug
echo
"client:{$_SERVER['REMOTE_ADDR']}:{$_SERVER['REMOTE_PORT']} gateway:{$_SERVER['GATEWAY_ADDR']}:{$_SERVER['GATEWAY_PORT']} client_id:$client_id session:"
.json_encode(
$_SESSION
).
" onMessage:"
.
$message
.
"\n"
;
// 客戶端傳遞的是json數據
$message_data
= json_decode(
$message
, true);
if
(!
$message_data
)
{
return
;
}
// 根據類型執行不同的業務
switch
(
$message_data
[
'type'
])
{
// 客戶端回應服務端的心跳
case
'pong'
:
return
;
// 客戶端登錄 message格式: {type:login, name:xx, room_id:1} ,添加到客戶端,廣播給所有客戶端xx進入聊天室
case
'login'
:
// 判斷是否有房間號
if
(!isset(
$message_data
[
'room_id'
]))
{
throw
new
\Exception(
"\$message_data['room_id'] not set. client_ip:{$_SERVER['REMOTE_ADDR']} \$message:$message"
);
}
// 把房間號昵稱放到session中
$room_id
=
$message_data
[
'room_id'
];
$client_name
= htmlspecialchars(
$message_data
[
'client_name'
]);
$_SESSION
[
'room_id'
] =
$room_id
;
$_SESSION
[
'client_name'
] =
$client_name
;
// 獲取房間內所有用戶列表
$clients_list
= Gateway::getClientSessionsByGroup(
$room_id
);
foreach
(
$clients_list
as
$tmp_client_id
=>
$item
)
{
$clients_list
[
$tmp_client_id
] =
$item
[
'client_name'
];
}
// $clients_list[$client_id] = $client_name;
// 轉播給當前房間的所有客戶端,xx進入聊天室 message {type:login, client_id:xx, name:xx}
$new_message
=
array
(
'type'
=>
$message_data
[
'type'
],
'client_id'
=>
$client_id
,
'client_name'
=>htmlspecialchars(
$client_name
),
'time'
=>
date
(
'Y-m-d H:i:s'
),
'to'
=>
$message_data
[
'to'
],
'room_id'
=>
$message_data
[
'room_id'
],
'from'
=>
$message_data
[
'from'
],
'tag'
=>
$message_data
[
'tag'
]);
Gateway::sendToGroup(
$room_id
, json_encode(
$new_message
));
Gateway::joinGroup(
$client_id
,
$room_id
);
// 給當前用戶發送用戶列表
$new_message
[
'client_list'
] =
$clients_list
;
Gateway::sendToCurrentClient(json_encode(
$new_message
));
return
;
// 客戶端發言 message: {type:say, to_client_id:xx, content:xx}
case
'say'
:
// 非法請求
if
(!isset(
$_SESSION
[
'room_id'
]))
{
throw
new
\Exception(
"\$_SESSION['room_id'] not set. client_ip:{$_SERVER['REMOTE_ADDR']}"
);
}
$room_id
=
$_SESSION
[
'room_id'
];
$client_name
=
$_SESSION
[
'client_name'
];
// 私聊
// if($message_data['to_client_id'] != 'all')
// {
// $new_message = array(
// 'type'=>'say',
// 'from_client_id'=>$client_id,
// 'from_client_name' =>$client_name,
// 'to_client_id'=>$message_data['to_client_id'],
// 'content'=>"<b>對你說: </b>".nl2br(htmlspecialchars($message_data['content'])),
// 'time'=>date('Y-m-d H:i:s'),
// );
// Gateway::sendToClient($message_data['to_client_id'], json_encode($new_message));
// $new_message['content'] = "<b>你對".htmlspecialchars($message_data['to_client_name'])."說: </b>".nl2br(htmlspecialchars($message_data['content']));
// return Gateway::sendToCurrentClient(json_encode($new_message));
// }
$new_message
=
array
(
'type'
=>
'say'
,
'from_client_id'
=>
$client_id
,
'from_client_name'
=>
$client_name
,
'to_client_id'
=>
'all'
,
'content'
=>
nl2br
(htmlspecialchars(
$message_data
[
'content'
])),
'time'
=>
date
(
'Y-m-d H:i:s'
),
);
return
Gateway::sendToGroup(
$room_id
,json_encode(
$new_message
));
}
}
/**
* 當客戶端斷開連接時
* @param integer $client_id 客戶端id
*/
public
static
function
onClose(
$client_id
)
{
// debug
echo
"client:{$_SERVER['REMOTE_ADDR']}:{$_SERVER['REMOTE_PORT']} gateway:{$_SERVER['GATEWAY_ADDR']}:{$_SERVER['GATEWAY_PORT']} client_id:$client_id onClose:''\n"
;
// 從房間的客戶端列表中刪除
if
(isset(
$_SESSION
[
'room_id'
]))
{
$room_id
=
$_SESSION
[
'room_id'
];
$new_message
=
array
(
'type'
=>
'logout'
,
'from_client_id'
=>
$client_id
,
'from_client_name'
=>
$_SESSION
[
'client_name'
],
'time'
=>
date
(
'Y-m-d H:i:s'
));
Gateway::sendToGroup(
$room_id
, json_encode(
$new_message
));
}
}
}
|
客戶端頁面
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
|
<!DOCTYPE html>
<
html
lang
=
"en"
>
<
head
>
<
meta
charset
=
"UTF-8"
>
<
title
>與{{$to->name}}的對話</
title
>
<
script
type
=
"text/javascript"
src
=
"{{asset('js')}}/swfobject.js"
></
script
>
<
script
type
=
"text/javascript"
src
=
"{{asset('js')}}/web_socket.js"
></
script
>
<
script
type
=
"text/javascript"
src
=
"{{asset('js')}}/jquery.min.js"
></
script
>
<
link
href
=
"{{asset('css')}}/jquery-sinaEmotion-2.1.0.min.css"
rel
=
"external nofollow"
rel
=
"stylesheet"
>
<
link
href
=
"{{asset('css')}}/bootstrap.min.css"
rel
=
"external nofollow"
rel
=
"stylesheet"
>
<
link
href
=
"{{asset('css')}}/style.css"
rel
=
"external nofollow"
rel
=
"stylesheet"
>
<
script
type
=
"text/javascript"
src
=
"{{asset('js')}}/jquery-sinaEmotion-2.1.0.min.js"
></
script
>
</
head
>
<
style
>
#sinaEmotion {
z-index: 999;
width: 373px;
padding: 10px;
display: none;
font-size: 12px;
background: #fff;
overflow: hidden;
position: absolute;
border: 1px solid #e8e8e8;
top: 100px;
left: 542.5px;
}
</
style
>
<
body
onload
=
"connect();"
style
=
"margin: auto; text-align: center;"
>
<
div
style
=
"margin: auto;"
>
<
div
style
=
"border: 1px solid red; height: 40px; width: 500px; margin: auto;"
>
{{--對話窗口頭部--}}
<
div
>
<
div
style
=
"width: 80px; height: 40px; border: 1px solid blue; float: left"
>
<
img
src="{{$to->heading}}" width="80px" height="40px">
</
div
>
<
div
style
=
"width: 150px; height: 40px; border: 1px solid blue; float: left"
>
{{$to->name}}
</
div
>
</
div
>
{{--//對話窗口內容--}}
<
div
class
=
"content"
style
=
"width: 500px; height: 400px; border: 1px solid green; margin-top: 40px; overflow-y: auto"
>
{{--對方的頭像與文字--}}
{{--<
div
style
=
"min-height: 50px;margin-top: 10px;"
>--}}
{{--<
div
style
=
"width: 50px;height: 50px; border: 1px solid red; margin-left:10px; float: left"
>--}}
{{--<
img
src="{{$to->heading}}" width="50px" height="50px">--}}
{{--</
div
>--}}
{{--<
div
style
=
"border: 1px solid red; float: left; min-height: 50px"
>dsadsadsadsadsa</
div
>--}}
{{--</
div
>--}}
{{--我的頭像與文字--}}
{{--<
div
style
=
"min-height:50px;margin-top: 10px;"
>--}}
{{--<
div
style
=
"width: 50px;height: 50px; border: 1px solid red; margin-left:10px; float: right"
>--}}
{{--<
img
src="{{$from->heading}}" width="50px" height="50px">--}}
{{--</
div
>--}}
{{--<
div
style
=
"border: 1px solid red; float: right; min-height: 50px"
>dsadsadsadsadsa</
div
>--}}
{{--</
div
>--}}
</
div
>
{{--對話發送窗口--}}
<
form
onsubmit
=
"return onSubmit(); return false;"
id
=
"ajaxfrom"
>
<
input
type
=
"hidden"
name
=
"to"
value="{{$to->id}}">
<
input
type
=
"hidden"
name
=
"from"
value="{{$from->id}}">
<
input
type
=
"hidden"
name
=
"room_id"
value
=
"{{$room}}"
>
<
input
type
=
"hidden"
name
=
"tag"
value
=
"{{$tag}}"
>
<
textarea
id
=
"textarea"
name
=
"content"
class
=
"Input_text"
style
=
"margin: 0px; width: 501px; height: 213px;"
></
textarea
>
<
div
class
=
"say-btn"
>
<
input
type
=
"button"
class
=
"btn btn-default face pull-left"
value
=
"表情"
/>
<
button
type
=
"submit"
class
=
"btn btn-default"
>發表</
button
>
</
div
>
</
form
>
房間號{{$room}}
</
div
>
</
div
>
</
body
>
</
html
>
<
script
type
=
"text/javascript"
>
if (typeof console == "undefined") { this.console = { log: function (msg) { } };}
// 如果瀏覽器不支持websocket,會使用這個flash自動模擬websocket協議,此過程對開發者透明
WEB_SOCKET_SWF_LOCATION = "/swf/WebSocketMain.swf";
// 開啟flash的websocket debug
WEB_SOCKET_DEBUG = true;
var ws, name, client_list={};
var to_client_id="";
// 連接服務端初始化函數
function connect() {
// 創建websocket 屆時可以替換為對應的服務器地址
ws = new WebSocket("ws://"+document.domain+":7272");
// 當socket連接打開時,輸入用戶名
ws.onopen = onopen;
// 當有消息時根據消息類型顯示不同信息
ws.onmessage = onmessage;
//當連接丟失時,調用連接方法嘗試重新連接
ws.onclose = function() {
console.log("連接關閉,定時重連");
connect();
};
//當操作報錯時,返回異常錯誤
ws.onerror = function() {
console.log("出現錯誤");
};
//發送ajax獲取當前房間的通話記錄
$.post("/get_record", { "room":"{{$room}}" },
function(msg){
$.each(msg,function (v,k) {
console.log(k);
//判斷
if(k.tag!="{{$tag}}"){
$(".content").append(
'<
div
style
=
"min-height: 50px;margin-top: 10px;"
>' +
'<
div
style
=
"width: 50px;height: 50px; border: 1px solid red; margin-left:10px; float: left"
>'+
'<
img
src="{{$to->heading}}" width="50px" height="50px">'+
'</
div
>'+
'<
div
style
=
"border: 1px solid red; float: left; min-height: 50px"
>'+k.content+'</
div
>'+
'<
div
>'
).parseEmotion();
}else{
$(".content").append(
'<
div
style
=
"min-height: 50px;margin-top: 10px;"
>' +
'<
div
style
=
"width: 50px;height: 50px; border: 1px solid red; margin-left:10px; float: right"
>'+
'<
img
src="{{$from->heading}}" width="50px" height="50px">'+
'</
div
>'+
'<
div
style
=
"border: 1px solid red; float: right; min-height: 50px"
>'+k.content+'</
div
>'+
'<
div
>'
).parseEmotion();
}
})
});
}
// 連接建立時發送登錄信息
function onopen()
{
var login_data='{"type":"login","client_name":"{{$from->name}}","room_id":"{{$room}}","to":"{{$to->id}}","from":"{{$from->id}}","tag":"{{$tag}}"}';
ws.send(login_data);
console.log('登錄成功')
}
// 服務端發來消息時
function onmessage(e)
{
var data = JSON.parse(e.data);
switch(data['type']){
// 服務端ping客戶端心跳
case 'ping':
ws.send('{"type":"pong"}');
break;
// 登錄 更新用戶列表
case 'login':
//講需要的發送ID保存到本地to_client_id變量中
for(var p in data['client_list']){
to_client_id=p;
}
console.log(to_client_id);
break;
// 發言
case 'say':
console.log(data);
say(data['from_client_id'], data['from_client_name'], data['content'], data['time']);
break;
// 用戶退出 更新用戶列表
case 'logout':
console.log(data);
break;
case 'init':
//此處可以發送ajax用於綁定不同的用戶ID和client
console.log(data);
break;
}
}
// 提交對話
function onSubmit() {
//先檢查當前的對話是否超過20條記錄數
var count=true;
//發送ajax獲取當前房間的通話記錄
$.ajax({
url: "/check_count",
type: "post",
async:false,
// cache: false,
// contentType: false,
// processData: false,
data:{
'room':"1",
},
success: function (msg) {
if(msg>10){
alert('當前的對話已經超過次數,請購買對應服務')
count=false;
}
}
});
if(count){
var neirong=$("#textarea").val().replace(/"/g, '\\"').replace(/\n/g,'\\n').replace(/\r/g, '\\r');
//ajax先把對應的內容發送到后台錄入,回調成功后才把信息發送
var fm=$("#ajaxfrom")[0];
var formData = new FormData(fm);
$.ajax({
url: "/record",
type: "post",
cache: false,
contentType: false,
processData: false,
data: formData,
beforeSend:function(){
},
success: function (msg) {
if(msg.code=="0"){
ws.send('{"type":"say","to_client_id":"all","to_client_name":"{{$to->name}}","content":"'+neirong+'"}');
//清空文本框內容
$("#textarea").val("");
//強制定位光標
$("#textarea").focus();
}else{
}
}
});
}
return false;
}
// 發言
function say(from_client_id, from_client_name, content, time){
//判斷當前的用戶名稱與發送消息的名稱是否一致
if( "{{$from->name}}" == from_client_name){
$(".content").append(
'<
div
style
=
"min-height: 50px;margin-top: 10px;"
>' +
'<
div
style
=
"width: 50px;height: 50px; border: 1px solid red; margin-left:10px; float: right"
>'+
'<
img
src="{{$from->heading}}" width="50px" height="50px">'+
'</
div
>'+
'<
div
style
=
"border: 1px solid red; float: right; min-height: 50px"
>'+content+'</
div
>'+
'<
div
>'
).parseEmotion();
}else{
$(".content").append(
'<
div
style
=
"min-height: 50px;margin-top: 10px;"
>' +
'<
div
style
=
"width: 50px;height: 50px; border: 1px solid red; margin-left:10px; float: left"
>'+
'<
img
src="{{$to->heading}}" width="50px" height="50px">'+
'</
div
>'+
'<
div
style
=
"border: 1px solid red; float: left; min-height: 50px"
>'+content+'</
div
>'+
'<
div
>'
).parseEmotion();
}
// $("#dialog").append('<
div
class
=
"speech_item"
><
img
src
=
"http://lorempixel.com/38/38/?'+from_client_id+'"
class
=
"user_icon"
/> '+from_client_name+' <
br
> '+time+'<
div
style
=
"clear:both;"
></
div
><
p
class
=
"triangle-isosceles top"
>'+content+'</
p
> </
div
>').parseEmotion();
}
$(function(){
//全局用戶ID
select_client_id = 'all';
//如果發送的用戶有變化則對應的用戶ID進行替換
$("#client_list").change(function(){
select_client_id = $("#client_list option:selected").attr("value");
});
//表情選擇
$('.face').click(function(event){
$(this).sinaEmotion();
event.stopPropagation();
});
});
// document.write('<
meta
name
=
"viewport"
content
=
"width=device-width,initial-scale=1"
>');
$("textarea").on("keydown", function(e) {
//按enter鍵自動提交
if(e.keyCode === 13 && !e.ctrlKey) {
e.preventDefault();
$('form').submit();
return false;
}
// 按ctrl+enter組合鍵換行
if(e.keyCode === 13 && e.ctrlKey) {
$(this).val(function(i,val){
return val + "\n";
});
}
});
</
script
>
|