<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                [分享: 使用workerman實現基于UDP的異步SIP服務器,服務器端可主動發送UDP數據給客戶端-workerman社區](https://www.workerman.net/a/1656) 自從使用workerman實現物聯網終端接入以來,我工作中的所有網絡場景(TCP\\UDP\\HTTP)等均使用workerman+channel以微服務方式實現,開發速度快,性能超級高。(幾十萬臺設備同時接入都輕輕松松承受住) 之前多次關注過workerman的UDP服務器,但一沒有實現我想要的結果 由于近期的業務需求,外加HTTP3 QUIC協議的廣泛使用,workerman作為一個廣泛使用的高性能PHP網絡開發框架,支持持久化的UDP通信是很有必要的。 一直以來想通過workerman編寫個基于UDP的SIP服務器和實現GB28181的國標協議,搭配SRS、ZLMediaKit或者monibuca,滿足攝像頭、硬盤錄像機設備的接入,也可配合FreeSwitch實現基于SIP的語音通話或視頻會議系統 workerman 主動發送udp數據 `https://www.workerman.net/q/2688` UDP服務器主動向客戶端發送消息 `https://www.workerman.net/q/4284` 直到今天終于使用workerman 實現單進程或多進程方式監聽某個UDP端口,主動從平臺向客戶端發送數據 并且所有功能均使用workerman的loop功能,能夠發揮平臺最大化性能 當進程只有一個時使用 socket 函數實現端口監聽,當進程大于1個時使用stream\_socket實現端口監聽(各有利弊,請酌情使用,大部分場景,推薦將進程數保持與CPU數量一致,自動使用 stream\_socket ) 經過我初步測試: 當使用stream\_socket時,服務器首次收到客戶端發送的數據后,能夠穩定的向客戶端發送約5分鐘的數據報文,直到該通信會話被Linux內核丟棄,因此使用UDP進行通信,**建議至少60秒進行一次雙向心跳交互保活**。 當使用socket時,服務器首次收到客戶端發送的數據后,能夠穩定的向客戶端長期發送數據報文(如果網絡中的防火墻或NAT路由器沒有將會話過期,應該可以一直使用) 下面直接發布代碼 ~~~php <?php chdir(dirname($_SERVER['SCRIPT_FILENAME'])); include_once __DIR__ . '/vendor/autoload.php'; use Workerman\Worker; use Workerman\Lib\Timer; $worker = new Worker(); $worker->count = 1; //開啟進程數量 $processName = "sip_server_udp5060"; $worker->transport = "udp"; $worker->name = $processName; $worker->reusePort = true; //開啟均衡負載模式 $date = date("Y-m-d"); Worker::$pidFile = "var/{$processName}.pid"; Worker::$logFile = "var/{$processName}_logFile.log"; Worker::$stdoutFile = "var/{$processName}_stdout.log"; $socket = null; $workerId = null; define("UNAUTHORIZED_KICKOFF_SECOND" , 1); define("UNAUTHORIZED_ALLOW_TRYTIME" , 10); define("UDP_SOCKET_TYPE_IS_STREAM" , $worker->count > 1 ? 1 : 0 ); $worker->onWorkerStart = function() { global $worker , $workerId , $socket , $processName; //定期與數據庫握手,避免被斷掉,該動作每個進程都得執行 $workerId = $worker->id ; $worker->connections = []; $worker->connections_ur = []; //根據daemon順序延時,這是確保系統正常運行的關鍵 usleep(1000 * 10 * ($worker->id+1) ); echo date("Y-m-d H:i:s")." 服務進程{$worker->id}已經啟動!\n"; if( $workerId >= 0 ){ //計劃監聽的UDP端口 if(UDP_SOCKET_TYPE_IS_STREAM == 0){ //這種模式只能運行一個進程 $socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); socket_bind($socket , "0.0.0.0" , 5060); }else{ //這種模式可以多個進程共同監聽同一端口 $context = stream_context_create(); $socket = stream_socket_server("udp://0.0.0.0:5060" , $error_code , $error_message , STREAM_SERVER_BIND , $context); } Worker::$globalEvent->add($socket, 1 , "acceptUdpConnection"); //加入全局事件loop /* //如果需要從服務器主動發數據給客戶端,可以通過channel方式調用,具體的業務實現可以參照官方channel使用介紹 Channel\Client::connect(CHANNEL_SIP_IP , CHANNEL_SIP_PORT); $event_name = $processName; //UDP的數據回復跟TCP不一樣,只要知道對方端口即可,理論上任意一個進程均可從服務器端發送數據給客戶端 //到公網環境查詢本機公網IP,便于生成日志 Channel\Client::on($event_name, function($event_data)use($worker , $event_name) { }); */ //每隔一段時間對長時間未進行通信的臨時會話進行清理 //不宜太大,太大容易遭受DDOS攻擊,也不宜太小,太小的話有可能還沒完成業務邏輯就被踢掉 Timer::add( UNAUTHORIZED_KICKOFF_SECOND , function (){ global $worker , $socket; $dateTime = date("Y-m-d H:i:s"); $timeNow = time(); foreach ($worker->connections_ur as $remote_address => $remote_arr){ if( $remote_arr['lastMsgTime'] < $timeNow - UNAUTHORIZED_KICKOFF_SECOND ){ //非法接入,直接踢掉 unset($worker->connections_ur[$remote_address]); //print("{$dateTime} {$remote_address} 踢掉非法接入\n"); //sendto($remote_address ,"illegal connection!\n"); } //平臺主動發送數據給終端, sendto($remote_address ,"{$dateTime} test send data!\n"); } }); //每隔一段時間對已經認證過的會話進行檢查,對于長時間未通信的需要進行清理 Timer::add( 60 , function (){ global $worker , $socket; $dateTime = date("Y-m-d H:i:s"); $timeNow = time(); foreach ($worker->connections as $remote_address => $remote_arr){ if( $remote_arr['lastMsgTime'] < $timeNow - 300 ){ //超時未通信,直接踢掉 unset($worker->connections[$remote_address]); //這里編寫遠端會話離線的內容 //print("{$dateTime} {$remote_address} 會話超時掉線\n"); //sendto($remote_address ,"connection timeout!\n"); } } }); } }; function sendto($remote_address , $send_buffer ){ global $socket; if(UDP_SOCKET_TYPE_IS_STREAM == 0){ list($host, $port) = explode(":" , $remote_address ); socket_sendto($socket , $send_buffer , strlen($send_buffer), 0, $host, $port); }else{ stream_socket_sendto( $socket, $send_buffer, 0, $remote_address); } } function acceptUdpConnection($socket){ global $worker; $dateTime = date("Y-m-d H:i:s"); $timeNow = time(); set_error_handler(function(){}); if(UDP_SOCKET_TYPE_IS_STREAM == 0){ socket_recvfrom($socket, $recv_buffer, 65535, 0, $from, $port); $remote_address = "{$from}:{$port}"; }else{ $recv_buffer = stream_socket_recvfrom($socket, 65535 , 0, $remote_address); } restore_error_handler(); if (false === $recv_buffer || empty($remote_address)) { return false; } if( isset($worker->connections[$remote_address]['lastMsgTime']) && $worker->connections[$remote_address]['lastMsgTime'] >= $timeNow - 300 ){ //已注冊成功 $worker->connections[$remote_address]['lastMsgTime'] = $timeNow; //處理業務邏輯 針對已經認證的合法連接 //針對已經認證過的連接,建議將收到的數據通過channel發布到其他服務端進行輪詢處理,以最大化提升系統處理性能,此時,本程序僅僅充當gateway功能 }else{ //未注冊成功 未認證的可以進行幾次通信 $tryCount = $worker->connections_ur[$remote_address]['tryCount']??1; if( $tryCount < UNAUTHORIZED_ALLOW_TRYTIME ){ $worker->connections_ur[$remote_address]['lastMsgTime'] = $timeNow; $worker->connections_ur[$remote_address]['tryCount'] = $tryCount + 1; //sendTo( $remote_address ,"{$dateTime} {$remote_address}:\nunauthorized,try time {$tryCount} \n"); //處理業務邏輯 對于未認證的連接,必須在超時前完成認證 完成認證后需要將連接從 connections_ur 里面移出,并且加入到 connections 里面去 }else{ //超過一定交互次數但仍未完成認證的會話將被忽視,直到被踢下線 //sendTo( $remote_address ,"{$dateTime} {$remote_address}:\nunauthorized,max try \n"); } } //收到數據,打印日志 print( "{$dateTime} {$remote_address}:\n{$recv_buffer}\n"); }; Worker::runAll(); /* 以下是程序輸出樣例數據 2024-03-27 01:15:52 39.129.72.95:50574: REGISTER sip:34020000002000000001@3402000000 SIP/2.0 Via: SIP/2.0/UDP 192.168.1.4:5060;rport;branch=z9hG4bK650883237 From: <sip:34020000001180870005@3402000000>;tag=698431213 To: <sip:34020000001180870005@3402000000> Call-ID: 1146222786 CSeq: 1 REGISTER Contact: <sip:34020000001180870005@192.168.1.4:5060> Max-Forwards: 70 User-Agent: Embedded Net DVR/NVR/DVS Expires: 86400 Content-Length: 0 2024-03-27 01:15:53 39.129.72.95:15363: REGISTER sip:34020000002000000001@3402000000 SIP/2.0 Via: SIP/2.0/UDP 192.168.1.2:5060;rport;branch=z9hG4bK1890963109 From: <sip:34020000001180870007@3402000000>;tag=1955740825 To: <sip:34020000001180870007@3402000000> Call-ID: 254089720 CSeq: 1 REGISTER Contact: <sip:34020000001180870007@192.168.1.2:5060> Max-Forwards: 70 User-Agent: Embedded Net DVR/NVR/DVS Expires: 86400 Content-Length: 0 2024-03-27 01:15:54 39.129.72.95:31137: REGISTER sip:34020000002000000001@3402000000 SIP/2.0 Via: SIP/2.0/UDP 192.168.1.8:5060;rport;branch=z9hG4bK1552460892 From: <sip:34020000001180870002@3402000000>;tag=1271059450 To: <sip:34020000001180870002@3402000000> Call-ID: 108704431 CSeq: 1 REGISTER Contact: <sip:34020000001180870002@192.168.1.8:5060> Max-Forwards: 70 User-Agent: Embedded Net DVR/NVR/DVS Expires: 86400 Content-Length: 0 01:38:09.293144 IP (tos 0x68, ttl 48, id 44466, offset 0, flags [DF], proto UDP (17), length 436) 39.129.72.95.64959 > 192.168.27.21.5060: [udp sum ok] SIP, length: 408 REGISTER sip:34020000002000000001@3402000000 SIP/2.0 Via: SIP/2.0/UDP 192.168.1.7:5060;rport;branch=z9hG4bK1838031091 From: <sip:34020000001180870003@3402000000>;tag=793428652 To: <sip:34020000001180870003@3402000000> Call-ID: 1815643450 CSeq: 1 REGISTER Contact: <sip:34020000001180870003@192.168.1.7:5060> Max-Forwards: 70 User-Agent: Embedded Net DVR/NVR/DVS Expires: 86400 Content-Length: 0 01:38:09.540771 IP (tos 0x0, ttl 64, id 30488, offset 0, flags [DF], proto UDP (17), length 64) 192.168.27.21.5060 > 39.129.72.95.50574: [udp sum ok] SIP 01:38:09.540806 IP (tos 0x0, ttl 64, id 30489, offset 0, flags [DF], proto UDP (17), length 64) 192.168.27.21.5060 > 39.129.72.95.31137: [udp sum ok] SIP 01:38:09.540815 IP (tos 0x0, ttl 64, id 30490, offset 0, flags [DF], proto UDP (17), length 64) 192.168.27.21.5060 > 39.129.72.95.15363: [udp sum ok] SIP 01:38:09.540822 IP (tos 0x0, ttl 64, id 45413, offset 0, flags [DF], proto UDP (17), length 64) 192.168.27.21.5060 > 39.130.87.71.34950: [udp sum ok] SIP 01:38:09.540830 IP (tos 0x0, ttl 64, id 27774, offset 0, flags [DF], proto UDP (17), length 64) 192.168.27.21.5060 > 39.128.225.81.35329: [udp sum ok] SIP 01:38:09.540838 IP (tos 0x0, ttl 64, id 30491, offset 0, flags [DF], proto UDP (17), length 64) 192.168.27.21.5060 > 39.129.72.95.26505: [udp sum ok] SIP 01:38:09.540845 IP (tos 0x0, ttl 64, id 2845, offset 0, flags [DF], proto UDP (17), length 64) 192.168.27.21.5060 > 39.129.72.95.64959: [udp sum ok] SIP 01:38:09.540860 IP (tos 0x0, ttl 64, id 30493, offset 0, flags [DF], proto UDP (17), length 64) 192.168.27.21.5060 > 39.129.72.95.36321: [udp sum ok] SIP 01:38:09.540867 IP (tos 0x0, ttl 64, id 27775, offset 0, flags [DF], proto UDP (17), length 64) 192.168.27.21.5060 > 39.128.225.81.35342: [udp sum ok] SIP 01:38:09.540873 IP (tos 0x0, ttl 64, id 30494, offset 0, flags [DF], proto UDP (17), length 64) 192.168.27.21.5060 > 39.129.72.95.23047: [udp sum ok] SIP * */ ~~~ >[danger]特別說明一下,本人對網絡有一定的了解,據我所知大部分網絡環境中的NAT防火墻UDP會話默認超時時間大概為5分鐘-10分鐘左右。 因此只要UDP客戶端與服務器端每隔60秒進行一次**雙向的**心跳交互,可以確保通信的持續進行(當有業務需求時,任意一端可向另外一端發送所需要的數據報文),就像SIP一樣,哪怕你只發送個“hello”也是可以的。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看