當SWOOLE遇上TCP

前言

前文再續,就書接上一回(拍一下驚堂木,然后喝口茶install一下B),話說筆者當初最早接觸Swoole的時候,正迫切的期望能找到一個使用PHP作為主要開發語言的TCP Server的解決方案,因為公司業務中積累了大量的PHP代碼,而新增的業務又迫切需要實現與客戶端的主動通信,最終在盆友的推薦下,找到了Swoole。

輪詢與長連接

一般情況下,我們接觸PHP都是作為一個Web網站的開發語言而接觸的,例如一個最簡單的HelloWorld.php,往往是這么寫的:

<?php
echo "Hello PHP";

LAMP的配置這里就不多說了

不自覺的,驀然間會讓我們產生一種錯覺,PHP只能用來處理這種場景的工作,其他事情并不合適。

亦或者說,很多盆友并沒有意識到,PHP其實還隱藏了洪荒之力

當時,筆者需要開發一個實時的消息服務APP,消息的實時性要求較高,也就是說,服務端需要可以主動向客戶端推送消息,而這個時候,如果再采用傳統的http api的方式,勢必陷入輪詢的困局。

客戶端每隔1s向服務器請求,檢查是否有新的數據,這種場景可能會產生大量無用的請求,也會極大的增加服務端的負荷。

傳統的Web服務,采用http/https作為應用層協議,并且通過“請求->響應”的機制實現客戶端和服務端的通訊,也就是說,服務端總是“被動”的提供服務,服務端“難以”主動的將消息告知客戶端。

這其實也是是websocket的產生背景。

這個時候,我們可以考慮實現自己的TCP Server,以解決這個問題。

顯然,這里討論的問題并不局限于開發語言,.Net、Java、Go、NodeJS等都有對應的解決方案。

通過TCP協議構建的Server,是可以實現服務端和客戶端保持一個持久的鏈接,鏈接一旦建立,就像電話打通了一樣,通話的雙方都可以主動向對方發送消息。

其實http/https協議的傳輸層也是tcp協議,但為啥http/https協議變成了一次性的服務呢?有緣的話,下回分解。

因此,雙方的鏈接會呈現出“持久在線”的狀態,也就是長連接這一說法的由來。

有興趣的盆友可以自行查找TCP是怎么實現“在線”這個狀態的,還記得筆者上學時,計算機網絡的老師的一句話,網絡通信上絕對的可靠是不存在的。

TCP Server在干啥?

回到我們的應用場景,客戶端需要先與服務端建立TCP長連接,并維持這個鏈接,當服務端產生了新的消息時,服務端主動將新消息發送給客戶端,客戶端接收消息并解析,然后將結果展示給客戶端。

以下例子,改編自拙作《當SWOOLE遇上SERVER》

<?php 
    //vi swoole_tcp_server_demo.php

    $server = new \swoole_server("127.0.0.1",8088,SWOOLE_PROCESS,SWOOLE_SOCK_TCP);

    $server->on('connect', function ($serv, $fd){
            echo "Client:Connect.\n";
            
            //啟動一個循環,定時向客戶端發一個消息
            
});

    $server->on('receive', function ($serv, $fd, $from_id, $data) {
            //打印收到的消息
            echo "Receive message: $data";
            //關閉連接(當然,也可以不關閉)
            $serv->close($fd);
});

    $server->on('close', function ($serv, $fd) {
            echo "Client: Close.\n";
});

$server -> start();

如果你是在遠程服務器上運行的,請將127.0.0.1替換為你的遠程服務器公網IP(或者你能訪問的內網IP)。

上一章的例子中,我們每次receive了一個客戶端的消息以后,就關閉了與這個客戶端的鏈接,并沒有向客戶端發出響應,但事實上,服務端完全可以在收到消息以后,向客戶端發出一個回復,就像“請求->響應”的工作機制一樣:

<?php 
//我們修改一下on reveive的回調,然后啟動服務
$server->on('receive', function ($serv, $fd, $from_id, $data) 
{
    //根據收到的消息做出不同的響應
    switch($data)
    {
        case 1:
        {
            $serv->send($fd,"1 for apple\n");
            break;
        }
        case 2:
        {
            $serv->send($fd,"2 for boy\n");
            break;
        }
        default:
        {
            $serv->send($fd,"Others is default\n");
        }
    }
});

用telnet作為客戶端訪問一下我們剛剛啟動Server

> telnet 127.0.0.1 8088
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.

然后分別輸入“1"、“2”、“hello”并回車

以下是telnet的輸出

> telnet 127.0.0.1 8088
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
1
1 for apple
2
2 for boy
3
others is default
5
others is default
hello
others is default

這段代碼很簡單,如果receive了客戶端的消息,對消息做一個switch,根據switch的結果,向客戶端返回不同的消息。

