当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阅读 196,165评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,503评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,295评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,589评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,439评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,342评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,749评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,397评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,700评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,740评论 2 313
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,523评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,364评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,755评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,024评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,297评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,721评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,918评论 2 336

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,517评论 18 139
  • 前言 上一回讲到,Swoole终于成功邂逅了PHP,现在要开始它们的奇妙路程了。 Server之初 通常,我们会把...
    零一间阅读 2,851评论 0 12
  • 1、TCP状态linux查看tcp的状态命令:1)、netstat -nat 查看TCP各个状态的数量2)、lso...
    北辰青阅读 9,351评论 0 11
  • 一、概念(载录于:http://www.cnblogs.com/EricaMIN1987_IT/p/3837436...
    yuantao123434阅读 8,322评论 6 152
  • 一直以来都好迷惑,觉得是我前世苍老了谁的岁月,这辈子在世上是为了寻她、等她、远远地看她…所以,迟到的缘分,离别的情...
    莫笙仁阅读 418评论 0 1