«

使用PHP stream系列函数创建一个socket服务端

时间:2024-2-20 11:34     作者:韩俊     分类: PHP


与常规web开发不同,使用socket开发可以摆脱http的限制。可自定义协议,使用长连接、PHP代码常驻内存等。学习资料来源于workerman官方视频与文档.

通常创建一个socket服务包括这几个简单的步骤:

1.创建一个socket套接字,监听在某协议的某个端口,如:tcp的9865端口,为了是外网可以访问,地址为0.0.0.0,监听地址应为这种格式tcp://0.0.0.0:9865

2.将监听socket设置为非阻塞,若不设置,程序会在客户端连接没有发消息时阻塞。

3.程序阻塞在I/0复用函数stream_select,一旦有读取到的新事件则进行处理。

4.处理客户端发送的数据。若读取的socket连接为监听socket表示有新的连接,否则为当前连接发送了数据。若读取到空或返回了false,则表示客户端断开。

一个简单的demo:

<?php

class Worker {
    //监听socket
    protected $socket = NULL;

    //所有的socket连接
    protected $allSockets = [];

    //连接事件回调
    public $onConnect = NULL;

    //断线事件回调
    public $onClose = NULL;

    //接收消息事件回调
    public $onMessage = NULL;

    public function __construct($socket_address) {
        //创建一个socket监听
        $this->socket = stream_socket_server($socket_address);

        //设置为非阻塞
        stream_set_blocking($this->socket, 0);

        //将socket监听加入allSockets
        $this->allSockets[(int)$this->socket] = $this->socket;
    }

    public function run() {
        while (true) {
            //不监听可写事件与带外数据事件
            $write = $except = [];
            //监听所有的socket事件
            $read = $this->allSockets;
            //整个进程阻塞在这里,持续监听可读事件
            //此处参数均为引用传递,在函数中会改变传值
            stream_select($read, $write, $except, 60);

            //处理所有可读事件
            foreach ($read as $index => $socket) {
                //如果是监听socket,此处表示有新的连接
                if ($socket === $this->socket) {
                    //通过stream_socket_accept获取新的连接
                    $new_conn_socket = stream_socket_accept($socket);

                    if ($this->onConnect) {
                        //触发连接事件的回调,并将当前连接传递给回掉函数
                        call_user_func($this->onConnect, $socket);
                    }
                    //记录此socket连接,以便于sream_select监听可读事件
                    $this->allSockets[(int)$new_conn_socket] = $new_conn_socket;
                } else //如果可读事件不为监听socket,则表示对应客户端有数据发过来
                {
                    //从连接中读取数据
                    $buffer = fread($socket, 65535);
                    //如果数据为空,表示客户端已经断开连接
                    if ('' === $buffer || false === $buffer) {
                        //尝试触发onClose回调
                        if ($this->onClose) {
                            call_user_func($this->onClose, $socket);
                        }
                        fclose($socket);
                        //关闭socket连接并从allSockets中删除
                        unset($this->allSockets[(int)$socket]);
                        continue;
                    }
                    //表示一个正常的连接,已经读取到消息,交给回掉函数处理
                    if ($this->onMessage) {
                        call_user_func($this->onMessage, $socket, $buffer);
                    }
                }
            }
        }
    }
}

$worker = new Worker('tcp://0.0.0.0:9865');

$worker->onConnect = function ($conn) {
    echo '新的连接来了';
};
$worker->onClose = function ($conn) {
    echo '连接断开了';
};
$worker->onMessage = function ($conn, $message) {
    $http_resonse = "HTTP/1.1 200 OK\r\n";
    $http_resonse .= "Connection: keep-alive\r\n";
    $http_resonse .= "Server: php socket server\r\n";
    $http_resonse .= "Content-length: 11\r\n\r\n";
    $http_resonse .= "hello world";
    fwrite($conn, $http_resonse);
};

$worker->run();

在cli环境下运行脚本:$ php worker.php

然后使用浏览器访问本地的9865端口即可看到我们的 hello world

标签: php php教程

热门推荐