[toc]
## :-: **跑官方demo**
常見的協議分3種:
### :-: **1、使用HTTP協議對外提供Web服務**

>[warning]注意:
1、瀏覽器訪問不了,可能是因為端口沒開(或關防火墻)

2、如果你使用curl命令來訪問的話,要在新開的(當前shell標簽右鍵單擊復制ssh渠道)bash中輸入指令
3、開發必讀里(守護進程才用的到重啟。)

\> php 文件名?restart?#重啟php
### :-: **2、使用WebSocket協議對外提供服務**
>[danger]看圖中圈的,客戶端的代碼要改ip

### :-: **3、直接使用TCP傳輸數據**


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服務

- 正確的訪問

- 非本地地址訪問

## :-: **雜記**

- 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參數