给udp客户端发消息

sanye

问题描述

当前的状态是客户端主动给服务端发消息,服务端在onMessage里面给客户端发消息是OK的,IP和端口已经保存,并且客户端每上传一条信息就自动更新ip和port,目前的问题点是,需要在onMessage外面,通过api的形式给客户端发消息,请问是否有成熟可用的案例或者思路?

目前服务端是:workerman建udp和websocket服务端,udp连接设备,websocket连接用户侧,udp将收集的数据通过websocket发给用户,websocket实时的和服务器进行交互获取最新数据,webman则提供控制api,需要对设备进行控制的命令用api的形式控制,没有用websocket是因为涉及到第三方等其他因素,相当于是用了workerman全家桶了

已经尝试过的方法

方案一:@xiuwang提出的

$client = stream_socket_client('udp://ip:port'); stream_socket_sendto($client, 'udp数据');

,,测试过,这种方案客户端无法收到数据,但现实发送成功

方案二:socket_sendto 方案也测试,客户端也没有办法收到数据

方案三:建立一个异步UDP客户端 [AsyncUdpConnection]、测试过,客户端也无法收到数据

方案四:引入swoole里面的协程UDP客户端,然后指定ip和port发送消息,客户端也没有收到

为此你搜索到了哪些方案及不适用的原因

以上方案不管适不适用,先当死马医,试了一遍,发现都不行,也可能是我方法不对,请教一下论坛各位大佬,有没有好的方案或者思路,希望不吝赐教。

最后的话:
workerman是真NB,之前做过一个TCP长连接项目,目前已经支持几万的设备了,毫无压力,这次是另外的项目,但可惜的是不是TCP而是UDP,不过这样也好,再次把workerman学习一遍。
用前也对比了一下swoole以及它孪生的hyperf、imi等,亲测后不管是开发成本还是对服务器的性能方面,还是workerman胜一筹,walkerNB。

639 4 2
4个回答

liziyu

walkerNB。

  • 暂无评论
remix789

不知道我理解的对不对哈

api发送数据给设备 然后redis存储标识 udp服务端接收数据 根据redis标识去匹配 然后存入异步队列

