PHP实现系统编程(四)--- 本地套接字(Unix Domain Socket)

Socket API一开始是为了解决网络通讯而设计的,而后来在此之上又衍生出一种叫做本地套接字(Unix Domain Socket)的技术,本地套接字顾名思义,只支持本地的两个进程之间进行通信,虽然网络套接字(Internet Domain Socket)也可以通过本地回环地址(127.0.0.1)来实现本地进程间通信,但由于本地套接字不需要经过网络协议栈,封包拆包、计算校验和等操作,所以效率上相比网络套接字有一定的优势。由于本地套接字性能高、稳定、支持非血缘关系的进程间通讯,所以本地套接字也是当下使用最广泛的IPC(进程间通信)的机制之一。

Nginx 与 PHP-FPM 之间使用网络套接字(127.0.0.1:9000)和使用本地套接字两种通信方式的性能对比 

一般我们都是让 PHP-FPM 监听 127.0.0.1:9000 ,显然这时 Nginx 与 PHP-FPM 是通过网络套接字来实现通讯的,其实,如果 Nginx和 PHP-FPM运行在同一台服务器上,我们还可以让 PHP-FPM监听本地套接字,接下来就针对这两种方式的性能做一简单的比较。

这里我的Nginx开启两个worker进程

[root@localhost ~]# ps -ef | grep nginx
root      1838     1  0 22:48 ?        00:00:00 nginx: master process /usr/sbin/nginx -c /etc/nginx/nginx.conf
nginx     1839  1838  0 22:48 ?        00:00:00 nginx: worker process                   
nginx     1840  1838  0 22:48 ?        00:00:00 nginx: worker process                   
root      1851  1797  0 22:49 pts/0    00:00:00 grep nginx

 

使用网络套接字,Nginx和PHP-FPM配置分别如下:


 


压力测试test.php脚本

<?php

phpinfo();


压测结果:




再来看看 Nginx和PHP-FPM 用本地套接字方式的通信,Nginx 和 PHP-FPM 的配置稍作修改:





压测结果:



以上测试都是多次压测后取得结果,从结果可以看到,本地套接字要比网络套接字的QPS平均高了100多,和我们预期基本一致。


PHP的本地套接字编程

其实PHP的本地套接字编程和网络套接字基本一致,只是传的参数不一样。

PHP为socket编程提供了两套API,一套是 socket_* 系列方法,这在我们前面的系列文章里演示过了,另一套是 stream_socket_* 系列方法,而后者使用起来更加的方便,这里我们采用后者来演示。

stream_socket_*  方法列表:

•stream_socket_accept — 接受由 stream_socket_server 创建的套接字连接
•stream_socket_client — Open Internet or Unix domain socket connection
•stream_socket_enable_crypto — Turns encryption on/off on an already connected socket
•stream_socket_get_name — 获取本地或者远程的套接字名称
•stream_socket_pair — 创建一对完全一样的网络套接字连接流
•stream_socket_recvfrom — Receives data from a socket, connected or not
•stream_socket_sendto — Sends a message to a socket, whether it is connected or not
•stream_socket_server — Create an Internet or Unix domain server socket
•stream_socket_shutdown — Shutdown a full-duplex connection

具体方法的使用请参阅PHP手册,这里直接演示代码:

server端代码:

<?php
//stream_server.php

$sockfile = '/dev/shm/unix.sock';
// 如果sock文件已存在,先尝试删除
if (file_exists($sockfile))
{
    unlink($sockfile);
}

$server = stream_socket_server("unix://$sockfile", $errno, $errstr);

if (!$server)
{
        die("创建unix domain socket fail: $errno - $errstr");
}

while(1)
{
        $conn = stream_socket_accept($server, 5);

        if ($conn)
        {
                while(1)
                {
                    $msg = fread($conn, 1024);
                    if (strlen($msg) == 0) //客户端关闭
                    {
                        fclose($conn);
                        break;
                    }
                    echo "read data: $msg";
                    fwrite($conn, "read ok!");
                }
        }

}
fclose($server);


client端代码:

<?php
//stream_client.php

$client = stream_socket_client("unix:///dev/shm/unix.sock", $errno, $errstr);

if (!$client)
{
        die("connect to server fail: $errno - $errstr");
}

while(1)
{
    $msg = fread(STDIN, 1024);

    if ($msg == "quit\n")
    {
        break;
    }

    fwrite($client, $msg);
    $rt = fread($client, 1024);

    echo $rt . "\n";
}

