PHP实现系统编程(一) --- 网络Socket及IO多路复用【网摘】

一直以来,PHP很少用于socket编程,毕竟是一门脚本语言,效率会成为很大的瓶颈,但是不能说PHP就无法用于socket编程,也不能说PHP的socket编程性能就有多么的低,例如知名的一款PHP socket框架 workerman 就是用纯PHP开发,并且号称拥有优秀的性能,所以在某些环境下,PHP socket编程或许也可一展身手。

PHP提供了一系列类似C语言socket库中的方法供我们调用:

[php] view plain copy

  1. socket_accept — Accepts a connection on a socket
  2. socket_bind — 给套接字绑定名字
  3. socket_clear_error — 清除套接字或者最后的错误代码上的错误
  4. socket_close — 关闭套接字资源
  5. socket_cmsg_space — Calculate message buffer size
  6. socket_connect — 开启一个套接字连接
  7. socket_create_listen — Opens a socket on port to accept connections
  8. socket_create_pair — Creates a pair of indistinguishable sockets and stores them in an array
  9. socket_create — 创建一个套接字(通讯节点)
  10. socket_get_option — Gets socket options for the socket
  11. socket_getopt — 别名 socket_get_option
  12. socket_getpeername — Queries the remote side of the given socket which may either result in host/port or in a Unix filesystem path, dependent on its type
  13. socket_getsockname — Queries the local side of the given socket which may either result in host/port or in a Unix filesystem path, dependent on its type
  14. socket_import_stream — Import a stream
  15. socket_last_error — Returns the last error on the socket
  16. socket_listen — Listens for a connection on a socket
  17. socket_read — Reads a maximum of length bytes from a socket
  18. socket_recv — 从已连接的socket接收数据
  19. socket_recvfrom — Receives data from a socket whether or not it is connection-oriented
  20. socket_recvmsg — Read a message
  21. socket_select — Runs the select() system call on the given arrays of sockets with a specified timeout
  22. socket_send — Sends data to a connected socket
  23. socket_sendmsg — Send a message
  24. socket_sendto — Sends a message to a socket, whether it is connected or not
  25. socket_set_block — Sets blocking mode on a socket resource
  26. socket_set_nonblock — Sets nonblocking mode for file descriptor fd
  27. socket_set_option — Sets socket options for the socket
  28. socket_setopt — 别名 socket_set_option
  29. socket_shutdown — Shuts down a socket for receiving, sending, or both
  30. socket_strerror — Return a string describing a socket error
  31. socket_write — Write to a socket

更多细节请查看PHP关于socket的官方手册:http://php.net/manual/zh/book.sockets.php

一个简单的TCP服务器示例 phptcpserver.php :

[php] view plain copy

  1. <?php
  2. $servsock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);  // 创建一个socket
  3. if (FALSE === $servsock)
  4. {
  5. $errcode = socket_last_error();
  6. fwrite(STDERR, "socket create fail: " . socket_strerror($errcode));
  7. exit(-1);
  8. }
  9. if (!socket_bind($servsock, ‘127.0.0.1‘, 8888))    // 绑定ip地址及端口
  10. {
  11. $errcode = socket_last_error();
  12. fwrite(STDERR, "socket bind fail: " . socket_strerror($errcode));
  13. exit(-1);
  14. }
  15. if (!socket_listen($servsock, 128))      // 允许多少个客户端来排队连接
  16. {
  17. $errcode = socket_last_error();
  18. fwrite(STDERR, "socket listen fail: " . socket_strerror($errcode));
  19. exit(-1);
  20. }
  21. while (1)
  22. {
  23. $connsock = socket_accept($servsock);  //响应客户端连接
  24. if ($connsock)
  25. {
  26. socket_getpeername($connsock, $addr, $port);  //获取连接过来的客户端ip地址和端口
  27. echo "client connect server: ip = $addr, port = $port" . PHP_EOL;
  28. while (1)
  29. {
  30. $data = socket_read($connsock, 1024);  //从客户端读取数据
  31. if ($data === ‘‘)
  32. {
  33. //客户端关闭
  34. socket_close($connsock);
  35. echo "client close" . PHP_EOL;
  36. break;
  37. }
  38. else
  39. {
  40. echo ‘read from client:‘ . $data;
  41. $data = strtoupper($data);  //小写转大写
  42. socket_write($connsock, $data);  //回写给客户端
  43. }
  44. }
  45. }
  46. }
  47. socket_close($servsock);

