摘要:如果你想體驗原味編程,用開頭的比較適合否則建議使用流函數(shù)。有關(guān)流的知識,請參考本人之前的博文回顧之流。接下來我們用流函數(shù)實現(xiàn)一個簡單的客戶端和服務(wù)端。流函數(shù)中的和兩個函數(shù)是我們想要的。本文目的是簡要介紹中的編程,行文到此已經(jīng)達到目的。
轉(zhuǎn)載請注明文章出處: https://tlanyan.me/php-review...PHP回顧系列目錄
PHP基礎(chǔ)
web請求
cookie
web響應(yīng)
session
數(shù)據(jù)庫操作
加解密
Composer
創(chuàng)建自己的Composer包
發(fā)送郵件
IO
流
web開發(fā)一直是PHP的主戰(zhàn)場,也是PHP最為被世人所熟知的一面。其實只要你愿意去發(fā)掘,PHP除了做網(wǎng)頁在許多其他方面也是小能手。
本文簡要介紹PHP的Socket編程。
準備知識在開始之前,希望你已經(jīng)知道網(wǎng)絡(luò)編程中的一些基本概念。比如OSI七層模型、TCP/IP四層模型;TCP中的三次握手、四次揮手等。這些概念是網(wǎng)絡(luò)編程的理論基礎(chǔ),實踐中不一定用得到,但能讓你把握整體脈絡(luò),更快的定位編程中出現(xiàn)的問題。
再說一下Socket。我們常說的網(wǎng)絡(luò)編程就是指Socket編程,它既指代實現(xiàn)了TCP/IP協(xié)議簇的一套網(wǎng)絡(luò)編程API,也指代一個客戶端與服務(wù)器的連接。socket是插座/接口的意思,計算機中常翻譯成“套接字”。實際中可以簡單的認為網(wǎng)絡(luò)編程與Socket編程等價,一個tcp連接的說法等價于一個socket。
PHP中的APIPHP中有以socket開頭的一套函數(shù)API用于Socket編程,PHP5引入“流”的抽象概念后,以stream開頭的一套API也可以用于網(wǎng)絡(luò)編程。兩者的主要區(qū)別是:
流是PHP中的核心概念,所以stream開頭的函數(shù)總是可用;sockets是PHP的一個拓展,雖然大部分情況下都默認啟用;
socket系列函數(shù)相對底層,而stream系列函數(shù)是高層的抽象。
如果你想體驗原味Socket編程,用socket開頭的API比較適合;否則建議使用流函數(shù)。有關(guān)流的知識,請參考本人之前的博文:PHP回顧之流。
接下來我們用流函數(shù)實現(xiàn)一個簡單的TCP客戶端和服務(wù)端。
客戶端客戶端網(wǎng)絡(luò)編程可以歸結(jié)為簡單的三步:
連接服務(wù)端(connect);
收發(fā)消息(receive/send);
關(guān)閉連接(close)。
下面是客戶端的代碼,發(fā)送10條消息到服務(wù)端:
// client.php $host = "127.0.0.1"; $port = 8000; $socket = @stream_socket_client("tcp://{$host}:{$port}", $errno, $errMsg); if ($socket === false) { throw new RuntimeException("unable to create socket: " . $errMsg); } fwrite(STDOUT, "success connect to server: [{$host}:{$port}]... "); foreach (range(1, 10) as $i) { if ($i % 5 === 0) { $method = "broadcast"; } else { $method = "echo"; } $args = [sprintf("The %dth greeting", $i)]; $message = json_encode([ "method" => $method, "args" => $args, ]); fwrite(STDOUT, " send to server: $message "); $len = @fwrite($socket, $message); if ($len === 0) { fwrite(STDOUT, "socket closed "); break; } $msg = @fread($socket, 4096); if ($msg) { fwrite(STDOUT, "receive server: $msg "); } elseif (feof($socket)) { fwrite(STDOUT, "socket closed "); break; } sleep(2); } fwrite(STDOUT, "close connnection... "); fclose($socket);
客戶端已經(jīng)搞定,接下來看服務(wù)端。
服務(wù)端服務(wù)端編程也很簡單,四步搞定:
監(jiān)聽端口(listen);
接受新連接(accept);
收發(fā)網(wǎng)絡(luò)消息(receive/send);
循環(huán)第二步和第三步(loop)。
由于服務(wù)端一般是長時間運行,除非重啟或進程被殺死,極少會主動關(guān)閉服務(wù)。另外服務(wù)端一般需要長時間運行,所以應(yīng)當運行在CLI模式下(短連的客戶端代碼可以在web中使用,例如代替CURL獲取網(wǎng)頁內(nèi)容,連接redis/MQ等)。
我們簡單的將收到的消息返回客戶端(Echo服務(wù)器):
// server.php $port = 8000; $socket = @stream_socket_server("tcp://0.0.0.0:$port", $errno, $errMsg); if ($socket === false) { throw new RuntimeException("fail to listen on port: {$port}!"); } fwrite(STDOUT, "socket server listen on port: {$port}" . PHP_EOL); while (true) { $client = @stream_socket_accept($socket); if ($client == false) { continue; } fwrite(STDOUT, "client:" . (int)$client . " connnected. "); @fwrite($client, "Welcome aboard! "); while (true) { $msg = @fread($client, 4096); if ($msg) { fwrite(STDOUT, " receive client: $msg "); // echo @fwrite($client, $msg); } elseif (feof($client)) { fwrite(STDOUT, "client:" . (int)$client . " disconnnect! "); fclose($client); break; } } }
先啟動服務(wù)端腳本:php server.php, 然后打開新的窗口啟動客戶端:php client.php。可以看到消息被正確的發(fā)送和接收。客戶端退出后,可多次重新運行客戶端腳本查看效果。
并發(fā)同時運行兩個或以上客戶端,會發(fā)現(xiàn)第二個起卡住,前面的客戶端退出后才繼續(xù)運行?;仡櫡?wù)端代碼,可以看到accept一個客戶端后,服務(wù)端就專心為其服務(wù),直到斷開才服務(wù)下一個。
同時服務(wù)多個客戶端,這才是我們期望的。默認情況下socket處于阻塞模式,無數(shù)據(jù)時fread函數(shù)會一直等待,導(dǎo)致程序不能抽身服務(wù)其他客戶端。要同時服務(wù)多個客戶端,第一步是設(shè)置非阻塞模式,第二步是更改輪詢方式。流函數(shù)中的stream_set_blocking和stream_select兩個函數(shù)是我們想要的。
將服務(wù)端的代碼更改如下:
// server.php $client) { while (true) { $msg = @fread($client, 4096); if ($msg) { fwrite(STDOUT, "receive client " . (int)$client . " message: $msg "); $json = json_decode($msg, true); if ($json) { $method = $json["method"]; if ($method === "echo") { @fwrite($client, $msg); } else { foreach ($clients as $cl) { @fwrite($cl, "message from " . (int)$client . ": $msg"); } } } } else { if (feof($client)) { fwrite(STDOUT, " client " . (int)$client . " closed. "); fclose($client); $key = array_search($client, $clients); unset($clients[$key]); } break; } } } }
然后啟動服務(wù)端:php server.php,再同時啟動多個客戶端,或者用多個進程同時發(fā)送消息(需安裝pcntl拓展):
// client.php for ($index = 0; $index < 10; ++ $index) { $pid = pcntl_fork(); if ($pid < 0) { fwrite(STDERR, "fail to fork! "); exit; } if ($pid === 0) { connectServer(); // connectServer就是上文中client.php中的代碼 exit; } } // 父進程先退出,不會出現(xiàn)僵尸進程,忽略孤兒進程的處理
啟動客戶端后,可以看到服務(wù)端正確的同時處理多個客戶端,這正是我們期待的。
缺憾上述代碼實現(xiàn)了客戶端和可并發(fā)的服務(wù)端,作為演示基本夠用。如果要投入到實踐中使用,至少有以下方面的不足:
多進程/多線程/協(xié)程缺失,除處理網(wǎng)絡(luò)消息外,不能(難)做其他邏輯業(yè)務(wù);
沒有協(xié)議解析,會導(dǎo)致多條信息合并成一條讀?。ɑ蛘咭粭l信息被拆成多條);
select低效且有并發(fā)連接數(shù)目限制,客戶端量大時需要poll/epoll等技術(shù);
每個方面展開來說至少都是一篇長文。本文目的是簡要介紹PHP中的Socket編程,行文到此已經(jīng)達到目的。由于網(wǎng)絡(luò)協(xié)議十分繁雜,想深入網(wǎng)絡(luò)編程請參閱更多權(quán)威文檔。
總結(jié)本文基于PHP5引入的流簡要介紹了PHP中的Socket編程,并給出了一個簡單并發(fā)服務(wù)器的實現(xiàn)。文中代碼僅做演示用,在生產(chǎn)環(huán)境中,請使用成熟的網(wǎng)絡(luò)框架/庫。
參考http://php.net/manual/en/book...
http://www.unixguide.net/netw...
http://php.net/manual/en/book...
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/28901.html
摘要:多進程中與多進程相關(guān)的兩個重要拓展是和。函數(shù)執(zhí)行期間,主進程除了等待無法處理其他任務(wù),所以一般不認為這是多進程編程?;厥兆舆M程有兩種方式,一種是主進程調(diào)用函數(shù)等待子進程結(jié)束另外一種是處理信號。 轉(zhuǎn)載請注明文章出處: https://tlanyan.me/php-review... PHP回顧系列目錄 PHP基礎(chǔ) web請求 cookie web響應(yīng) session 數(shù)據(jù)庫操作 加解...
摘要:通過,腳本層無需過多考慮執(zhí)行的具體環(huán)境,而本身則可以讓針對自己的特點給出特有實現(xiàn)。模式下,也只執(zhí)行一次。這幾個概念的關(guān)系如下網(wǎng)關(guān)協(xié)議,與語言無關(guān),所以與關(guān)系也不大??偨Y(jié)本文簡要回顧了程序的架構(gòu)和執(zhí)行流程,并對幾個容易混淆概念做了介紹。 轉(zhuǎn)載請注明文章出處:https://tlanyan.me/php-review... PHP回顧系列目錄 PHP基礎(chǔ) web請求 cookie we...
摘要:本文先回顧生成器,然后過渡到協(xié)程編程。其作用主要體現(xiàn)在三個方面數(shù)據(jù)生成生產(chǎn)者,通過返回數(shù)據(jù)數(shù)據(jù)消費消費者,消費傳來的數(shù)據(jù)實現(xiàn)協(xié)程。解決回調(diào)地獄的方式主要有兩種和協(xié)程。重點應(yīng)當關(guān)注控制權(quán)轉(zhuǎn)讓的時機,以及協(xié)程的運作方式。 轉(zhuǎn)載請注明文章出處: https://tlanyan.me/php-review... PHP回顧系列目錄 PHP基礎(chǔ) web請求 cookie web響應(yīng) sess...
摘要:今年從北京站開始,分享主題與后端相關(guān)。嘉賓匯總高馳濤性能之路姜季廷的前后之道孫宏亮生態(tài)中的現(xiàn)狀與實踐信海龍異步化探索今年還會在其他九個城市巡回分享,感謝大家的關(guān)注與分享。 今年 SegmentFault D-Day 從北京站開始,分享主題與「后端」相關(guān)。當然,我們還會在其他九個城市巡回分享,歡迎大家關(guān)注,幫忙擴散。 開場介紹 首先是 youku 美女星宇對 SegmentFault 社...
閱讀 1414·2021-11-22 09:34
閱讀 1384·2021-09-22 14:57
閱讀 3417·2021-09-10 10:50
閱讀 1398·2019-08-30 15:54
閱讀 3697·2019-08-29 17:02
閱讀 3479·2019-08-29 12:54
閱讀 2621·2019-08-27 10:57
閱讀 3325·2019-08-26 12:24