fclose($client);


运行

server端:

[root@localhost html]# php stream_server.php 
read data: hello unix domain socket
read data: are you ok?
read data: I'm fine!
PHP Warning:  stream_socket_accept(): accept failed: Connection timed out in /usr/share/nginx/html/stream_server.php on line 13
PHP Warning:  stream_socket_accept(): accept failed: Connection timed out in /usr/share/nginx/html/stream_server.php on line 13
PHP Warning:  stream_socket_accept(): accept failed: Connection timed out in /usr/share/nginx/html/stream_server.php on line 13
PHP Warning:  stream_socket_accept(): accept failed: Connection timed out in /usr/share/nginx/html/stream_server.php on line 13
PHP Warning:  stream_socket_accept(): accept failed: Connection timed out in /usr/share/nginx/html/stream_server.php on line 13
PHP Warning:  stream_socket_accept(): accept failed: Connection timed out in /usr/share/nginx/html/stream_server.php on line 13

client端:

[root@localhost html]# php stream_client.php 
hello unix domain socket
read ok!
are you ok?
read ok!
I'm fine! 
read ok!
^C

以上是一个最简单的本地套接字的代码演示,细心的读者可能注意到了server端报的warning,服务器如果长时间没有客户端过来连接,超过了stream_socket_accept 方法设置的timeout,服务器端便会报这个警告,事实上,真正的服务端代码是不会是像这样写的,因为这种方式同一时间只能处理一个客户端连接,如果要实现并发,一种方式就是使用IO多路复用,如同 socket_* 系列方法中有socket_select 方法 (参考系列文章第一篇http://blog.csdn.net/zhang197093/article/details/77366407),stream_socket_* 系列方法提供了 stream_select 方法来实现多路复用,使用方法也很相似。

int stream_select ( array &$read , array &$write , array &$except , int $tv_sec [, int $tv_usec = 0 ] )

The stream_select() function accepts arrays of streams and waits for them to change status. Its operation is equivalent to that of the socket_select() function except in that it acts on streams.

详细的方法介绍请参考PHP手册 : http://php.net/manual/zh/function.stream-select.php

优化后的代码如下:

<?php
//stream_server.php

$sockfile = '/dev/shm/unix.sock';
// 如果sock文件已存在,先尝试删除
if (file_exists($sockfile))
{
    unlink($sockfile);
}

$server = stream_socket_server("unix://$sockfile", $errno, $errstr);


if (!$server)
{
        die("创建unix domain socket fail: $errno - $errstr");
}

$listen_reads = array($server);
$listen_writes = array();
$listen_excepts = NULL;

while(1)
{
        $can_reads = $listen_reads;
        $can_writes = $listen_writes;
        $num_streams = stream_select($can_reads, $can_writes, $listen_excepts, 0);

        if ($num_streams)
        {
                foreach ($can_reads as &$sock)
                {
                        if ($server == $sock)
                        {
                                $conn = stream_socket_accept($server, 5); //此时一定存在客户端连接,不会有超时的情况
                                if ($conn)
                                {
                                        // 把客户端连接加入监听
                                        $listen_reads[] = $conn;
                                        $listen_writes[] = $conn;
                                }
                        }
                        else
                        {
                                $msg = fread($sock, 1024);  //此时一定是可读的
                                if (strlen($msg) == 0) //读取到0个字符,说明客户端关闭
                                {
                                        fclose($sock);
                                        // 从sock监听中移除
                                        $key = array_search($sock, $listen_reads);
                                        unset($listen_reads[$key]);
                                        $key = array_search($sock, $listen_writes);
                                        unset($listen_writes[$key]);
                                        echo "客户端关闭\n";
                                }
                                else
                                {
                                   echo "read data: $msg";
                                    // 是否可写
                                    if (in_array($sock, $can_writes))
                                    {
                                        fwrite($conn, "read ok!");
                                    }
                                }
                        }
                }
        }


}
fclose($server);

此时这个server就不会有前面那个Warning了,并且支持并发

[root@localhost html]# php stream_server.php 
read data: hello world
read data: hello unix domain socket
read data: harry up
read data: 
read data: 
read data: I'm another client
客户端关闭
客户端关闭
read data: I'm the third client
客户端关闭

That‘s all!

  • 3
    点赞
  • 2
    收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:书香水墨 设计师:CSDN官方博客 返回首页
评论 2

打赏作者

代码一天不写我浑森蓝廋

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值