WorkerMan学习篇:websocket+workerman聊天功能(三):点对点发送消息模拟

原创 2017年02月14日 16:25:37
  • 2456

1.WorkerMan学习篇:准备和热身 
2.WorkerMan学习篇:连接mysql时到底发生了什么鬼 
3. WorkerMan学习篇:websocket+workerman聊天功能设计(一):简单认证 
4.WorkerMan学习篇:websocket+workerman聊天功能(二):同步在线用户列表

上节课我们已经完成了同步在线用户列表,因为我们是使用IP作为一个唯一判断标志,所以我们的一台电脑只能登录一个用户:

    if(preg_match('/^login:(\w{3,20})/i',$data,$result)){ //代表是客户端认证$ip = $connection->getRemoteIp();if(!array_key_exists($ip,$clients)){ //必须是之前没有注册过
//....
//....
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

实际上,可以在一台电脑上,同时几个客户端来登录,只要保证这个key不唯一就行了,比如说可以拼接上一个端口:

 //存储新登录用户的数据
$clients[$ip.':'.$port] = ['ipp'=>$ip.':'.$port,'name'=>$result[1],'conn'=>$connection];
  • 1
  • 2

 
server端全部代码:

<?php//本机IP是10.211.55.13
//需要监听的端口是 9090use Workerman\Connection\AsyncTcpConnection;
use Workerman\Worker;require 'workerman/Autoloader.php';$clients = []; //保存客户端信息// 创建一个Worker监听9090端口,使用websocket协议通讯
$ws_worker = new Worker("websocket://10.211.55.13:9090");// 启动4个进程对外提供服务
$ws_worker->count = 4;/*** 同步登录用户列表*/
function syncUsers()
{global $clients;$users = 'users:'.json_encode(array_column($clients,'name','ipp')); //准备要广播的数据foreach($clients as $ip=>$client){$client['conn']->send($users);}
}// 当收到客户端发来的数据后
$ws_worker->onMessage = function($connection, $data)
{//这里用global的原因是:php是有作用域的,我们是在onMessage这个回调还是里操作外面的数组//想要改变作用域外面的数组,就global一下global $clients;//验证客户端用户名在3-20个字符if(preg_match('/^login:(\w{3,20})/i',$data,$result)){ //代表是客户端认证$ip = $connection->getRemoteIp();$port = $connection->getRemotePort();if(!array_key_exists($ip.':'.$port, $clients)){ //必须是之前没有注册过//存储新登录用户的数据$clients[$ip.':'.$port] = ['ipp'=>$ip.':'.$port,'name'=>$result[1],'conn'=>$connection];// 向客户端发送数据$connection->send('notice:success'); //验证成功消息$connection->send('msg:welcome '.$result[1]); //普通消息echo $ip .':'.$port.'==>'.$result[1] .'==>login' . PHP_EOL; //这是为了演示,控制台打印信息//有新用户登录//需要同步登录用户数据syncUsers();}}elseif(preg_match('/^msg:(.*?)/isU',$data,$msgset)){ //代表是客户端发送的普通消息if(array_key_exists($connection->getRemoteIp(),$clients)){ //必须是之前验证通过的客户端echo 'get msg:' . $msgset[1] .PHP_EOL; //这是为了演示,控制台打印信息if($msgset[1] == 'nihao'){//如果收到'nihao',就给客户端发送'nihao 用户名'//给客户端发送普通消息$connection->send('msg:nihao '.$clients[$connection->getRemoteIp()]);}}}// 设置连接的onClose回调$connection->onClose = function($connection) //客户端主动关闭{global $clients;unset($clients[$connection->getRemoteIp().':'.$connection->getRemotePort()]);//客户端关闭//即退出登录,也需要更新用户列表数据syncUsers();echo "connection closed\n";};
};// 运行worker
Worker::runAll();
  • 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

点对点聊天简单设计

听起来好像是:利用websocket客户端直接向其他宇宙中的websocket发送消息。

实际上是: 
客户端html——>workerman监听9090端口(自己有个协议,服务端来中转)——–>客户端html

1.客户端构建发送消息,遵守一定的协议

var listusers = document.getElementById('listusers');
var toUserIPP = listusers.options[listusers.selectedIndex].value; //发给用户的ip和端口
var toUserName = listusers.options[listusers.selectedIndex].text; //发给用户的昵称
socket.send('chat:<'+toUserIPP+'>:'+msg);
  • 1
  • 2
  • 3
  • 4

客户端最终发送一条chat:<10.211.55.2:50543>:message 这样的消息给服务端。 
服务端判断并根据ip+端口发送给对应的用户:

if (preg_match('/^chat:\<(.*?)\>:(.*?)/isU',$data,$msgset)){$ipp = $msgset[1];$msg = $msgset[2];if (array_key_exists($ipp,$clients)){ //如果有这个用户//就发送普通消息$clients[$ipp]['conn']->send('msg:'.$msg);echo $ipp.'==>'.$msg.PHP_EOL;}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

服务端全部代码:

<?php//本机IP是10.211.55.13
//需要监听的端口是 9090use Workerman\Connection\AsyncTcpConnection;
use Workerman\Worker;require 'workerman/Autoloader.php';$clients = []; //保存客户端信息// 创建一个Worker监听9090端口,使用websocket协议通讯
$ws_worker = new Worker("websocket://10.211.55.13:9090");// 启动4个进程对外提供服务
$ws_worker->count = 4;/*** 同步登录用户列表*/
function syncUsers()
{global $clients;$users = 'users:'.json_encode(array_column($clients,'name','ipp')); //准备要广播的数据foreach($clients as $ip=>$client){$client['conn']->send($users);}
}// 当收到客户端发来的数据后
$ws_worker->onMessage = function($connection, $data)
{//这里用global的原因是:php是有作用域的,我们是在onMessage这个回调还是里操作外面的数组//想要改变作用域外面的数组,就global一下global $clients;//验证客户端用户名在3-20个字符if(preg_match('/^login:(\w{3,20})/i',$data,$result)){ //代表是客户端认证$ip = $connection->getRemoteIp();$port = $connection->getRemotePort();if(!array_key_exists($ip.':'.$port, $clients)){ //必须是之前没有注册过//存储新登录用户的数据$clients[$ip.':'.$port] = ['ipp'=>$ip.':'.$port,'name'=>$result[1],'conn'=>$connection];// 向客户端发送数据$connection->send('notice:success'); //验证成功消息$connection->send('msg:welcome '.$result[1]); //普通消息echo $ip .':'.$port.'==>'.$result[1] .'==>login' . PHP_EOL; //这是为了演示,控制台打印信息//有新用户登录//需要同步登录用户数据syncUsers();}}elseif(preg_match('/^msg:(.*?)/isU',$data,$msgset)){ //代表是客户端发送的普通消息if(array_key_exists($connection->getRemoteIp(),$clients)){ //必须是之前验证通过的客户端echo 'get msg:' . $msgset[1] .PHP_EOL; //这是为了演示,控制台打印信息if($msgset[1] == 'nihao'){//如果收到'nihao',就给客户端发送'nihao 用户名'//给客户端发送普通消息$connection->send('msg:nihao '.$clients[$connection->getRemoteIp()]);}}}elseif (preg_match('/^chat:\<(.*?)\>:(.*?)/isU',$data,$msgset)){$ipp = $msgset[1];$msg = $msgset[2];if (array_key_exists($ipp,$clients)){ //如果有这个用户//就发送普通消息$clients[$ipp]['conn']->send('msg:'.$msg);echo $ipp.'==>'.$msg.PHP_EOL;}}// 设置连接的onClose回调$connection->onClose = function($connection) //客户端主动关闭{global $clients;unset($clients[$connection->getRemoteIp().':'.$connection->getRemotePort()]);//客户端关闭//即退出登录,也需要更新用户列表数据syncUsers();echo "connection closed\n";};
};// 运行worker
Worker::runAll();
  • 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

客户端全部代码:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>WebSocket_client</title><script>//创建一个socket实例var socket = null; //初始为nullvar isLogin = false; //是否登录到服务器上//定义一个连服务的函数function connectServer(){var username = document.getElementById('username').value;if (username == ''){alert('用户昵称必填');}socket = new WebSocket("ws://10.211.55.13:9090");socket.onopen = function() {socket.send('login:' + username);};socket.onmessage = function(e) {var getMsg = e.data;if(/^notice:success$/.test(getMsg)){ //服务器验证通过isLogin = true;}else if(/^msg:/.test(getMsg)){ //代表是普通消息console.log(getMsg);var p = document.createElement('p');p.innerHTML = '<span>收到消息:</span>' + getMsg.replace('msg:','');document.getElementById('txtcontent').appendChild(p);}else if(/^users:/.test(getMsg)){ //显示当前已登录用户console.log(getMsg);getMsg = getMsg.replace('users:','');getMsg= eval('('+getMsg+')'); //转jsonvar listusers = document.getElementById('listusers');listusers.innerHTML = '';//清空for(var key in getMsg){var option = document.createElement('option');option.value = key; //ipoption.innerHTML = getMsg[key]; //昵称listusers.appendChild(option); //添加元素进去}}};socket.onclose = function(){isLogin = false;}}//发送消息function send(){if (!isLogin){alert('请先通过服务器验证');}var msg = document.getElementById('txtmsg').value;//console.log(msg);socket.send('msg:' + msg); //发送消息到服务端var listusers = document.getElementById('listusers');var toUserIPP = listusers.options[listusers.selectedIndex].value; //发给用户的ip和端口var toUserName = listusers.options[listusers.selectedIndex].text; //发给用户的昵称socket.send('chat:<'+toUserIPP+'>:'+msg);//chat:<10.211.55.2:50543>:message//显示我们的消息到div中var p = document.createElement('p');p.innerHTML = '<span>发送消息给:['+toUserName+']:</span>' + msg;document.getElementById('txtcontent').appendChild(p);}</script>
</head>
<body><div id="txtcontent" style="width: 500px;height: 250px;border: 1px solid gray"></div><div>所有用户:<select id="listusers"></select></div><div>你的昵称:<input type="text" id="username" /></div><div>回复内容:<textarea style="width: 500px;height: 100px" id="txtmsg"></textarea></div><div><button onclick="connectServer()">连接服务器</button><button onclick="send()">发送消息</button></div>
</body>
</html>

WorkMan学习篇:三相关推荐

  1. HBase 表结构 学习篇 (三)

    HBase 它是建立在Hadoop文件系统(HDFS)之上的分布式面向列的数据库.提供对数据的随机实时读/写访问.在表中由行排序,表模式定义只能是列族,也就是键值对.一个表格有多个列族以及每个列族可以 ...

  2. MaterialDesign学习篇(三),AppBarLayout、CollapsingToolbarLayout的使用

    什么是AppBarLayout AppBarLayout继承自LinearLayout,子控件默认为竖直方向显示,可以用它实现Material Design的Toolbar:它支持滑动手势:它的子控件 ...

  3. MODBUS学习篇三

    六.modbus实现主机对从设备读取数据包和从设备写入数据包      1.准备好硬件上能精确到1ms的定时器      2. 先完成一个MODBUS所需要的软件上的定时器 void Timer2_I ...

  4. MaterialDesign学习篇(二),Toolbar、DrawerLayout的使用

    什么是Toolbar Toolbar是应用的内容的标准工具栏,可以说是Actionbar的升级版,两者不是独立关系,要使用Toolbar还是得跟ActionBar扯上关系的.相比Actionbar,T ...

  5. Python学习篇(五) Python中的循环

    文章目录 前言 一.range函数 二.while循环 2.1四步循环法 三.for in 循环 四.流程控制语句 4.1 break 4.2 continue 五.else语句 六.嵌套循环 七.二 ...

  6. Python深度学习篇

    Python深度学习篇一<什么是深度学习> Excerpt 在过去的几年里,人工智能(AI)一直是媒体大肆炒作的热点话题.机器学习.深度学习 和人工智能都出现在不计其数的文章中,而这些文章 ...

  7. 拒绝从入门到放弃_《鸟哥的 Linux 私房菜 — 基础学习篇(第三版)》必读目录

    目录 目录 前言 关于这本书 必看知识点 最后 前言 相信部分刚进入这个行业的新同学会对一个问题感到疑惑,为什么从培训学校出来的学员不被欢迎? 这里记录下一些我个人的看法(博主也曾有面试新员工的经历) ...

  8. homeassistant mysql_学习笔记 篇三:HomeAssistant学习笔记docker安装的ha更换数据库

    学习笔记 篇三:HomeAssistant学习笔记docker安装的ha更换数据库 2018-11-15 12:06:58 4点赞 18收藏 3评论 是返乡过年?还是就地过年?最新一届#双面过节指南# ...

  9. 我在Salira的800天(2009.5.20~2011.7.29)-三.研究与学习篇

    本博客(http://blog.csdn.net/livelylittlefish )贴出作者(阿波)相关研究.学习内容所做的笔记,欢迎广大朋友指正! Content 零.序 一.感谢篇 二.工作篇 ...

  10. 树莓派可以移动linux,树莓派学习笔记 篇三:树莓派4B 与移动存储设备的那些事儿...

    树莓派学习笔记 篇三:树莓派4B 与移动存储设备的那些事儿 2019-11-24 23:03:55 39点赞 286收藏 14评论 本文是「树莓派学习笔记」系列的第三篇,将学习下 Linux 系统分区 ...

最新文章

  1. Linux内核源码结构
  2. virtio驱动_0020 virtio-blk简易驱动
  3. 微服务软件架构的认识和设计模式
  4. Redis的安装与简单部署
  5. java 获取注释_Java面试题Java语言有哪些注释的方式?
  6. wingdows安装psutil_psutil模块安装指南(win与linux)
  7. 嵌入式linux图形系统设计,轻量级嵌入式Linux图形系统设计与实现
  8. 前端学习(588):console面板简介与交互式命令
  9. sort和qsort函数
  10. 链表简单实现(增删查改)
  11. 力扣455. 分发饼干(JavaScript)
  12. 机器学习(2)——K-近邻算法讲解
  13. 世界顶级黑市拳赛内幕
  14. 零基础入门渗透测试教程
  15. 关于程序的入口函数(main _start...)
  16. Remix OS——一个很有魅力的Android系统
  17. PG+POSTGIS地图空间位置网格聚合算法
  18. 云主机试用,云主机最高14天试用
  19. CentOS 打开3306端口
  20. linux内核zfs,Linus Torvalds回应用户抱怨:不建议使用 ZFS On Linux

热门文章

  1. 关于ajax同步状态及sucess,complete的顺序的理解
  2. 算法岗实习面试经历(机器学习/强化学习岗实习生)
  3. SMILES Enumeration
  4. IDEA背景色和背景图片的设置
  5. LIO-SAM后端中的回环检测及位姿计算
  6. SQL Sever——远程过程调用失败(0x800706be)
  7. 金蝶kis修改服务器,金蝶kis 修改服务器地址
  8. Credential Harvester的脚本修改
  9. 正交编码 正交编码器 增量式编码器
  10. 中国移动SP短信网关接入平台