workerman is a communication service written in PHP. Previous projects used it as a data interface service
This time I used it to make a simple online chat room~
1. Download the latest version of workerman
You can go http://www.workerman.net Go to download
I have separated service and client into two folders to facilitate management
The general project structure is as follows.
Client:
The client is simple. A simple html code. Embedded a websocket monitoring service
var ws, name, client_list={}; function connect() { // 创建websocket ws = new WebSocket("ws://192.168.0.88:2345"); // 当socket连接打开时,输入用户名 ws.onopen = onopen; // 当有消息时根据消息类型显示不同信息 ws.onmessage = onmessage; ws.onclose = function() { console.log("连接关闭,定时重连"); connect(); }; ws.onerror = function() { console.log("出现错误"); }; }
realizes the opening of websocket, monitoring of messages, and close
1. When a client is opened, a dialog box for entering a name will pop up immediately
function onopen(){ //console.log(name); //var username=connect_id=""; if(!name) { name=prompt("请输入您的名字",""); if(!name || name=='null'){ name = '咕哒子'; } } $('#curuser').text(name); data='{"type":"1","user":"'+name+'"}'; ws.send(data); }
and push the data to the server. type =1 represents login.
2. When receiving a message, determine the message type, whether it is a group message or a private message. And then process.
In addition, every time a new user logs in, a user list will be pushed to each client. Rendering
function onmessage(e){ //console.log(e.data); var data = eval("("+e.data+")"); var info=$('#chatinfo').html(); if(data.type==1) $('#chatinfo').html(info+'<br/>'+data.data); else if(data.type==2) { // 在线用户列表 userinfo $('#userinfo').html(data.data); } else if(data.type==3) { // 在线用户列表 个人信息 name=data.data.userinfo; //console.log(data.data); } }
Then there is the code for each user to send messages. It can be a private chat or a group message
$('#send').click(function(e){ var msg=$('#msg').val(); var tofriend=$('#tofriend').val(); var tofriendname=$('#tofriendname').val(); if(tofriend!="") { data='{"type":"3","user":"'+name+'","msg":"'+msg+'","friend_id":"'+tofriend+'","friendname":"'+tofriendname+'"}'; }else{ data='{"type":"2","user":"'+name+'","msg":"'+msg+'"}'; } ws.send(data); $('#msg').attr("value",''); });
The client is almost like this.
The client has several pitfalls,
Pit 1. If the variable name is name, it will not be reset when the web page is refreshed, otherwise it will be reset. (After checking the information, I found that the name variable is window.name. Therefore, the value will not be refreshed when the web page is refreshed)
Pit 2. JS group array, the variable must use "" and the outermost layer is '' For example: data='{"type":"1","user":"' name '"}'; Otherwise, there will be problems with parsing. Can't do it the other way around!
Server:
The server is mainly the worker component and uses the Channel distributed communication component to implement subscription, cluster push, group push and private chat.
First, of course, is monitoring. Enable a worker's websocket monitoring.
// 创建一个Worker监听2346端口,使用websocket协议通讯 $ws_worker = new Worker("websocket://0.0.0.0:2345"); $channel_server = new Channel\Server('0.0.0.0', 2206); // 启动4个进程对外提供服务 $ws_worker->count = 4; $ws_worker->name="kinmoschat";
When the workerman monitoring is enabled, register the channel communication.
$ws_worker->onWorkerStart=function($ws_worker) { // channel 客户端链接上 服务器 Channel\Client::connect('127.0.0.1',2206); $event_name='私聊'; // 订阅 worker-<id 事件,并注册事件处理函数 Channel\Client::on($event_name,function($event_data)use($ws_worker){ //print_r($event_data); //print_r($ws_worker->connections); $to_connect_id=$event_data['to_connection_id']; $message=$event_data['content']; foreach ($ws_worker->connections as $connection) { if($connection->id==$to_connect_id) { $connection->send($message); } } // if(!isset($ws_worker->connections[$to_connect_id])) // { // echo 'connect is not exist\n'; // return; // } // $to_connection=$ws_worker->connections[$to_connect_id]; // $to_connection->send($message); }); // 订阅广播事件 $event_name = '广播'; // 收到广播 向所有客户端发送消息 Channel\Client::on($event_name,function($event_data)use($ws_worker){ //print_r($event_data); $message=$event_data['content']; foreach ($ws_worker->connections as $connection) { $connection->send($message); } }); };
Register two events, a broadcast event, a private chat event, a broadcast for online notification, and a group message. Private chat is private chat. . Here, you can also do group sending. However, this version has not been implemented yet.
Then there is the callback for the client link.
$ws_worker->onConnect=function($connection){ $connection->id = md5($connection->id."_".time()."_".rand(10000,99999)); };
Here, when the client calls back, I will modify the client's connectid. A simple md5 is mainly to prevent serial IDs from being exploited too easily. .
Then, the main body of the entire project, the processing callback of the server-side message.
For each incoming client, assign a unique ID
Maintain a relationship table with connectid=>user
Because multiple processes are opened, it is saved in the session Invalid, so I plan to save it in the database
When the link is disconnected, delete the data
$ws_worker->onMessage = function($connection, $data) { $res=array('code'=>200, 'msg'=>'ok', 'data'=>null,'type'=>1); // 向客户端发送hello $data //print_r($data); $data=json_decode($data,true); //print_r($data); if(!isset($data['type'])||empty($data['type']))// type 1 2 { $res=array('code'=>301, 'msg'=>'消息包格式错误', 'data'=>null); }else{ switch ($data['type']) { case '1': // 客户端上线消息 //print_r($connection->id); if(!isset($data['user'])||empty($data['user'])) { $res=array('code'=>301, 'msg'=>'消息包格式错误', 'data'=>null); break; } // 维护一个数组 保存 用户 connection_id => user $dsn='mysql:host=127.0.0.1;dbname=kinmoschat;'; $pdo=new PDO($dsn,'root','123456'); //准备SQL语句 $sql = "INSERT INTO `user`(`connect_id`,`username`) VALUES (:connect_id,:username)"; //调用prepare方法准备查询 $stmt = $pdo->prepare($sql); //传递一个数组为预处理查询中的命名参数绑定值,并执行SQL $stmt->execute(array(':connect_id' => $connection->id,':username' => $data['user'])); //获取最后一个插入数据的ID值 //echo $pdo->lastInsertId() . '<br />'; // 向自己推送一条消息 $res2['type']=3;// 系统信息 $res2['data']=array('userinfo' =>$data['user']);// 系统信息 $connection->send(json_encode($res2)); $msg="用户 ".$data['user']." 上线了~~"; $res['data']=$msg; break; case '2': // 客户端群发送消息 if(!isset($data['user'])||empty($data['user'])||!isset($data['msg'])||empty($data['msg'])) { $res=array('code'=>301, 'msg'=>'消息包格式错误', 'data'=>null); break; } $msg="用户 ".$data['user']."说:".$data['msg']; $res['data']=$msg; break; case '3': // 客户端私聊 if(!isset($data['user'])||empty($data['user'])||!isset($data['msg'])||empty($data['msg'])||!isset($data['friend_id'])||empty($data['friend_id'])) { $res=array('code'=>301, 'msg'=>'消息包格式错误', 'data'=>null); break; } $msg="用户 ".$data['user']."对您说:".$data['msg']; $res['data']=$msg; $res['type']=1;// 聊天消息 $res1=json_encode($res); // 推送给单个用户 $event_name = '私聊'; Channel\Client::publish($event_name, array( 'content' => $res1, 'to_connection_id' =>$data['friend_id'] )); // 另外还要给自己推条消息 $msg="您对 ".$data['friendname']."说:".$data['msg']; $res['data']=$msg; $res['type']=1;// 聊天消息 $res2=json_encode($res); Channel\Client::publish($event_name, array( 'content' => $res2, 'to_connection_id' =>$connection->id )); return; break; default: # code... break; } } $res['type']=1;// 聊天消息 $res=json_encode($res); // 广播给所有客户端 $event_name = '广播'; Channel\Client::publish($event_name, array( 'content' => $res )); $dsn='mysql:host=127.0.0.1;dbname=kinmoschat;'; $dbh=new PDO($dsn,'root','123456'); $stmt=$dbh->query('SELECT connect_id,username FROM user'); $row=$stmt->fetchAll(); $uerHtml=""; foreach ($row as $key => $value) { $uerHtml.='<a class="user" onclick="userclick(\''.$value['username'].'\',\''.$value['connect_id'].'\');" value="'.$value['connect_id'].'" href="javascript:void(0);">'.$value['username'].'</a><br/>'; } //print_r($row); $res1['type']=2;// 用户消息 $res1['data']=$uerHtml; $res1=json_encode($res1); $event_name = '广播'; Channel\Client::publish($event_name, array( 'content' => $res1 )); };
The connectid=>name of each user will be stored in the database. .
When an online message is received, it is broadcast to all users.
Received a group message. . Broadcast to all clients.
Received a private message. Then push it individually to yourself and the person who sent it.
Listen to the client closing event. When the client is closed, delete the relevant records in the user table
// 关闭链接 将数据库中的该数据删除 $ws_worker->onClose=function($connection) { //echo 3233; $dsn='mysql:host=127.0.0.1;dbname=kinmoschat;'; $pdo=new PDO($dsn,'root','123456'); $sql="delete from user where connect_id='".$connection->id."'"; //print_r($sql); $pdo->exec($sql); };
For more workerman knowledge, please pay attention to the workerman tutorial column.
The above is the detailed content of How to implement online chat using Workerman. For more information, please follow other related articles on the PHP Chinese website!