多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
[toc] ## :-: **跑官方demo** 常见的协议分3种: ### :-: **1、使用HTTP协议对外提供Web服务** ![](https://box.kancloud.cn/f39fa24250edf2a6ef051cced2c330ce_1196x645.png) >[warning]注意: 1、浏览器访问不了,可能是因为端口没开(或关防火墙) ![](https://box.kancloud.cn/b3b07c8e65e95b0443df2ede77a322b7_994x223.png) 2、如果你使用curl命令来访问的话,要在新开的(当前shell标签右键单击复制ssh渠道)bash中输入指令 3、开发必读里(守护进程才用的到重启。) ![](https://box.kancloud.cn/d7d1de3ac926fcc09e2f6c2faf2a1483_494x57.png) \> php 文件名 restart #重启php ### :-: **2、使用WebSocket协议对外提供服务** >[danger]看图中圈的,客户端的代码要改ip ![](https://box.kancloud.cn/6293f5cae0ce28a3b5df6e4436ebbeb4_490x219.png) ### :-: **3、直接使用TCP传输数据** ![](https://box.kancloud.cn/2b06ac720527c35ff06e1ae94385f07a_495x427.png) ![](https://box.kancloud.cn/7bcfc5a0cbb5ef3db82f3646c8f83e36_602x110.png) Ctrl + \]  #关闭telnet 在输入quit >[warning]如果报错说,没有telnet,可参考https://www.cnblogs.com/ikai/p/7073201.html #安装telnet ## :-: **示例代码,简单聊天室** >[info]此处使用的是,WebSocket协议对外提供服务 - 建议学习之前,先学下websocket。可参考本书路径:/前端常用知识点/其他/websocket - 首先抛开别的不谈,咱先捋明白业务逻辑,不然看了代码也是懵逼 - 既然做聊天室(说白了就是类似QQ,一对一聊天) - 1、聊天首先要有昵称(花名) - 2、必须登录后才能聊天 - 3、聊天还要找到对方(我们开发肯定是找ip,而不是和聊QQ去根据名字找好友)后在发送消息 ### :-: **思路** >[info]总思路:客户端发给服务端,服务端处理完,js还要解析显示在html上 1. 认证服务器(登录) - 客户端发送登录消息 - 服务端处理 - 使用正则判断,截取用户名 - 将登录用户的ip和昵称,保存起来,以后做判断用 - 解析完返回消息(客户端标识登录) 2. 发消息 - 客户端发送普通消息 - 并将客户端发送的消息,显示出来 - 服务端处理 - 使用正则判断,截取用户名 - 判断发消息的客户端,是否通过服务器认证(是否登录了) - 通过认证的,才可以往客户端发送消息 - 客户端解析服务端返回消息 - 将解析好的信息,显示出来 3. 广播,显示昵称 - 服务端 - 拼接要返回的数据,转成json - 遍历,拿当前和服务器连接的客户端,发消息给客户端 - 客户端 - 解析服务端返回的数据,遍历将他显示在昵称列表上 4. 单播,一对一聊天 - 客户端 - 通过选择用户昵称 - 将消息发给某个用户 - 服务端,通过服务端转发 - 解析发过来的数据,取接收方的ip和要发的消息 - 判断接收方是否登录了,登录才可发送 - 使用接收方的$connection对象发送数据 - 客户端 - 解析服务端发过来的数据 - 将其在接收方显示出来 5. 关闭客户端 - 在数组中删除已经登录的用户 6. 关闭服务端 - 将登录标识置为false >[info]以下是写这个例子我参考的优质博客,大家可以参考 - http://blog.csdn.net/github_26672553/article/details/54932788 - http://blog.csdn.net/github_26672553/article/details/54946302 - http://blog.csdn.net/github_26672553/article/details/55098197 ### :-: **客户端代码** ~~~ html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div id="txtcontent" style="width: 500px;height: 250px;border: 1px solid gray"></div> <div>所有用户:<select id="listuers"></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> <script> var socket = null; //将socket实例保存到变量中 var isLogin = false; //登录标识符 //1、链接服务端 function connectServer(){ var username = document.getElementById('username').value; //如果用户名为空 if(username == ''){ alert('用户昵称不能为空'); } //2、实例化 socket = new WebSocket('ws://47.94.21.171:2000'); //3、打开的时候,发送消息 socket.onopen = function(){ socket.send('login:' + username); //谁登录了 }; //4、接收服务器返回的数据 socket.onmessage = function(e){ //console.log(e); var getMsg = e.data; //返回的数据 //解析getMsg。思路:通过正则匹配,显示出来 if(/^notice:success$/.test(getMsg)){ //服务端验证通过 isLogin = true; }else if(/^msg:/.test(getMsg)){ //将服务端返回的普通消息显示出来 var p = document.createElement('P'); //将msg:替换为空 p.innerHTML = '<span>服务端返回的消息:</span>' + getMsg.replace('msg:',''); document.getElementById('txtcontent').appendChild(p); //追加节点 }else if(/^users:/.test(getMsg)){ //广播(将所有用户昵称显示出来) //console.log(getMsg); nicheng = getMsg.replace('users:',''); //{"61.144.116.123":"yangxi"} shownicheng = eval('('+nicheng+')'); //转json var listusers = document.getElementById('listuers'); listusers.innerHTML = ''; //先清空 for(var key in shownicheng){ var option = document.createElement('option'); option.value = key; //ip option.innerHTML = shownicheng[key]; //将昵称填充进去 listusers.appendChild(option); //追加节点 } }else if(/^dian:/.test(getMsg)){ //单播(点对点发消息) //console.log(getMsg); var p = document.createElement('P'); //创建节点 //将msg:替换为空 p.innerHTML = '<span>单播的消息:</span>' + getMsg.replace('dian:',''); document.getElementById('txtcontent').appendChild(p); //追加节点 } }; //5、服务端关闭 socket.onclose = function(){ isLogin = false; alert('服务器断开'); }; } //发送消息按钮 function send(){ if(!isLogin){ alert('请先通过服务器验证'); } //获取要发送的内容 var msg = document.getElementById('txtmsg').value; //发送消息给服务端 socket.send('msg:' + msg); //单聊,点对点发送消息 var listusers = document.getElementById('listuers'); var toUserIPP = listusers.options[listusers.selectedIndex].value; //收消息的ip var toUserName = listusers.options[listusers.selectedIndex].text; //收消息的昵称 socket.send('dian:<' + toUserIPP + '>:' + msg); //将发出的普通消息显示出来 var p = document.createElement('p'); p.innerHTML = '<span>客户端发出的消息:</span>' + msg; document.getElementById('txtcontent').appendChild(p); } </script> </body> </html> ~~~ ### :-: **服务端代码** ~~~ php <?php /** * 注释最全,跑码版 * User: Administrator * Date: 2018/3/12 * Time: 10:48 */ use Workerman\Connection\AsyncTcpConnection; use Workerman\Worker; require '../Workerman/Autoloader.php'; $clients = []; //保存客户端信息 //1、创建一个worker监听,使用websocket协议 $ws_worker = new Worker('websocket://0.0.0.0:2000'); //开启4个进程 $ws_worker->count = 4; //2、接收客户端发来的数据 $ws_worker->onMessage = function($connection,$data){ global $clients; //2.1、用户点击的是(链接服务器)验证客户端 if(preg_match('/^login:(\w{3,20})/i',$data,$result)){ $ip = $connection->getRemoteIp(); //获取当前客户端IP $port = $connection->getRemotePort(); //获取当前客户端端口 if(!array_key_exists($ip,$clients)){ //判断该ip是否登录过 //$clients[$ip] = $result[1]; //新登录的ip保存起来 $clients[$ip.':'.$port] = ['ipp'=>$ip.':'.$port,'name'=>$result[1],'conn'=>$connection]; //广播的话,不止保存昵称,还要保存ip和当前和服务器连接的那个客户端 //将处理完的信息返回给客户端(给客户端发送任意消息) $connection->send('notice:success'); $connection->send('msg:你好'.$result[1]); echo $ip . ':'.$port . '--------' .$result[1] . 'login' . PHP_EOL; //打印看结果 //一旦有用户登录,就把保存的客户端信息发过去(显示出所有用户) //$connection->send('users:'.json_encode($clients)); //广播(群聊) $users = 'users:'.json_encode(array_column($clients,'name','ipp')); //返回数组中指定的列 foreach($clients as $ip=>$client){ //拿当前和服务器连接的那个客户端,发送消息 $client['conn']->send($users); } } }else if(preg_match('/^msg:(.*?)/isU',$data,$megset)){ //2.2、处理发来的普通消息 if(array_key_exists($connection->getRemoteIp(),$clients)){ //判断该ip是否存在,存在就是已经登录的 echo '用户:' . $connection->getRemoteIp() . '发的消息是' . $megset[1] . PHP_EOL; if($megset[1] == 'nihao'){ $connection->send('msg:nihao'.$clients[$connection->getRemoteIp()]); } //我认为广播应该在这些,将用户A说的话,显示到页面上,让所有用户都能看见 } }else if(preg_match('/^dian:\<(.*?)\>:(.*?)/isU',$data,$meg)){ //单播,点对点发消息 $ipp = $meg[1]; //接收消息用户的ip $msg = $meg[2]; //发送的数据 $name = $clients[$ipp]['name']; echo "<pre>"; var_dump($name); if(array_key_exists($ipp,$clients)){ //接收的ip也登录了,也就是有这个用户 $clients[$ipp]['conn']->send('dian:'.$msg); echo $ipp.'==>'.$msg.PHP_EOL; } } }; //客户端关闭 $ws_worker->onClose = function($connection){ global $clients; //echo $clients[$connection->getRemoteIp()].'客户端已断开'.PHP_EOL; unset($clients[$connection->getRemoteIp()]); }; //3、运行 Worker::runAll(); ~~~ ## :-: **其他示例** >[info]示例:黑/白名单访问 ~~~ php <?php require_once __DIR__.'/Workerman/Autoloader.php'; use Workerman\Worker; $worker = new Worker('tcp://0.0.0.0:8085'); // 连接回调 $worker->onConnect = function ($connection){ // IP 白名单验证 if($connection->getRemoteIP() != '127.0.0.1'){ $connection->close("IP Address Forbidden"); } }; // 接受发送消息 $worker->onMessage = function ($conn,$data){ $conn->send("Hello World"); }; // 关闭连接 $worker->onClose = function ($connection){ echo "connection close \n"; }; $worker::runAll(); ~~~ - 开启Workerman服务 ![](https://box.kancloud.cn/d8997ec60e45b9ea5e072b97d90484ca_423x130.png) - 正确的访问 ![](https://box.kancloud.cn/0c94aceb35808b545f11cfbb87cec378_488x87.png) - 非本地地址访问 ![](https://box.kancloud.cn/a4555920cd90b7ecdd4bb1e4e5e9ef23_393x70.png) ## :-: **杂记** ![](https://box.kancloud.cn/3dd490bdaff03238a1a121ac11b7ce09_1437x348.png) - Worker是容器,监听特定端口 - 当客户端连接到这个端口,会在容器内部产生一个connection对象 - Worker容器,可能有很多个connection对象 - 通过操作connection对象向客户端发送和接收数据等操作 - 有2个connection类: - TcpConnection类(连接类的基类);客户端连接上之后自动产生的connection对象; - AsyncTcpConnection(是TcpConnection的子类);当我们<span style="color:red;">在workerman之后需要访问一个web服务</span>,可以通过这个类异步的发起一个http链接,去链接远程的服务端,异步的通讯;该类是客户端连接其他服务端所用到的类