[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链接,去链接远程的服务端,异步的通讯;该类是客户端连接其他服务端所用到的类
- 杂谈
- 开发 & 维护的工作流程
- 新手如何看php手册 和 框架手册
- 开发 & 维护的不同点
- 从0到1,搭建新项目的工作流程
- 从1到N,维护的工作流程
- 优化流程
- 生成错误日志和慢日志的方法
- 查错思路
- 怎么快速接手一个项目
- 前端常用知识点
- javascript
- 自己封装的函数
- 处理数字
- 功能代码
- 动态添加图片
- 判断是手机端还是pc端
- javascript:;是什么意思?怎么用呢
- html & h5
- a标签中target设置为blank和_blank有什么区别?
- 乱码
- 提交方式:button标签 和 input
- 块元素
- 内联元素
- h5特有属性
- h5的localStorage【增、删、改、查】
- jquery
- 常用方法
- 功能代码
- 动态删除图片
- 一个按钮,切换2种状态
- 换肤
- 深入理解(function(){... })();
- json & xml
- json
- 语法速记
- json对象取值
- 字符串、对象、数组的区别
- xml
- [CDATA[%s]]的作用是什么
- 转义字符
- CDATA 想被xml解析的文本数据
- CDATA 不想被xml解析的文本数据
- 微信小程序
- 其他
- websocket
- 跨域
- css
- 行内 & 内连 & 外连 写法
- 优先级
- 更加精准的匹配
- 使用百分比如何生效
- php在html、js、jq中的的原生写法
- *php在html中的语法
- php在js中的语法
- php在jq中的语法
- 正则表达式
- php常用基础知识(思想为主)
- php为什么是“边编译边运行”
- 冒号、endif、endwhile、endfor使用
- 递归思想(速记法)
- cookie和session的理解
- php常用内置(系统)函数
- 常量
- 字符串
- 数组
- 日期时间
- 文件 & 目录
- 数学
- 程序执行
- 判断
- 选项和信息(修改配置文件的)
- 错误处理 & 日志记录
- 编码格式
- session
- IP相关
- 类 & 对象
- 性能
- 其他函数
- 魔术方法
- $_SERVER
- 变量处理
- php自己封装的一些函数
- 导入、导出、生成文件
- 数组
- 数字
- 字符串
- 其他
- 获取linux硬件信息
- 常见插件/类库使用
- 前端-框架/插件
- bootstrap 学习笔记
- layer 学习笔记
- layDate 学习笔记
- 百度ueditor1.4.4.3富文本编辑器
- quill富文本编辑器
- 百度ECharts图形报表
- webuploader上传图片
- 后端类库
- workerman 聊天室
- QRCODE 二维码
- redis
- seaslog 日志
- phpspider 爬虫
- Mailer 发送邮件
- simple_html_dom
- phpstorm使用
- 快捷键
- 连接mysql数据库
- 断点 + debug调试
- 运行内存不够
- wamp环境
- yii、laravel、tp、开发自己的php框架
- 看框架源码的思路
- tp5框架的使用
- 1、助手函数原理解析
- 开发自己的php框架
- 常用的开发思路 和 小功能实现代码
- 爬虫思路
- 功能点思路
- tp5判断是不是异地登录(简单版)
- 微信开发,反向代理
- 微信开发,关闭当前页面
- 消息队列的实现
- 页面静态化
- session串号
- 站内信设计思路
- web在线管理器
- 语言相关(开发有关)
- 接收json(text/xml)格式数据
- 原生文件上传(状态码)
- openssl扩展
- 打印对象 和 遍历对象
- 使用OB缓存的几个原则
- CLI模式执行php文件
- foreach时,添加元素 或 修改元素的值
- 功能点 代码实现
- 生成url目录树(没有pid)
- 多图上传(vue传base64)
- 下载文件,耗时算法
- 生成商品二维码
- 导出excel
- 搜索
- 阿里大鱼发短信
- 使用阿里云oss
- location.href跳转后,丢失用户的session
- “\r ” “\r\n” “\t”的区别
- php的配置文件详解
- 开启错误日志
- 开启慢日志
- 开启短标签
- 分析php-fpm.conf中的request_terminate_timeout参数