异步队列 将数据推送给websocket客户

  • sanye 2022-11-30

    谢答。现在我的问题不是发送消息给websocket客户,而是怎么在onMessage之外通过udp给客户发消息,IP和port都已经存储,现在是怎么发数据给设备的问题。

  • remix789 2022-11-30

    gatewayclinet 不可以么? 没搞过udp的 只搞过tcp的

  • sanye 2022-11-30

    GatewatWorker我之前做过项目很熟悉,只是这次的项目的硬件指定用UDP,没有办法。

  • remix789 2022-11-30

    无能为力, 坐等大佬回答 学习下

  • sanye 2022-11-30

    感谢,共同进步!

  • remix789 2022-11-30

    方案三:建立一个异步UDP客户端 [AsyncUdpConnection]、测试过,客户端也无法收到数据 先推给服务端,让服务端转发也不行?

  • sanye 2022-11-30

    这个貌似理论上没问题,我在onMessage里面保存$connection,然后收到用户侧的udp,再在onMessage里面去发送,稍等,我马上测试一下

  • sanye 2022-11-30

    成了,兄弟。

    在api接口里用AsyncUdpConnection连接udp服务器,然后发送命令,udp服务收到消息后区分api发送的数据还是设备发送的数据,然后按照api的消息再下发命令,直接搞定、

  • remix789 2022-11-30

    那就好, 主要是我没弄过udp的, 不了解

  • sanye 2022-11-30

    兄弟再问一下,有没有全局保存$connection的方案,我想在onMessage里面把$connection保存下来,然后在webman里面使用

  • 静默 2022-11-30

    保存的话保存到全局变量就行了。
    global $udpConnections;
    $udpConnections[$connection->id] = $connection;
    不要无限保存,长时间不用的$connection对象要close并且删除保存,否则内存里积攒的无用$connection对象越来越多,内存会上涨。

  • sanye 2022-11-30

    @静默 $udpConnections[$connection->id] = $connection; 这个不行

  • remix789 2022-11-30

    $udpConnections[$connection->getRemoteAddress()] = $connection; 这样

  • six 2022-11-30

    写一个函数

    function connection($key, $connection = null) {
        static $connections = [];
        if ($connection) {
            $connections[$key] = $connection;
        } 
        return $connections[$key]??null;
    }

    存connection的时候用 connection('连接标识', $connection);
    取connection的时候用 connection('连接标识');

  • sanye 2022-11-30

    这个只能在单进程中使用

  • six 2022-11-30

    业务处理如果都放在了队列里,感觉udp只用一个进程就能支持几万设备使用了。

    进程间这种资源类型的connection对象是共享不了的,多进udp程通讯也得像我那样存connection,然后再配合webman的channel插件。

    不过udp最好是单进程用,因为一个客户端发多个udp消息给服务端,这多个udp消息可能会被服务端的不同的进程收到,这会导致多个进程都有生成了$connection对象,但是多个$connection对象对应的客户端都是一个。如果你用channel分布式通讯,多个进程都存储了对应客户端的$connection对象,都推送数据的话,客户端收到的数据会是重复的。

  • sanye 2022-11-30

    不是,我上面的意思是:上卖弄的connection方法只能在workerman里面的udp进程里面使用,但是现在我想在webman里面(webman里面用workerman创建了udp服务)调用udp进程里面保存的$connection,这样的话上面的connection方法貌似就不行了

  • remix789 2022-11-30

    这个 connection 你拿到 workerman 外部是使用不了的, 服务端 拿到connection 去转发 命令

  • six 2022-11-30

    跨进程不能共享$connection。你可以udp服务内部再监听一个udp端口,用于内部调用。webman通过udp给这个内部端口发数据,内部端口收到数据后再调用$connection('标识')->send(); 就能给客户端发了

    use Workerman\Worker;
    class UdpServeice 
    {
        public function onWorkerStart()
        {
             // 监听给内部udp端口,用于内部通讯
             $inner_udp_service = new Worker('udp://127.0.0.1:7070');
             $inner_udp_service->onMessage = function($con, $data) {
                 $key = $data;// 假设data就是key标识
                 $this->connection($key)->send('一些数据');
             };
             $inner_udp_service->listen();
        }
    
        public function onMessage($connection, $data) {
            $key = $data; // 假设data就是key标识
            $this->connection($key, $connection);
        };
    
        function connection($key, $connection = null) {
            static $connections = [];
            if ($connection) {
                $connections[$key] = $connection;
            } 
            return $connections[$key]??null;
        }
    
    }

    假设webman里要给udp key为123的设备发数据,只需要调用

    $client = stream_socket_client('udp://127.0.0.1:7070');
    stream_socket_sendto($client, '123');

    整体逻辑应该就是这样

  • sanye 2022-11-30

    @remix789 好的,非常感谢

  • sanye 2022-11-30

    @six 非常非常感谢,经过我这愚笨的大脑CPU高速运转,终于明白了第一段代码的意思,这个方法确实不错,非常感谢。

    $client = stream_socket_client('udp://127.0.0.1:7070');
    stream_socket_sendto($client, '123');

    上面这段代码的意思我也明白了,问一个不相关的问题,stream_socket_client里面的IP地址可以直接填客户端udp的ip地址吗?然后通过stream_socket_sendto直接发消息给客户端的udp

  • six 2022-11-30

    如果调用stream_socket_client的服务器和udp服务器是在一个内网,应该可行

chaz6chez

你保存ip和port是没有用的,你的客户端没有实现服务端的功能,用ip+port没有办法发送成功的;服务端之所以可以给客户端发消息,是因为使用了客户端主动和服务端建立的连接,当然udp不存在建立连接的说法,但大致意思是一样的。

  • sanye 2022-11-30

    谢答,那我这个有什么解决方案吗?

  • chaz6chez 2022-11-30

    你可以参考webman/push的实现方式,也就是同一个服务监听两种协议和端口,监听udp的服务实现监听一个http,然后实现一个消息发布的接口,这个接口利用服务端保存的udp connection对udp客户端推送消息

  • chaz6chez 2022-11-30

    https://github.com/webman-php/push/blob/main/src/Server.php 132-138

    这里new了一个新的worker,并监听;分别实现了监听websocket和http,同理,你可以分别监听udp和http或者其他;

  • chaz6chez 2022-11-30

    他们本质上在一个进程内,所以你可以拿到udp的connection对象,进行发布

  • chaz6chez 2022-11-30

    本质上一个worker进程可以同时监听N个不同的协议,他们都会放在一个event-loop进行监听并回调执行触发,只要你分别保存各自协议的connection,就可以达到分别触达;但非必要不建议“集大成”,最好还是各自管理。

WatcherLuo

同一个局域网内客户端监听一个UDP端口,这样知道ip和port就能发,如果不在一个局域网那就要用p2p穿透了。

  • 暂无评论
🔝