# listen
```php
void Worker::listen(void)
```
用于實例化Worker后執行監聽。
此方法主要用于在Worker進程啟動后動態創建新的Worker實例,能夠實現同一個進程監聽多個端口,支持多種協議。
例如一個http Worker啟動后實例化一個websocket Worker,那么這個進程即能通過http協議訪問,又能通過websocket協議訪問。由于websocket Worker和http Worker在同一個進程中,所以它們可以訪問共同的內存變量,共享所有socket連接。可以做到接收http請求,然后操作websocket客戶端完成向客戶端推送數據類似的效果。
**注意:**
如果PHP版本<=7.0,則不支持在多個子進程中實例化相同端口的Worker。例如A進程創建了監聽2016端口的Worker,那么B進程就不能再創建監聽2016端口的Worker,否則會報```Address already in use```錯誤。例如下面的代碼是```無法```運行的。
```php
use Workerman\Worker;
require_once __DIR__ . '/Workerman/Autoloader.php';
$worker = new Worker();
// 4個進程
$worker->count = 4;
// 每個進程啟動后在當前進程新增一個Worker監聽
$worker->onWorkerStart = function($worker)
{
/**
* 4個進程啟動的時候都創建2016端口的Worker
* 當執行到worker->listen()時會報Address already in use錯誤
* 如果worker->count=1則不會報錯
*/
$inner_worker = new Worker('http://0.0.0.0:2016');
$inner_worker->onMessage = 'on_message';
// 執行監聽。這里會報Address already in use錯誤
$inner_worker->listen();
};
$worker->onMessage = 'on_message';
function on_message($connection, $data)
{
$connection->send("hello\n");
}
// 運行worker
Worker::runAll();
```
如果您的PHP版本>=7.0,可以設置Worker->reusePort=true, 這樣可以做到多個子進程創建相同端口的Worker。見下面的例子:
```php
use Workerman\Worker;
require_once './Workerman/Autoloader.php';
$worker = new Worker('text://0.0.0.0:2015');
// 4個進程
$worker->count = 4;
// 每個進程啟動后在當前進程新增一個Worker監聽
$worker->onWorkerStart = function($worker)
{
$inner_worker = new Worker('http://0.0.0.0:2016');
// 設置端口復用,可以創建監聽相同端口的Worker(需要PHP>=7.0)
$inner_worker->reusePort = true;
$inner_worker->onMessage = 'on_message';
// 執行監聽。正常監聽不會報錯
$inner_worker->listen();
};
$worker->onMessage = 'on_message';
function on_message($connection, $data)
{
$connection->send("hello\n");
}
// 運行worker
Worker::runAll();
```
### 示例 php后端及時推送消息給客戶端
**原理:**
1、建立一個websocket Worker,用來維持客戶端長連接
2、websocket Worker內部建立一個text Worker
3、websocket Worker 與 text Worker是同一個進程,可以方便的共享客戶端連接
4、某個獨立的php后臺系統通過text協議與text Worker通訊
5、text Worker操作websocket連接完成數據推送
**代碼及步驟**
push.php
```php
<?php
use Workerman\Worker;
require_once './Workerman/Autoloader.php';
// 初始化一個worker容器,監聽1234端口
$worker = new Worker('websocket://0.0.0.0:1234');
/*
* 注意這里進程數必須設置為1,否則會報端口占用錯誤
* (php 7可以設置進程數大于1,前提是$inner_text_worker->reusePort=true)
*/
$worker->count = 1;
// worker進程啟動后創建一個text Worker以便打開一個內部通訊端口
$worker->onWorkerStart = function($worker)
{
// 開啟一個內部端口,方便內部系統推送數據,Text協議格式 文本+換行符
$inner_text_worker = new Worker('text://0.0.0.0:5678');
$inner_text_worker->onMessage = function($connection, $buffer)
{
// $data數組格式,里面有uid,表示向那個uid的頁面推送數據
$data = json_decode($buffer, true);
$uid = $data['uid'];
// 通過workerman,向uid的頁面推送數據
$ret = sendMessageByUid($uid, $buffer);
// 返回推送結果
$connection->send($ret ? 'ok' : 'fail');
};
// ## 執行監聽 ##
$inner_text_worker->listen();
};
// 新增加一個屬性,用來保存uid到connection的映射
$worker->uidConnections = array();
// 當有客戶端發來消息時執行的回調函數
$worker->onMessage = function($connection, $data)
{
global $worker;
// 判斷當前客戶端是否已經驗證,既是否設置了uid
if(!isset($connection->uid))
{
// 沒驗證的話把第一個包當做uid(這里為了方便演示,沒做真正的驗證)
$connection->uid = $data;
/* 保存uid到connection的映射,這樣可以方便的通過uid查找connection,
* 實現針對特定uid推送數據
*/
$worker->uidConnections[$connection->uid] = $connection;
return;
}
};
// 當有客戶端連接斷開時
$worker->onClose = function($connection)
{
global $worker;
if(isset($connection->uid))
{
// 連接斷開時刪除映射
unset($worker->uidConnections[$connection->uid]);
}
};
// 向所有驗證的用戶推送數據
function broadcast($message)
{
global $worker;
foreach($worker->uidConnections as $connection)
{
$connection->send($message);
}
}
// 針對uid推送數據
function sendMessageByUid($uid, $message)
{
global $worker;
if(isset($worker->uidConnections[$uid]))
{
$connection = $worker->uidConnections[$uid];
$connection->send($message);
return true;
}
return false;
}
// 運行所有的worker
Worker::runAll();
```
啟動后端服務
```php push.php start -d```
前端接收推送的js代碼
```php
var ws = new WebSocket('ws://127.0.0.1:1234');
ws.onopen = function(){
var uid = 'uid1';
ws.send(uid);
};
ws.onmessage = function(e){
alert(e.data);
};
```
后端推送消息的代碼
```php
// 建立socket連接到內部推送端口
$client = stream_socket_client('tcp://127.0.0.1:5678', $errno, $errmsg, 1);
// 推送的數據,包含uid字段,表示是給這個uid推送
$data = array('uid'=>'uid1', 'percent'=>'88%');
// 發送數據,注意5678端口是Text協議的端口,Text協議需要在數據末尾加上換行符
fwrite($client, json_encode($data)."\n");
// 讀取推送結果
echo fread($client, 8192);
```
- 序言
- 原理
- 開發必讀
- 入門指引
- 特性
- 簡單的開發示例
- 安裝
- 環境要求
- 下載安裝
- 啟動停止
- 開發流程
- 開發前必讀
- 目錄結構
- 開發規范
- 基本流程
- 通訊協議
- 通訊協議作用
- 定制通訊協議
- 一些例子
- Worker類
- 構造函數
- 屬性
- id
- count
- name
- protocol
- transport
- reusePort
- connections
- stdoutFile
- pidFile
- logFile
- user
- reloadable
- daemonize
- globalEvent
- 回調屬性
- onWorkerStart
- onWorkerReload
- onConnect
- onMessage
- onClose
- onBufferFull
- onBufferDrain
- onError
- 接口
- runAll
- stopAll
- listen
- TcpConnection類
- 屬性
- id
- protocol
- worker
- maxSendBufferSize
- defaultMaxSendBufferSize
- maxPackageSize
- 回調屬性
- onMessage
- onClose
- onBufferFull
- onBufferDrain
- onError
- 接口
- send
- getRemoteIp
- getRemotePort
- close
- destroy
- pauseRecv
- resumeRecv
- pipe
- AsyncTcpConnection類
- 構造函數
- connect
- reconnect
- transport
- Timer定時器類
- add
- del
- 定時器注意事項
- WebServer
- 調試
- 基本調試
- 查看運行狀態
- 網絡抓包
- 跟蹤系統調用
- 常用組件
- GlobalData數據共享組件
- GlobalDataServer
- GlobalDataClient
- Channel分布式通訊組件
- ChannelServer
- channelClient
- 例子-集群推送
- 例子-分組發送
- FileMonitor文件監控組件
- MySQL組件
- workerman/mysql
- swoole/mysql(異步)
- redis組件
- swoole/redis
- 異步http組件
- swoole/http-client
- 異步消息隊列組件
- react/zmq
- react/stomp
- 異步dns組件
- swoole/dns
- 常見問題
- 心跳
- 客戶端鏈接失敗原因
- 是否支持多線程
- 與其它框架整合
- 運行多個workerman
- 支持哪些協議
- 如何設置進程數
- 查看客戶端連接數
- 對象和資源的持久化
- 例子無法工作
- 啟動失敗
- 停止失敗
- 支持多少并發
- 更改代碼不生效
- 向指定客戶端發送數據
- 如何主動推送消息
- 在其它項目中推送
- 如何實現異步任務
- status里send_fail的原因
- Windows下開發Linux下部署
- 是否支持socket.io
- 終端關閉導致workerman關閉
- 與nginx apache的關系
- 禁用函數檢查
- 平滑重啟原理
- 為Flash開843端口
- 如何廣播數據
- 如何建立udp服務
- 監聽ipv6
- 關閉未認證的鏈接
- 傳輸加密-ssl/tsl
- 創建wss服務
- 創建https服務
- workerman作為客戶端
- 作為ws/wss客戶端
- PHP的幾種回調寫法
- 附錄
- Linux內核調優
- 壓力測試
- 安裝擴展
- websocket協議
- ws協議
- text協議
- frame協議
- 不支持的函數/特性
- 版權信息