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

原文:http://blog.csdn.net/zhang197093/article/details/78143687?locationNum=6&fps=1

--------------------------------------------------------------------------------------------------------------------------------------------------------

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进程

[plain] view plain copy

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

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

压力测试test.php脚本

[php] view plain copy

  1. <?php
  2. phpinfo();

压测结果:

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

压测结果:

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

PHP的本地套接字编程

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

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

stream_socket_*  方法列表:

[plain] view plain copy

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

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

server端代码:

[php] view plain copy

  1. <?php
  2. //stream_server.php
  3. $sockfile = ‘/dev/shm/unix.sock‘;
  4. // 如果sock文件已存在,先尝试删除
  5. if (file_exists($sockfile))
  6. {
  7. unlink($sockfile);
  8. }
  9. $server = stream_socket_server("unix://$sockfile", $errno, $errstr);
  10. if (!$server)
  11. {
  12. die("创建unix domain socket fail: $errno - $errstr");
  13. }
  14. while(1)
  15. {
  16. $conn = stream_socket_accept($server, 5);
  17. if ($conn)
  18. {
  19. while(1)
  20. {
  21. $msg = fread($conn, 1024);
  22. if (strlen($msg) == 0) //客户端关闭
  23. {
  24. fclose($conn);
  25. break;
  26. }
  27. echo "read data: $msg";
  28. fwrite($conn, "read ok!");
  29. }
  30. }
  31. }
  32. fclose($server);

client端代码:

[php] view plain copy

  1. <?php
  2. //stream_client.php
  3. $client = stream_socket_client("unix:///dev/shm/unix.sock", $errno, $errstr);
  4. if (!$client)
  5. {
  6. die("connect to server fail: $errno - $errstr");
  7. }
  8. while(1)
  9. {
  10. $msg = fread(STDIN, 1024);
  11. if ($msg == "quit\n")
  12. {
  13. break;
  14. }
  15. fwrite($client, $msg);
  16. $rt = fread($client, 1024);
  17. echo $rt . "\n";
  18. }
  19. fclose($client);

运行

server端:

[plain] view plain copy

  1. [[email protected] html]# php stream_server.php
  2. read data: hello unix domain socket
  3. read data: are you ok?
  4. read data: I‘m fine!
  5. PHP Warning:  stream_socket_accept(): accept failed: Connection timed out in /usr/share/nginx/html/stream_server.php on line 13
  6. PHP Warning:  stream_socket_accept(): accept failed: Connection timed out in /usr/share/nginx/html/stream_server.php on line 13
  7. PHP Warning:  stream_socket_accept(): accept failed: Connection timed out in /usr/share/nginx/html/stream_server.php on line 13
  8. PHP Warning:  stream_socket_accept(): accept failed: Connection timed out in /usr/share/nginx/html/stream_server.php on line 13
  9. PHP Warning:  stream_socket_accept(): accept failed: Connection timed out in /usr/share/nginx/html/stream_server.php on line 13
  10. PHP Warning:  stream_socket_accept(): accept failed: Connection timed out in /usr/share/nginx/html/stream_server.php on line 13

client端:

[plain] view plain copy

  1. [[email protected] html]# php stream_client.php
  2. hello unix domain socket
  3. read ok!
  4. are you ok?
  5. read ok!
  6. I‘m fine!
  7. read ok!
  8. ^C

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

[php] view plain copy

  1. 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] view plain copy

  1. <?php
  2. //stream_server.php
  3. $sockfile = ‘/dev/shm/unix.sock‘;
  4. // 如果sock文件已存在,先尝试删除
  5. if (file_exists($sockfile))
  6. {
  7. unlink($sockfile);
  8. }
  9. $server = stream_socket_server("unix://$sockfile", $errno, $errstr);
  10. if (!$server)
  11. {
  12. die("创建unix domain socket fail: $errno - $errstr");
  13. }
  14. $listen_reads = array($server);
  15. $listen_writes = array();
  16. $listen_excepts = NULL;
  17. while(1)
  18. {
  19. $can_reads = $listen_reads;
  20. $can_writes = $listen_writes;
  21. $num_streams = stream_select($can_reads, $can_writes, $listen_excepts, 0);
  22. if ($num_streams)
  23. {
  24. foreach ($can_reads as &$sock)
  25. {
  26. if ($server == $sock)
  27. {
  28. $conn = stream_socket_accept($server, 5); //此时一定存在客户端连接,不会有超时的情况
  29. if ($conn)
  30. {
  31. // 把客户端连接加入监听
  32. $listen_reads[] = $conn;
  33. $listen_writes[] = $conn;
  34. }
  35. }
  36. else
  37. {
  38. $msg = fread($sock, 1024);  //此时一定是可读的
  39. if (strlen($msg) == 0) //读取到0个字符,说明客户端关闭
  40. {
  41. fclose($sock);
  42. // 从sock监听中移除
  43. $key = array_search($sock, $listen_reads);
  44. unset($listen_reads[$key]);
  45. $key = array_search($sock, $listen_writes);
  46. unset($listen_writes[$key]);
  47. echo "客户端关闭\n";
  48. }
  49. else
  50. {
  51. echo "read data: $msg";
  52. // 是否可写
  53. if (in_array($sock, $can_writes))
  54. {
  55. fwrite($conn, "read ok!");
  56. }
  57. }
  58. }
  59. }
  60. }
  61. }
  62. fclose($server);

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

[plain] view plain copy

  1. [[email protected] html]# php stream_server.php
  2. read data: hello world
  3. read data: hello unix domain socket
  4. read data: harry up
  5. read data:
  6. read data:
  7. read data: I‘m another client
  8. 客户端关闭
  9. 客户端关闭
  10. read data: I‘m the third client
  11. 客户端关闭

