使用PHP创建一个socket服务端

  与常规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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
<?php
 
    class Worker{
        //监听socket
        protected $socket = NULL;
 
        //所有的socket连接
        protected $allSockets = array();
 
        //连接事件回调
        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 = array();
                //监听所有的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

 

posted @   郭延龙  阅读(2277)  评论(3编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示