启动这个服务器:

[plain] view plain copy

  1. [[email protected] php]# php phptcpserver.php

之后这个服务器就一直阻塞在那里,等待客户端连接,我们可以用telnet命令来连接这个服务器:

[plain] view plain copy

  1. [[email protected] ~]# telnet 127.0.0.1 8888
  2. Trying 127.0.0.1...
  3. Connected to 127.0.0.1.
  4. Escape character is ‘^]‘.
  5. ajdjajksdjkaasda
  6. AJDJAJKSDJKAASDA
  7. 小明哈哈哈哈笑
  8. 小明哈哈哈哈笑
  9. 小明efsfsdfsdf了哈哈哈
  10. 小明EFSFSDFSDF了哈哈哈

服务器端输出:

[plain] view plain copy

  1. [[email protected] php]# php phptcpserver.php
  2. client connect server: ip = 127.0.0.1, port = 50398
  3. read from client:ajdjajksdjkaasda
  4. read from client:小明哈哈哈哈笑
  5. read from client:小明efsfsdfsdf了哈哈哈

但其实这个TCP服务器是有问题的,它一次只能处理一个客户端的连接和数据传输,这是因为一个客户端连接过来后,进程就去负责读写客户端数据,当客户端没有传输数据时,tcp服务器处于阻塞读状态,无法再去处理其他客户端的连接请求了。

解决这个问题的一种办法就是采用多进程服务器,每当一个客户端连接过来,服务器开一个子进程专门负责和该客户端的数据传输,而父进程仍然监听客户端的连接,但是起进程的代价是昂贵的,这种多进程的机制显然支撑不了高并发。

另一个解决办法是使用IO多路复用机制,使用php为我们提供的socket_select方法,它可以监听多个socket,如果其中某个socket状态发生了改变,比如从不可写变为可写,从不可读变为可读,这个方法就会返回,从而我们就可以去处理这个socket,处理客户端的连接,读写操作等等。来看php文档中对该socket_select的介绍

[plain] view plain copy

  1. socket_select — Runs the select() system call on the given arrays of sockets with a specified timeout
  2. 说明
  3. int socket_select ( array &$read , array &$write , array &$except , int $tv_sec [, int $tv_usec = 0 ] )
  4. socket_select() accepts arrays of sockets and waits for them to change status.
  5. Those coming with BSD sockets background will recognize that those socket resource arrays are in fact the so-called file descriptor sets.
  6. Three independent arrays of socket resources are watched.
  7. You do not need to pass every array to socket_select(). You can leave it out and use an empty array or NULL instead.
  8. Also do not forget that those arrays are passed by reference and will be modified after socket_select() returns.
  9. 返回值
  10. On success socket_select() returns the number of socket resources contained in the modified arrays,
  11. which may be zero if the timeout expires before anything interesting happens.
  12. On error FALSE is returned. The error code can be retrieved with socket_last_error().

大致翻译下:

socket_select  ---  在给定的几组sockets数组上执行 select() 系统调用,用一个特定的超时时间。

socket_select() 接受几组sockets数组作为参数,并监听它们改变状态

这些基于BSD scokets 能够识别这些socket资源数组实际上就是文件描述符集合。

三个不同的socket资源数组会被同时监听。

这三个资源数组不是必传的, 你可以用一个空数组或者NULL作为参数,不要忘记这三个数组是以引用的方式传递的,在函数返回后,这些数组的值会被改变。

socket_select() 调用成功返回这三个数组中状态改变的socket总数,如果设置了timeout,并且在timeout之内都没有状态改变,这个函数将返回0,出错时返回FALSE,可以用socket_last_error() 获取错误码。

使用 socket_select() 优化之前 phptcpserver.php 代码:

