本篇文章給大家分享的內容是關於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!
相关推荐:
以上是PHP實作系統程式設計之本地套接字(Unix Domain Socket)的詳細內容。更多資訊請關注PHP中文網其他相關文章!