学习需要耐心&&时间,更重要的是你要学会坚持!
当前位置:首页 > PHP领域 > 正文

PHP实时消息推送,你会不会用workerman

2020-03-01 PHP领域 知末随未


TCP/IP
TCP/IP是个协议组,可分为三个层次:网络层、传输层和应用层。
在网络层有IP协议、ICMP协议、ARP协议、RARP协议和BOOTP协议。
在传输层中有TCP协议与UDP协议。

在应用层有:
TCP包括FTP、HTTP、TELNET、SMTP等协议
UDP包括DNS、TFTP等协议

短连接
连接->传输数据->关闭连接
HTTP是无状态的,浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束就中断连接。
也可以这样说:短连接是指SOCKET连接后发送后接收完数据后马上断开连接。
 
长连接
连接->传输数据->保持连接 -> 传输数据-> 。。。 ->关闭连接。
长连接指建立SOCKET连接后不管是否使用都保持连接,但安全性较差。

http的长连接
HTTP也可以建立长连接的,使用Connection:keep-alive,HTTP 1.1默认进行持久连接。HTTP1.1和HTTP1.0相比较而言,最大的区别就是增加了持久连接支持(貌似最新的 http1.0 可以显示的指定 keep-alive),但还是无状态的,或者说是不可以信任的。
 
什么时候用长连接,短连接?
长连接多用于操作频繁,点对点的通讯,而且连接数不能太多情况,。每个TCP连接都需要三步握手,这需要时间,如果每个操作都是先连接,再操作的话那么处理速度会降低很多,所以每个操作完后都不断开,次处理时直接发送数据包就OK了,不用建立TCP连接。例如:数据库的连接用长连接, 如果用短连接频繁的通信会造成socket错误,而且频繁的socket 创建也是对资源的浪费。
 
而像WEB网站的http服务一般都用短链接,因为长连接对于服务端来说会耗费一定的资源,而像WEB网站这么频繁的成千上万甚至上亿客户端的连接用短连接会更省一些资源,如果用长连接,而且同时有成千上万的用户,如果每个用户都占用一个连接的话,那可想而知吧。所以并发量大,但每个用户无需频繁操作情况下需用短连好。

workerman是啥?
Workerman是一款纯PHP开发的开源高性能的PHP socket 服务器框架。被广泛的用于手机app、移动通讯,微信小程序,手游服务端、网络游戏、PHP聊天室、硬件通讯、智能家居、车联网、物联网等领域的开发。 支持TCP长连接,支持Websocket、HTTP等协议,支持自定义协议。拥有异步Mysql、异步Redis、异步Http、异步消息队列等众多高性能组件。

开始步入正题:为了达到实时通讯,很多时候我们采用了ajax轮询机制,后面可以采用workerman方式来实现,项目也是tp写的,官方手册这么说到
与其它mvc框架结合建议以上图的方式(ThinkPHP为例):
1、ThinkPHP与Workerman是两个独立的系统,独立部署(可部署在不同服务器),互不干扰。
2、ThinkPHP以HTTP协议提供网页页面在浏览器渲染展示。
3、ThinkPHP提供的页面的js发起websocket连接,连接workerman
4、连接后给Workerman发送一个数据包(包含用户名密码或者某种token串)用于验证websocket连接属于哪个用户。
5、仅在ThinkPHP需要向浏览器推送数据时,才调用workerman的socket接口推送数据。
6、其余请求还是按照原本ThinkPHP的HTTP方式调用处理。

总结:
把Workerman作为一个可以向浏览器推送的通道,仅仅在需要向浏览器推送数据时才调用Workerman接口完成推送。业务逻辑全部在ThinkPHP中完成。
ok,到这里,把workerman容器跑起来,注意这里是CLI模式运行

然后再我们项目接收信息中这么写,附上代码

<script>

    // 连接服务端

    var socket = io('http://127.0.0.1:2120');

    // uid可以是自己网站的用户id,以便针对uid推送

    uid = 123;

    // socket连接后以uid登录

    socket.on('connect', function(){

    socket.emit('login', uid);

    });

    // 后端推送来消息时

    socket.on('new_msg', function(msg){

        console.log("收到消息:"+msg);  //自己业务逻辑处理

    });

     

</script>
接着,我们在用户向用户发送信息的时候添加
// 指明给谁推送,为空表示向所有在线用户推送

$to_uid = "123";

// 推送的url地址

$push_api_url = "http://127.0.0.1:2121/";

$post_data = array(

   "type" => "publish",

   "content" => "数据",

   "to" => $to_uid, 

);

$ch = curl_init ();

curl_setopt ( $ch, CURLOPT_URL, $push_api_url );

curl_setopt ( $ch, CURLOPT_POST, 1 );

curl_setopt ( $ch, CURLOPT_HEADER, 0 );

curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, 1 );

curl_setopt ( $ch, CURLOPT_POSTFIELDS, $post_data );

curl_setopt ($ch, CURLOPT_HTTPHEADER, array("Expect:"));

$return = curl_exec ( $ch );

curl_close ( $ch );

var_export($return);
其中,workerman里面的推送核心代码实现
// 全局数组保存uid在线数据

$uidConnectionMap = array();

// 记录最后一次广播的在线用户数

$last_online_count = 0;

  

  

// PHPSocketIO服务

$sender_io = new SocketIO(2120);

// 客户端发起连接事件时,设置连接socket的各种事件回调

  

// 当$sender_io启动后监听一个http端口,通过这个端口可以给任意uid或者所有uid推送数据

$sender_io->on('workerStart', function(){

    // 监听一个http端口

    $inner_http_worker = new Worker('http://0.0.0.0:2121');

    // 当http客户端发来数据时触发

    $inner_http_worker->onMessage = function($http_connection, $data){

        global $uidConnectionMap;

        $_POST = $_POST ? $_POST : $_GET;

        // 推送数据的url格式 type=publish&to=uid&content=xxxx

        switch(@$_POST['type']){

            case 'publish':

                global $sender_io;

                $to = @$_POST['to'];

                $_POST['content'] = htmlspecialchars(@$_POST['content']);

                // 有指定uid则向uid所在socket组发送数据

                if($to){

                    $sender_io->to($to)->emit('new_msg', $_POST['content']);

                // 否则向所有uid推送数据

                }else{

                    $sender_io->emit('new_msg', @$_POST['content']);

                }

                // http接口返回,如果用户离线socket返回fail

                if($to && !isset($uidConnectionMap[$to])){

                    return $http_connection->send('offline');

                }else{

                    return $http_connection->send('ok');

                }

        }

        return $http_connection->send('fail');

    };

     

});

  

if(!defined('GLOBAL_START'))

{

    Worker::runAll();

}

以上是本文的全部内容,希望对大家学习有帮助,也希望大家多多支持 磊丰的技术博客 感谢阅读!

站长磊丰学长
男,文化程度不高,性格有点犯二,爱好学习与分享,闲着没事喜欢研究各种代码,写写文章,潜水技术宅。
关注公众号:PHP自学中心
关注公众号:Go语言学习圈
学习与交流:程序员技术微信群

网站工具箱