[php] view plain copy

  1. <?php
  2. $servsock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);  // 创建一个socket
  3. if (FALSE === $servsock)
  4. {
  5. $errcode = socket_last_error();
  6. fwrite(STDERR, "socket create fail: " . socket_strerror($errcode));
  7. exit(-1);
  8. }
  9. if (!socket_bind($servsock, ‘127.0.0.1‘, 8888))    // 绑定ip地址及端口
  10. {
  11. $errcode = socket_last_error();
  12. fwrite(STDERR, "socket bind fail: " . socket_strerror($errcode));
  13. exit(-1);
  14. }
  15. if (!socket_listen($servsock, 128))      // 允许多少个客户端来排队连接
  16. {
  17. $errcode = socket_last_error();
  18. fwrite(STDERR, "socket listen fail: " . socket_strerror($errcode));
  19. exit(-1);
  20. }
  21. /* 要监听的三个sockets数组 */
  22. $read_socks = array();
  23. $write_socks = array();
  24. $except_socks = NULL;  // 注意 php 不支持直接将NULL作为引用传参,所以这里定义一个变量
  25. $read_socks[] = $servsock;
  26. while (1)
  27. {
  28. /* 这两个数组会被改变,所以用两个临时变量 */
  29. $tmp_reads = $read_socks;
  30. $tmp_writes = $write_socks;
  31. // int socket_select ( array &$read , array &$write , array &$except , int $tv_sec [, int $tv_usec = 0 ] )
  32. $count = socket_select($tmp_reads, $tmp_writes, $except_socks, NULL);  // timeout 传 NULL 会一直阻塞直到有结果返回
  33. foreach ($tmp_reads as $read)
  34. {
  35. if ($read == $servsock)
  36. {
  37. /* 有新的客户端连接请求 */
  38. $connsock = socket_accept($servsock);  //响应客户端连接, 此时不会造成阻塞
  39. if ($connsock)
  40. {
  41. socket_getpeername($connsock, $addr, $port);  //获取远程客户端ip地址和端口
  42. echo "client connect server: ip = $addr, port = $port" . PHP_EOL;
  43. // 把新的连接sokcet加入监听
  44. $read_socks[] = $connsock;
  45. $write_socks[] = $connsock;
  46. }
  47. }
  48. else
  49. {
  50. /* 客户端传输数据 */
  51. $data = socket_read($read, 1024);  //从客户端读取数据, 此时一定会读到数组而不会产生阻塞
  52. if ($data === ‘‘)
  53. {
  54. //移除对该 socket 监听
  55. foreach ($read_socks as $key => $val)
  56. {
  57. if ($val == $read) unset($read_socks[$key]);
  58. }
  59. foreach ($write_socks as $key => $val)
  60. {
  61. if ($val == $read) unset($write_socks[$key]);
  62. }
  63. socket_close($read);
  64. echo "client close" . PHP_EOL;
  65. }
  66. else
  67. {
  68. socket_getpeername($read, $addr, $port);  //获取远程客户端ip地址和端口
  69. echo "read from client # $addr:$port # " . $data;
  70. $data = strtoupper($data);  //小写转大写
  71. if (in_array($read, $tmp_writes))
  72. {
  73. //如果该客户端可写 把数据回写给客户端
  74. socket_write($read, $data);
  75. }
  76. }
  77. }
  78. }
  79. }
  80. socket_close($servsock);

现在,这个TCP服务器就可以支持多个客户端同时连接了,测试下:

服务器端:

[plain] view plain copy

  1. [[email protected] php]# php phptcpserver.php
  2. client connect server: ip = 127.0.0.1, port = 50404
  3. read from client # 127.0.0.1:50404 # hello world
  4. client connect server: ip = 127.0.0.1, port = 50406
  5. read from client # 127.0.0.1:50406 # hello PHP
  6. read from client # 127.0.0.1:50404 # 少小离家老大回
  7. read from client # 127.0.0.1:50404 # 乡音无改鬓毛衰
  8. read from client # 127.0.0.1:50406 # 老当益壮,
  9. read from client # 127.0.0.1:50406 # 宁移白首之心
  10. client close
  11. client connect server: ip = 127.0.0.1, port = 50408

稍微修改上面的服务器返回,返回一个HTTP响应头和一个简单的HTTP响应体,这样就摇身一变成了一个最简单的HTTP服务器:

