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

来源:互联网 发布:import from es6 node 编辑:程序博客网 时间:2024/06/07 00:10

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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

 //存储新登录用户的数据$clients[$ip.':'.$port] = ['ipp'=>$ip.':'.$port,'name'=>$result[1],'conn'=>$connection];
  • 1
  • 2
  • 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";    };};// 运行workerWorker::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
  • 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
  • 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
  • 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";    };};// 运行workerWorker::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
  • 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; //初始为null        var 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+')'); //转json                    var listusers = document.getElementById('listusers');                    listusers.innerHTML = '';//清空                    for(var key in getMsg){                        var option = document.createElement('option');                        option.value = key; //ip                        option.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>
  • 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
  • 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
0 0