這個場景是一個很典型的“請求->響應”的場景

那有些盆友也許會問了,這樣做的話,跟我使用URL訪問網站獲取響應有什么區別?

這個問題很好,思考是不斷進步的階梯

那么我們來做些不一樣的,繼續修改on receive的回調:

<?php
//我們修改一下on reveive的回調,然后啟動服務
$server->on('receive', function ($serv, $fd, $from_id, $data) 
{
    //根據收到的消息做出不同的響應
    switch($data)
    {
        case 1:
        {
            foreach($serv->connections as $tempFD)
            {
                 $serv->send($tempFD,"1 for apple\n");
            }
            break;
        }
        case 2:
        {
            $serv->send($fd,"2 for boy\n");
            break;
        }
        default:
        {
            $serv->send($fd,"Others is default\n");
        }
    }
});

當case 1的時候,我們遍歷了$serv的connections成員,獲得了與當前服務器連接的所有客戶端,并且向所有的客戶端都發送了“1 for apple\n”這個字符串。繼續用telnet作為客戶端,我們這次需要打開兩個telnet,當兩個telnet都成功連接了Server之后,用第一個telnet發送1:

第一個telnet客戶端的輸出

> telnet 127.0.0.1 8088
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
1
1 for apple

第二個telnet客戶端的輸出

> telnet 127.0.0.1 8088
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
1 for apple

第二個telnet客戶端雖然并沒有向server端發送“1”作為消息,server端仍然向第二個客戶端發送了消息“1 for apple\n”,這可以做什么?如果我們要做一個聊天室的話,就可以簡單的實現發送公共聊天消息的功能。

如果打開一下腦洞,在Server的業務中將用戶分類存儲,發送的時候有選擇的向不同的用戶發送消息,就可以實現私聊,亦或者是分組消息。

如果只是這樣,可能又有童鞋問了,僅僅這樣做,還是一個“請求->響應”的工作模式吖,只不過是將一對一的請求響應,變成了一對多的請求響應?

確實有點這個感覺,那我們來做點不一樣,這次,server會不斷向客戶端發送消息,不管有沒有請求。

<?php
//這次我們要修改的是on connect回調哦!
$server->on('connect', function ($serv, $fd)
{
    $serv->tick(1000, function() use ($serv, $fd) {
            $serv->send($fd, "這是一條定時消息\n");
        });
});

以上代碼中的tick方法,表示啟動一個定時器,該定時器每1000毫秒觸發一次,并執行回調方法。

Swoole Tick是Swoole工具箱中的一個強大工具,它比PHP原生的pcntl_alarm更加精確,也支持異步調用,非常方便,更多介紹可以參考手冊。

這次仍然是打開telnet

> telnet 127.0.0.1 8088
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
這是一條定時消息
這是一條定時消息
這是一條定時消息
這是一條定時消息
這是一條定時消息
這是一條定時消息
這是一條定時消息
2
這是一條定時消息
2 for boy
這是一條定時消息
這是一條定時消息
這是一條定時消息
這是一條定時消息
這是一條定時消息
這是一條定時消息
...

這次,只要連接上服務器,不管客戶端說沒說話,會一直收到“這是一條定時消息”的消息,并且,如果我們見縫插針地寫個2并發送,就會收到on receive中的反饋“2 for boy”,并不會與“這是一條定時消息”沖突。

這里,前者就是服務端主動發出,客戶端被動接受的消息;而后者,卻又是“請求->響應”的工作模式,兩者并不沖突,僅取決于具體的代碼實現。

小結

好滴,今天的三分熱度就到這了,再多就得超時了,這篇的內容主要列舉了TCP Server的幾個基本工作場景,及這些場景通過Swoole Server的簡單實現。其實TCP Server的核心應用特征就在于,一旦連接建立,雙方都可以平等地自由選擇什么時候向對方發出消息,并選擇是否對收到的消息做出響應。

想象一下,你跟你的基友在電話兩旁自說自話 QAQ

來源

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,785評論 18 139
  • 前言 上一回講到,Swoole終于成功邂逅了PHP,現在要開始它們的奇妙路程了。 Server之初 通常,我們會把...
    零一間閱讀 2,885評論 0 12
  • 1、TCP狀態linux查看tcp的狀態命令:1)、netstat -nat 查看TCP各個狀態的數量2)、lso...
    北辰青閱讀 9,471評論 0 11
  • 一、概念(載錄于:http://www.cnblogs.com/EricaMIN1987_IT/p/3837436...
    yuantao123434閱讀 8,409評論 6 152
  • 一直以來都好迷惑,覺得是我前世蒼老了誰的歲月,這輩子在世上是為了尋她、等她、遠遠地看她…所以,遲到的緣分,離別的情...
    莫笙仁閱讀 443評論 0 1