[php] view plain copy

  1. ....
  2. socket_getpeername($read, $addr, $port);  //获取远程客户端ip地址和端口
  3. echo "read from client # $addr:$port # " . $data;
  4. $response = "HTTP/1.1 200 OK\r\n";
  5. $response .= "Server: phphttpserver\r\n";
  6. $response .= "Content-Type: text/html\r\n";
  7. $response .= "Content-Length: 3\r\n\r\n";
  8. $response .= "ok\n";
  9. if (in_array($read, $tmp_writes))
  10. {
  11. //如果该客户端可写 把数据回写给客户端
  12. socket_write($read, $response);
  13. socket_close($read);  // 主动关闭客户端连接
  14. //移除对该 socket 监听
  15. foreach ($read_socks as $key => $val)
  16. {
  17. if ($val == $read) unset($read_socks[$key]);
  18. }
  19. foreach ($write_socks as $key => $val)
  20. {
  21. if ($val == $read) unset($write_socks[$key]);
  22. }
  23. }
  24. .....

重新启动该服务器,用curl模拟请求该http服务器:

[plain] view plain copy

  1. [[email protected] ~]# curl ‘127.0.0.1:8888‘
  2. ok
  3. [[email protected] ~]# curl ‘127.0.0.1:8888‘
  4. ok
  5. [[email protected] ~]# curl ‘127.0.0.1:8888‘
  6. ok
  7. [[email protected] ~]# curl ‘127.0.0.1:8888‘
  8. ok
  9. [[email protected] ~]# curl ‘127.0.0.1:8888‘
  10. ok
  11. [[email protected] ~]#

服务器端输出:

[plain] view plain copy

  1. client connect server: ip = 127.0.0.1, port = 50450
  2. read from client # 127.0.0.1:50450 # GET / HTTP/1.1
  3. User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.27.1 zlib/1.2.3 libidn/1.18 libssh2/1.4.2
  4. Host: 127.0.0.1:8888
  5. Accept: */*
  6. client close
  7. client connect server: ip = 127.0.0.1, port = 50452
  8. read from client # 127.0.0.1:50452 # GET / HTTP/1.1
  9. User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.27.1 zlib/1.2.3 libidn/1.18 libssh2/1.4.2
  10. Host: 127.0.0.1:8888
  11. Accept: */*
  12. client close
  13. client connect server: ip = 127.0.0.1, port = 50454
  14. read from client # 127.0.0.1:50454 # GET / HTTP/1.1
  15. User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.27.1 zlib/1.2.3 libidn/1.18 libssh2/1.4.2
  16. Host: 127.0.0.1:8888
  17. Accept: */*
  18. client close
  19. client connect server: ip = 127.0.0.1, port = 50456
  20. read from client # 127.0.0.1:50456 # GET / HTTP/1.1
  21. User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.27.1 zlib/1.2.3 libidn/1.18 libssh2/1.4.2
  22. Host: 127.0.0.1:8888
  23. Accept: */*
  24. client close

这样一个高并发的HTTP服务器就开发好了,用压测软件测试下并发能力:

看到高达5000多的QPS,有没有小激动呢^^。

PHP是世界上最好的语言 that‘s all !

原文地址:https://www.cnblogs.com/wanglijun/p/8732790.html

时间: 2024-10-12 15:30:34

PHP实现系统编程(一) --- 网络Socket及IO多路复用【网摘】的相关文章

嵌入式 Linux系统编程(一)——文件IO

嵌入式 Linux系统编程(一)--文件IO 一.文件IO概念 linux文件IO操作有两套大类的操作方式:不带缓存的文件IO操作,带缓存的文件IO操作.不带缓存的属于直接调用系统调用(system call)的方式,高效完成文件输入输出.它以文件标识符(整型)作为文件唯一性的判断依据.这种操作不是ASCI标准的,与系统有关,移植有一定的问题.而带缓存的是在不带缓存的基础之上封装了一层,维护了一个输入输出缓冲区,使之能跨OS,成为ASCI标准,称为标准IO库.不带缓存的方式频繁进行用户态 和内核

嵌入式 Linux系统编程(三)——标准IO库

嵌入式 Linux系统编程(三)--标准IO库 与文件IO函数相类似,标准IO库中提供的是fopen.fclose.fread.fwrite等面向流对象的IO函数,这些函数在实现时本身就要调用linux的文件IO这些系统调用. 一.标准IO库函数的缓冲机制 由于IO设备的访问速度与CPU的速度相差好几个数量级,为了协调IO设备与CPU的速度的不匹配,对于块设备,内核使用了页高速缓存,即数据会先被拷贝到操作系统内核的页缓存区中,然后才会从操作系统内核的缓存区拷贝到应用程序的地址空间. 当应用程序尝

