摘要:如果你想體驗(yàn)原味編程,用開(kāi)頭的比較適合否則建議使用流函數(shù)。有關(guān)流的知識(shí),請(qǐng)參考本人之前的博文回顧之流。接下來(lái)我們用流函數(shù)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的客戶(hù)端和服務(wù)端。流函數(shù)中的和兩個(gè)函數(shù)是我們想要的。本文目的是簡(jiǎn)要介紹中的編程,行文到此已經(jīng)達(dá)到目的。
轉(zhuǎn)載請(qǐng)注明文章出處: https://tlanyan.me/php-review...PHP回顧系列目錄
PHP基礎(chǔ)
web請(qǐng)求
cookie
web響應(yīng)
session
數(shù)據(jù)庫(kù)操作
加解密
Composer
創(chuàng)建自己的Composer包
發(fā)送郵件
IO
流
web開(kāi)發(fā)一直是PHP的主戰(zhàn)場(chǎng),也是PHP最為被世人所熟知的一面。其實(shí)只要你愿意去發(fā)掘,PHP除了做網(wǎng)頁(yè)在許多其他方面也是小能手。
本文簡(jiǎn)要介紹PHP的Socket編程。
準(zhǔn)備知識(shí)在開(kāi)始之前,希望你已經(jīng)知道網(wǎng)絡(luò)編程中的一些基本概念。比如OSI七層模型、TCP/IP四層模型;TCP中的三次握手、四次揮手等。這些概念是網(wǎng)絡(luò)編程的理論基礎(chǔ),實(shí)踐中不一定用得到,但能讓你把握整體脈絡(luò),更快的定位編程中出現(xiàn)的問(wèn)題。
再說(shuō)一下Socket。我們常說(shuō)的網(wǎng)絡(luò)編程就是指Socket編程,它既指代實(shí)現(xiàn)了TCP/IP協(xié)議簇的一套網(wǎng)絡(luò)編程API,也指代一個(gè)客戶(hù)端與服務(wù)器的連接。socket是插座/接口的意思,計(jì)算機(jī)中常翻譯成“套接字”。實(shí)際中可以簡(jiǎn)單的認(rèn)為網(wǎng)絡(luò)編程與Socket編程等價(jià),一個(gè)tcp連接的說(shuō)法等價(jià)于一個(gè)socket。
PHP中的APIPHP中有以socket開(kāi)頭的一套函數(shù)API用于Socket編程,PHP5引入“流”的抽象概念后,以stream開(kāi)頭的一套API也可以用于網(wǎng)絡(luò)編程。兩者的主要區(qū)別是:
流是PHP中的核心概念,所以stream開(kāi)頭的函數(shù)總是可用;sockets是PHP的一個(gè)拓展,雖然大部分情況下都默認(rèn)啟用;
socket系列函數(shù)相對(duì)底層,而stream系列函數(shù)是高層的抽象。
如果你想體驗(yàn)原味Socket編程,用socket開(kāi)頭的API比較適合;否則建議使用流函數(shù)。有關(guān)流的知識(shí),請(qǐng)參考本人之前的博文:PHP回顧之流。
接下來(lái)我們用流函數(shù)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的TCP客戶(hù)端和服務(wù)端。
客戶(hù)端客戶(hù)端網(wǎng)絡(luò)編程可以歸結(jié)為簡(jiǎn)單的三步:
連接服務(wù)端(connect);
收發(fā)消息(receive/send);
關(guān)閉連接(close)。
下面是客戶(hù)端的代碼,發(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);
客戶(hù)端已經(jīng)搞定,接下來(lái)看服務(wù)端。
服務(wù)端服務(wù)端編程也很簡(jiǎn)單,四步搞定:
監(jiān)聽(tīng)端口(listen);
接受新連接(accept);
收發(fā)網(wǎng)絡(luò)消息(receive/send);
循環(huán)第二步和第三步(loop)。
由于服務(wù)端一般是長(zhǎng)時(shí)間運(yùn)行,除非重啟或進(jìn)程被殺死,極少會(huì)主動(dòng)關(guān)閉服務(wù)。另外服務(wù)端一般需要長(zhǎng)時(shí)間運(yùn)行,所以應(yīng)當(dāng)運(yùn)行在CLI模式下(短連的客戶(hù)端代碼可以在web中使用,例如代替CURL獲取網(wǎng)頁(yè)內(nèi)容,連接redis/MQ等)。
我們簡(jiǎn)單的將收到的消息返回客戶(hù)端(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; } } }
先啟動(dòng)服務(wù)端腳本:php server.php, 然后打開(kāi)新的窗口啟動(dòng)客戶(hù)端:php client.php。可以看到消息被正確的發(fā)送和接收。客戶(hù)端退出后,可多次重新運(yùn)行客戶(hù)端腳本查看效果。
并發(fā)同時(shí)運(yùn)行兩個(gè)或以上客戶(hù)端,會(huì)發(fā)現(xiàn)第二個(gè)起卡住,前面的客戶(hù)端退出后才繼續(xù)運(yùn)行。回顧服務(wù)端代碼,可以看到accept一個(gè)客戶(hù)端后,服務(wù)端就專(zhuān)心為其服務(wù),直到斷開(kāi)才服務(wù)下一個(gè)。
同時(shí)服務(wù)多個(gè)客戶(hù)端,這才是我們期望的。默認(rèn)情況下socket處于阻塞模式,無(wú)數(shù)據(jù)時(shí)fread函數(shù)會(huì)一直等待,導(dǎo)致程序不能抽身服務(wù)其他客戶(hù)端。要同時(shí)服務(wù)多個(gè)客戶(hù)端,第一步是設(shè)置非阻塞模式,第二步是更改輪詢(xún)方式。流函數(shù)中的stream_set_blocking和stream_select兩個(gè)函數(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; } } } }
然后啟動(dòng)服務(wù)端:php server.php,再同時(shí)啟動(dòng)多個(gè)客戶(hù)端,或者用多個(gè)進(jìn)程同時(shí)發(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; } } // 父進(jìn)程先退出,不會(huì)出現(xiàn)僵尸進(jìn)程,忽略孤兒進(jìn)程的處理
啟動(dòng)客戶(hù)端后,可以看到服務(wù)端正確的同時(shí)處理多個(gè)客戶(hù)端,這正是我們期待的。
缺憾上述代碼實(shí)現(xiàn)了客戶(hù)端和可并發(fā)的服務(wù)端,作為演示基本夠用。如果要投入到實(shí)踐中使用,至少有以下方面的不足:
多進(jìn)程/多線程/協(xié)程缺失,除處理網(wǎng)絡(luò)消息外,不能(難)做其他邏輯業(yè)務(wù);
沒(méi)有協(xié)議解析,會(huì)導(dǎo)致多條信息合并成一條讀取(或者一條信息被拆成多條);
select低效且有并發(fā)連接數(shù)目限制,客戶(hù)端量大時(shí)需要poll/epoll等技術(shù);
每個(gè)方面展開(kāi)來(lái)說(shuō)至少都是一篇長(zhǎng)文。本文目的是簡(jiǎn)要介紹PHP中的Socket編程,行文到此已經(jīng)達(dá)到目的。由于網(wǎng)絡(luò)協(xié)議十分繁雜,想深入網(wǎng)絡(luò)編程請(qǐng)參閱更多權(quán)威文檔。
總結(jié)本文基于PHP5引入的流簡(jiǎn)要介紹了PHP中的Socket編程,并給出了一個(gè)簡(jiǎn)單并發(fā)服務(wù)器的實(shí)現(xiàn)。文中代碼僅做演示用,在生產(chǎn)環(huán)境中,請(qǐng)使用成熟的網(wǎng)絡(luò)框架/庫(kù)。
參考http://php.net/manual/en/book...
http://www.unixguide.net/netw...
http://php.net/manual/en/book...
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/28901.html
摘要:多進(jìn)程中與多進(jìn)程相關(guān)的兩個(gè)重要拓展是和。函數(shù)執(zhí)行期間,主進(jìn)程除了等待無(wú)法處理其他任務(wù),所以一般不認(rèn)為這是多進(jìn)程編程。回收子進(jìn)程有兩種方式,一種是主進(jìn)程調(diào)用函數(shù)等待子進(jìn)程結(jié)束另外一種是處理信號(hào)。 轉(zhuǎn)載請(qǐng)注明文章出處: https://tlanyan.me/php-review... PHP回顧系列目錄 PHP基礎(chǔ) web請(qǐng)求 cookie web響應(yīng) session 數(shù)據(jù)庫(kù)操作 加解...
摘要:通過(guò),腳本層無(wú)需過(guò)多考慮執(zhí)行的具體環(huán)境,而本身則可以讓針對(duì)自己的特點(diǎn)給出特有實(shí)現(xiàn)。模式下,也只執(zhí)行一次。這幾個(gè)概念的關(guān)系如下網(wǎng)關(guān)協(xié)議,與語(yǔ)言無(wú)關(guān),所以與關(guān)系也不大。總結(jié)本文簡(jiǎn)要回顧了程序的架構(gòu)和執(zhí)行流程,并對(duì)幾個(gè)容易混淆概念做了介紹。 轉(zhuǎn)載請(qǐng)注明文章出處:https://tlanyan.me/php-review... PHP回顧系列目錄 PHP基礎(chǔ) web請(qǐng)求 cookie we...
摘要:本文先回顧生成器,然后過(guò)渡到協(xié)程編程。其作用主要體現(xiàn)在三個(gè)方面數(shù)據(jù)生成生產(chǎn)者,通過(guò)返回?cái)?shù)據(jù)數(shù)據(jù)消費(fèi)消費(fèi)者,消費(fèi)傳來(lái)的數(shù)據(jù)實(shí)現(xiàn)協(xié)程。解決回調(diào)地獄的方式主要有兩種和協(xié)程。重點(diǎn)應(yīng)當(dāng)關(guān)注控制權(quán)轉(zhuǎn)讓的時(shí)機(jī),以及協(xié)程的運(yùn)作方式。 轉(zhuǎn)載請(qǐng)注明文章出處: https://tlanyan.me/php-review... PHP回顧系列目錄 PHP基礎(chǔ) web請(qǐng)求 cookie web響應(yīng) sess...
摘要:命令行時(shí)返回值為,標(biāo)準(zhǔn)輸入輸出均指向終端可用進(jìn)程號(hào)查看。會(huì)在腳本執(zhí)行完畢后關(guān)閉三個(gè)流,無(wú)需用戶(hù)手動(dòng)關(guān)閉。與遠(yuǎn)程網(wǎng)址交互是一個(gè)請(qǐng)求和響應(yīng)的過(guò)程,其中細(xì)節(jié)可參考本人之前的文章回顧之請(qǐng)求和回顧之響應(yīng),也可參考協(xié)議的權(quán)威文檔。 轉(zhuǎn)載請(qǐng)注明文章出處: https://tlanyan.me/php-review... PHP回顧系列目錄 PHP基礎(chǔ) web請(qǐng)求 cookie web響應(yīng) ses...
摘要:今年從北京站開(kāi)始,分享主題與后端相關(guān)。嘉賓匯總高馳濤性能之路姜季廷的前后之道孫宏亮生態(tài)中的現(xiàn)狀與實(shí)踐信海龍異步化探索今年還會(huì)在其他九個(gè)城市巡回分享,感謝大家的關(guān)注與分享。 今年 SegmentFault D-Day 從北京站開(kāi)始,分享主題與「后端」相關(guān)。當(dāng)然,我們還會(huì)在其他九個(gè)城市巡回分享,歡迎大家關(guān)注,幫忙擴(kuò)散。 開(kāi)場(chǎng)介紹 首先是 youku 美女星宇對(duì) SegmentFault 社...
閱讀 1402·2021-11-22 09:34
閱讀 1378·2021-09-22 14:57
閱讀 3400·2021-09-10 10:50
閱讀 1371·2019-08-30 15:54
閱讀 3690·2019-08-29 17:02
閱讀 3472·2019-08-29 12:54
閱讀 2611·2019-08-27 10:57
閱讀 3316·2019-08-26 12:24