項目背景:
最近由于公司的業務需求,需要用到聊天功能。而且有比較多的個性化需求需要定制。之前使用別人的聊天組件是基于微擎的。如果要移植到普通的H5在邏輯修改還有定制上存在比較多的困難。為此只能克服困難,自己搭建一個吧
什么是Workerman?
Workerman是一款 開源 高性能異步 PHP socket即時通訊框架 。支持高并發,超高穩定性,被廣泛的用于手機app、移動通訊,微信小程序,手游服務端、網絡游戲、PHP聊天室、硬件通訊、智能家居、車聯網、物聯網等領域的開發。 支持TCP長連接,支持Websocket、HTTP等協議,支持自定義協議。擁有異步Mysql、異步Redis、異步Http、MQTT物聯網客戶端、異步消息隊列等眾多高性能組件。
開始實戰吧!
1.第一步我們先把workerman里需要用到的擴展composer下來吧
1 | "workerman/gateway-worker": "^3.0", |
2 | "workerman/gatewayclient": "^3.0", |
3 | "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直接運行對應的啟動文件
如果你是在window下就雙擊start_for_win.bat運行
5.運行成功后,你就應該可以看到以下的界面

到此我們搭建基于workerman的通信環境就已經完成。接下來我們就可以根據自己的項目需求進行開發。在此向大家重點說明。我們所有的聊天是邏輯都在目錄中的Events.php進行修改。
---------------------------------華麗分割線---------------------------------------------------
下面我給大家貼一下我編寫的部分份代碼。
Event.php
026 | use \GatewayWorker\Lib\Gateway; |
036 | public static function onConnect( $client_id ) |
038 | Gateway::sendToClient( $client_id , json_encode( array ( |
040 | 'client_id' => $client_id |
050 | public static function onMessage( $client_id , $message ) |
053 | 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" ; |
056 | $message_data = json_decode( $message , true); |
063 | switch ( $message_data [ 'type' ]) |
071 | if (!isset( $message_data [ 'room_id' ])) |
073 | throw new \Exception( "\$message_data['room_id'] not set. client_ip:{$_SERVER['REMOTE_ADDR']} \$message:$message" ); |
077 | $room_id = $message_data [ 'room_id' ]; |
078 | $client_name = htmlspecialchars( $message_data [ 'client_name' ]); |
079 | $_SESSION [ 'room_id' ] = $room_id ; |
080 | $_SESSION [ 'client_name' ] = $client_name ; |
084 | $clients_list = Gateway::getClientSessionsByGroup( $room_id ); |
085 | foreach ( $clients_list as $tmp_client_id => $item ) |
087 | $clients_list [ $tmp_client_id ] = $item [ 'client_name' ]; |
092 | $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' ], |
093 | 'from' => $message_data [ 'from' ], 'tag' => $message_data [ 'tag' ]); |
094 | Gateway::sendToGroup( $room_id , json_encode( $new_message )); |
095 | Gateway::joinGroup( $client_id , $room_id ); |
098 | $new_message [ 'client_list' ] = $clients_list ; |
099 | Gateway::sendToCurrentClient(json_encode( $new_message )); |
105 | if (!isset( $_SESSION [ 'room_id' ])) |
107 | throw new \Exception( "\$_SESSION['room_id'] not set. client_ip:{$_SERVER['REMOTE_ADDR']}" ); |
109 | $room_id = $_SESSION [ 'room_id' ]; |
110 | $client_name = $_SESSION [ 'client_name' ]; |
128 | $new_message = array ( |
130 | 'from_client_id' => $client_id , |
131 | 'from_client_name' => $client_name , |
132 | 'to_client_id' => 'all' , |
133 | 'content' => nl2br (htmlspecialchars( $message_data [ 'content' ])), |
134 | 'time' => date ( 'Y-m-d H:i:s' ), |
137 | return Gateway::sendToGroup( $room_id ,json_encode( $new_message )); |
144 | public static function onClose( $client_id ) |
147 | echo "client:{$_SERVER['REMOTE_ADDR']}:{$_SERVER['REMOTE_PORT']} gateway:{$_SERVER['GATEWAY_ADDR']}:{$_SERVER['GATEWAY_PORT']} client_id:$client_id onClose:''\n" ; |
150 | if (isset( $_SESSION [ 'room_id' ])) |
152 | $room_id = $_SESSION [ 'room_id' ]; |
153 | $new_message = array ( 'type' => 'logout' , 'from_client_id' => $client_id , 'from_client_name' => $_SESSION [ 'client_name' ], 'time' => date ( 'Y-m-d H:i:s' )); |
154 | Gateway::sendToGroup( $room_id , json_encode( $new_message )); |
客戶端頁面
004 | < meta charset = "UTF-8" > |
005 | < title >與{{$to->name}}的對話</ title > |
006 | < script type = "text/javascript" src = "{{asset('js')}}/swfobject.js" ></ script > |
007 | < script type = "text/javascript" src = "{{asset('js')}}/web_socket.js" ></ script > |
008 | < script type = "text/javascript" src = "{{asset('js')}}/jquery.min.js" ></ script > |
009 | < link href = "{{asset('css')}}/jquery-sinaEmotion-2.1.0.min.css" rel = "external nofollow" rel = "stylesheet" > |
010 | < link href = "{{asset('css')}}/bootstrap.min.css" rel = "external nofollow" rel = "stylesheet" > |
011 | < link href = "{{asset('css')}}/style.css" rel = "external nofollow" rel = "stylesheet" > |
012 | < script type = "text/javascript" src = "{{asset('js')}}/jquery-sinaEmotion-2.1.0.min.js" ></ script > |
027 | border: 1px solid #e8e8e8; |
032 | < body onload = "connect();" style = "margin: auto; text-align: center;" > |
033 | < div style = "margin: auto;" > |
034 | < div style = "border: 1px solid red; height: 40px; width: 500px; margin: auto;" > |
037 | < div style = "width: 80px; height: 40px; border: 1px solid blue; float: left" > |
038 | < img src="{{$to->heading}}" width="80px" height="40px"> |
040 | < div style = "width: 150px; height: 40px; border: 1px solid blue; float: left" > |
045 | < div class = "content" style = "width: 500px; height: 400px; border: 1px solid green; margin-top: 40px; overflow-y: auto" > |
047 | {{--< div style = "min-height: 50px;margin-top: 10px;" >--}} |
048 | {{--< div style = "width: 50px;height: 50px; border: 1px solid red; margin-left:10px; float: left" >--}} |
049 | {{--< img src="{{$to->heading}}" width="50px" height="50px">--}} |
051 | {{--< div style = "border: 1px solid red; float: left; min-height: 50px" >dsadsadsadsadsa</ div >--}} |
054 | {{--< div style = "min-height:50px;margin-top: 10px;" >--}} |
055 | {{--< div style = "width: 50px;height: 50px; border: 1px solid red; margin-left:10px; float: right" >--}} |
056 | {{--< img src="{{$from->heading}}" width="50px" height="50px">--}} |
058 | {{--< div style = "border: 1px solid red; float: right; min-height: 50px" >dsadsadsadsadsa</ div >--}} |
062 | < form onsubmit = "return onSubmit(); return false;" id = "ajaxfrom" > |
063 | < input type = "hidden" name = "to" value="{{$to->id}}"> |
064 | < input type = "hidden" name = "from" value="{{$from->id}}"> |
065 | < input type = "hidden" name = "room_id" value = "{{$room}}" > |
066 | < input type = "hidden" name = "tag" value = "{{$tag}}" > |
067 | < textarea id = "textarea" name = "content" class = "Input_text" style = "margin: 0px; width: 501px; height: 213px;" ></ textarea > |
068 | < div class = "say-btn" > |
069 | < input type = "button" class = "btn btn-default face pull-left" value = "表情" /> |
070 | < button type = "submit" class = "btn btn-default" >發表</ button > |
079 | < script type = "text/javascript" > |
080 | if (typeof console == "undefined") { this.console = { log: function (msg) { } };} |
081 | // 如果瀏覽器不支持websocket,會使用這個flash自動模擬websocket協議,此過程對開發者透明 |
082 | WEB_SOCKET_SWF_LOCATION = "/swf/WebSocketMain.swf"; |
083 | // 開啟flash的websocket debug |
084 | WEB_SOCKET_DEBUG = true; |
085 | var ws, name, client_list={}; |
090 | // 創建websocket 屆時可以替換為對應的服務器地址 |
091 | ws = new WebSocket("ws://"+document.domain+":7272"); |
092 | // 當socket連接打開時,輸入用戶名 |
095 | ws.onmessage = onmessage; |
096 | //當連接丟失時,調用連接方法嘗試重新連接 |
097 | ws.onclose = function() { |
098 | console.log("連接關閉,定時重連"); |
102 | ws.onerror = function() { |
107 | $.post("/get_record", { "room":"{{$room}}" }, |
109 | $.each(msg,function (v,k) { |
112 | if(k.tag!="{{$tag}}"){ |
113 | $(".content").append( |
114 | '< div style = "min-height: 50px;margin-top: 10px;" >' + |
115 | '< div style = "width: 50px;height: 50px; border: 1px solid red; margin-left:10px; float: left" >'+ |
116 | '< img src="{{$to->heading}}" width="50px" height="50px">'+ |
118 | '< div style = "border: 1px solid red; float: left; min-height: 50px" >'+k.content+'</ div >'+ |
122 | $(".content").append( |
123 | '< div style = "min-height: 50px;margin-top: 10px;" >' + |
124 | '< div style = "width: 50px;height: 50px; border: 1px solid red; margin-left:10px; float: right" >'+ |
125 | '< img src="{{$from->heading}}" width="50px" height="50px">'+ |
127 | '< div style = "border: 1px solid red; float: right; min-height: 50px" >'+k.content+'</ div >'+ |
138 | var login_data='{"type":"login","client_name":"{{$from->name}}","room_id":"{{$room}}","to":"{{$to->id}}","from":"{{$from->id}}","tag":"{{$tag}}"}'; |
145 | function onmessage(e) |
147 | var data = JSON.parse(e.data); |
148 | switch(data['type']){ |
151 | ws.send('{"type":"pong"}'); |
155 | //講需要的發送ID保存到本地to_client_id變量中 |
156 | for(var p in data['client_list']){ |
159 | console.log(to_client_id); |
164 | say(data['from_client_id'], data['from_client_name'], data['content'], data['time']); |
171 | //此處可以發送ajax用于綁定不同的用戶ID和client |
179 | function onSubmit() { |
188 | // contentType: false, |
189 | // processData: false, |
193 | success: function (msg) { |
195 | alert('當前的對話已經超過次數,請購買對應服務') |
204 | var neirong=$("#textarea").val().replace(/"/g, '\\"').replace(/\n/g,'\\n').replace(/\r/g, '\\r'); |
205 | //ajax先把對應的內容發送到后臺錄入,回調成功后才把信息發送 |
206 | var fm=$("#ajaxfrom")[0]; |
207 | var formData = new FormData(fm); |
215 | beforeSend:function(){ |
218 | success: function (msg) { |
221 | ws.send('{"type":"say","to_client_id":"all","to_client_name":"{{$to->name}}","content":"'+neirong+'"}'); |
223 | $("#textarea").val(""); |
225 | $("#textarea").focus(); |
243 | function say(from_client_id, from_client_name, content, time){ |
244 | //判斷當前的用戶名稱與發送消息的名稱是否一致 |
245 | if( "{{$from->name}}" == from_client_name){ |
246 | $(".content").append( |
247 | '< div style = "min-height: 50px;margin-top: 10px;" >' + |
248 | '< div style = "width: 50px;height: 50px; border: 1px solid red; margin-left:10px; float: right" >'+ |
249 | '< img src="{{$from->heading}}" width="50px" height="50px">'+ |
251 | '< div style = "border: 1px solid red; float: right; min-height: 50px" >'+content+'</ div >'+ |
255 | $(".content").append( |
256 | '< div style = "min-height: 50px;margin-top: 10px;" >' + |
257 | '< div style = "width: 50px;height: 50px; border: 1px solid red; margin-left:10px; float: left" >'+ |
258 | '< img src="{{$to->heading}}" width="50px" height="50px">'+ |
260 | '< div style = "border: 1px solid red; float: left; min-height: 50px" >'+content+'</ div >'+ |
265 | // $("#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(); |
269 | select_client_id = 'all'; |
271 | //如果發送的用戶有變化則對應的用戶ID進行替換 |
272 | $("#client_list").change(function(){ |
273 | select_client_id = $("#client_list option:selected").attr("value"); |
276 | $('.face').click(function(event){ |
277 | $(this).sinaEmotion(); |
278 | event.stopPropagation(); |
282 | // document.write('< meta name = "viewport" content = "width=device-width,initial-scale=1" >'); |
283 | $("textarea").on("keydown", function(e) { |
285 | if(e.keyCode === 13 && !e.ctrlKey) { |
292 | if(e.keyCode === 13 && e.ctrlKey) { |
293 | $(this).val(function(i,val){ |
這兩個代碼片段其實就是主要運行的核心片段。其他框架的自帶參數需要各位自己去根據文檔去調試優化。到此基于workerman的聊天用于功能demo已經搭建完畢。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。