socket之IO多路复用

IO 多路复用 I/O多路复用指:通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作. Linux Linux中的 select,poll,epoll 都是IO多路复用的机制. select select最早于1983年出现在4.2BSD中,它通过一个select()系统调用来监视多个文件描述符的数组,当select()返回后,该数组中就绪的文件描述符便会被内核修改标志位,使得进程可以获得这些文件描述符从而进行后续的读写操作. sele

VMware10中的Linux系统利用NAT网络连接方式访问外网配置

一.描述 在VMware10中 提供常见的三种网络连接方式 : 1.Bridge:这种方式最简单,直接将虚拟网卡桥接到一个物理网卡上面,与linux下一个网卡绑定两个不同地址类似,实际上是将网卡设置为混杂模式,从而达到侦听多个IP的能力. 在此种模式下,虚拟机内部的网卡(例如linux下的eth0)直接连到了我们真实物理网卡所在的网络上,相当于虚拟机和真实主机处于对等的地位,在网络关系上是平等的,没有谁在谁后面的问题.使用这种方式很简单,前提是你有1个以上的IP地址,这个不太适合使用. 2.na

1.socket编程:socket编程,网络字节序,函数介绍,IP地址转换函数,sockaddr数据结构,网络套接字函数,socket相关函数,TCP server和client

 1  Socket编程 socket这个词可以表示很多概念: 在TCP/IP协议中,"IP地址+TCP或UDP端口号"唯一标识网络通讯中的一个进程,"IP 地址+端口号"就称为socket. 在TCP协议中,建立连接的两个进程各自有一个socket来标识,那么这两个socket组成的socket pair就唯一标识一个连接.socket本身有"插座"的意思,因此用来描述网络连 接的一对一关系. TCP/IP协议最早在BSD UNIX上实现,

socket编程之三:socket网络编程中的常用函数

这节本来打算先给出常用函数介绍,再给两个代码实例,写着写着发现越来越长,决定把代码放在下一节. 本节内容持续更新...... 1 socket()函数 原型: int socket(int domain, int type, int protocol); 描述: 类似打开一个文件,返回一个socket描述符,唯一标识一个socket,后面相应的操作都是这用这个socket描述符. 参数: domain:协议族,常用的协议族有AF_INET.AF_INET6.AF_LOCAL.AF_ROUTE等:

Linux/Unix系统编程手册--SOCKET章节读书笔记

SOCKET章节读书笔记 强烈推荐Linux/Unix系统编程手册,号称超越APUE的神书. backlog含义 #include <sys/socket.h> int listen(int socketfd, int backlog) backlog参数限制未决连接(未accept)的数量,在这个数量之内,connect会立刻成功. Linux上上限为128,定义在 udp已连接socket udp socket也是可以调用connect()的,这种叫已连接socket,内核会记录这个soc

从零开始学Python第八周:网络编程基础(socket)

Socket网络编程 一,Socket编程 (1)Socket方法介绍 Socket是网络编程的一个抽象概念.通常我们用一个Socket表示"打开了一个网络链接",而打开一个Socket需要知道目标计算机的IP地址和端口号,再指定协议类型即可. 套接字是一个双向的通信信道的端点.套接字可能在沟通过程,进程之间在同一台机器上,或在不同的计算机之间的进程 要创建一个套接字,必须使用Socket模块的socket.socket()方法 在socket模块中的一般语法: s = socket.

java网络socket编程详解

7.2 面向套接字编程    我们已经通过了解Socket的接口,知其所以然,下面我们就将通过具体的案例,来熟悉Socket的具体工作方式 7.2.1使用套接字实现基于TCP协议的服务器和客户机程序    依据TCP协议,在C/S架构的通讯过程中,客户端和服务器的Socket动作如下: 客户端: 1.用服务器的IP地址和端口号实例化Socket对象. 2.调用connect方法,连接到服务器上. 3.将发送到服务器的IO流填充到IO对象里,比如BufferedReader/PrintWriter