That‘s all!

版权声明:本文为博主原创文章,未经博主允许不得转载。 //blog.csdn.net/zhang197093/article/details/78143687

原文地址:https://www.cnblogs.com/oxspirt/p/8568835.html

时间: 2024-10-08 11:43:20

【转】PHP实现系统编程(四)--- 本地套接字(Unix Domain Socket)的相关文章

《网络编程》原始套接字 ---ping程序实现

概述 基于字节流套接字(SOCK_STREAM)和数据报套接字(SOCK_DGRAM)不可以访问传输层协议,只是对应用层的报文进行操作,传输层的数据报格式都是由系统提供的协议栈实现,用户只需要填充相应的应用层报文,由系统完成底层报文首部的填充并发送.原始套接字(SOCK_RAW)可以访问位于基层的传输层协议,原始套接字没有端口号. 原始套接字(SOCK_RAW)是一种不同于 SOCK_STREAM.SOCK_DGRAM 的套接字,它实现于系统核心.原始套接字使进程可以读与写 ICMP.IGMP

Linux网络编程:原始套接字的魔力【上】

基于原始套接字编程 在开发面向连接的TCP和面向无连接的UDP程序时,我们所关心的核心问题在于数据收发层面,数据的传输特性由TCP或UDP来保证: 也就是说,对于TCP或UDP的程序开发,焦点在Data字段,我们没法直接对TCP或UDP头部字段进行赤裸裸的修改,当然还有IP头.换句话说,我们对它们头部操作的空间非常受限,只能使用它们已经开放给我们的诸如源.目的IP,源.目的端口等等. 今天我们讨论一下原始套接字的程序开发,用它作为入门协议栈的进阶跳板太合适不过了.OK闲话不多说,进入正题. 原始

Unix网络编程随手记——套接字接口函数

套接字接口(socket interface)是一组函数,它们和Unix I/O函数结合起来,用以创建网络应用.大多数现代系统上都实现套接字接口,包括所有的Unix变种.Windows和Macintosh. 1.套接字的基本结构 struct sockaddr 这个结构用来存储套接字地址. 数据定义: 1 struct sockaddr 2 { 3 unsigned short sa_family; /* address族, AF_xxx */ 4 char sa_data[14]; /* 14

socket IPC(本地套接字 domain)

1.简介 socket API原本是为网络通讯设计的,但后来在socket的框架上发展出一种IPC机制,就是UNIX Domain Socket.虽然网络socket也可用于同一台主机的进程间通讯(通过loopback地址127.0.0.1),但是UNIX Domain Socket用于IPC更有效率:不需要经过网络协议栈,不需要打包拆包.计算校验和.维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程.这是因为,IPC机制本质上是可靠的通讯,而网络协议是为不可靠的通讯设计的.UNIX

Unix网络编程--卷一:套接字联网API 读书笔记

UNIX网络编程--卷一:套接字联网API 本书面对的读者是那些希望自己编写的程序能够使用成为套接字(socket)的API进行彼此通信的人. 目录: 1.简介 2.传输层:TCP.UDP和SCTP 3.套接字编程简介 4.基本TCP套接字编程 5.TCP客户/服务器程序例子 6.I/O复用:select和poll函数 7.套接字选项 8.基本UDP套接字编程 9.基本SCTP套接字编程 10.SCTP客户/服务器程序例子 11.名字与地址转换 12.IPV4与IPV6互操作性 13.守护进程和

(一)网络编程基础之套接字入门

套接字基础 首先,我们来思考下这样一个问题:为什么要使用套接字进行网络编程? 答:Linux环境下使用套接字进行进程之间的通信.套接字接口(socket interface)是一组函数,也是操作系统提供给应用程序的接口.在Unix系统中,套接字和Unix I/O函数结合起来,用来创建网络应用程序.(也就是说,操作系统对外只提供了套接字作为网络通信的接口,假如想进行网络通信,套接字我们用也得用,不用也得用,而且使用套接字来进行网络通信是十分通用的方法).这里最典型的就是客户端--服务器模型. 因特

《网络编程》路由套接字

概述 Unix 系统集成了路由功能,它包含相应的路由数据库可提供的路由信息,用户可以通过命令方式来增加.修改以及删除路由表中的项目,也可以只查看路由表的信息.在创建套接字时,可以通过指定参数 AF_ROUTE 域创建路由套接字,路由套接字可以访问内核中路由子系统的接口信息.路由套接字上支持 3 种类型的操作: 进程通过写到路由套接字向内核发送消息: 进程通过读入路由套接字接收来自内核的消息: 进程调用 sysctl 函数获取路由表或列出所有已配置的接口: 数据链路地址结构 struct sock

【Unix网络编程】chapter3 套接字编程简介

chapter3套接字编程简介3.1 概述 地址转换函数在地址的文本表达和他们存放在套接字地址结构中的二进制值之间进行转换.多数现存的IPv4代码使用inet_addr和inet_ntoa这两个函数,不过这两个新函数inet_pton和inet_ntop同时适用于IPv4和IPv6. 3.2 套接字地址结构 sockaddr_ 3.2.1 IPv4套接字地址结构 IPv4套接字地址结构通常也称为"网际套接字地址结构",它以sockaddr_in命令,定义在<netinet/in.

Socket编程实践(9) --套接字IO超时设置方法

引:超时设置3种方案 1. alarm超时设置方法 //代码实现: 这种方式较少用 void sigHandlerForSigAlrm(int signo) { return ; } signal(SIGALRM, sigHandlerForSigAlrm); alarm(5); int ret = read(sockfd, buf, sizeof(buf)); if (ret == -1 && errno == EINTR) { // 超时被时钟打断 errno = ETIMEDOUT;