若转载教程,请注明出自SW-X框架官方文档

第一阶段:PHP-Socket初学理论

1、你会使用Socket吗?

在WEB开发中,前辈们已经给我们铺了很长的路,让网络间的通信处理简单了许多。
以前同学们听到Socket编程、长连接、TCP/UDP协议等,可能就会觉得这些都是比较高深的编程知识,
但其实只要弄懂其中的工作原理,这些神秘的面纱就会慢慢揭开,编程逻辑就清晰了。
一个生活中的场景可以简单解释Socket的工作流程:
你要打电话给一个朋友,先拨号,朋友听到电话铃声后提起电话,
这时你和你的朋友就建立起了连接,就可以通信了。
等交流结束,任意一方挂断电话,又或者双方同时挂断,既代表结束此次交谈。
也许当初TCP/IP协议,就是按照这个场景来设计的呢。

2、PHP的Socket编程概述

从PHP5.3+后,PHP自带了Socket模块,使得P0HP具备了Socket通信能力,
具体函数说明可以参考官方手册:http://php.net/manual/zh/function.socket-create.php
具体实现跟C语言非常类似,只是少了内存分配和网络字节序转换这种底层操作。
同时,php的pcntl模块和posix模块配合可以实现基本的进程管理、信号处理等操作系统级别的功能。
这里有两个非常关键的函数,pcntl_fork() 和 posix_setsid()。
fork()一个进程,则表示创建了一个运行进程的副本,副本被认为是子进程,而原始进程被认为是父进程。
当fork()运行之后,则可以脱离启动它的进程和终端控制等,也意味着父进程可以自由退出。
pcntl_fork()返回值,-1表示执行失败,0表示在子进程中,大于0表示在父进程中。
setsit(),它首先使新进程成为一个新会话的“领导者”,最后使进程不再控制终端。
这也是成为守护进程最关键一步,这意味着,不会随着终端关闭而强制退出进程。
对于一个不会被中断的常驻进程来说,这是很关键的一步。
最后再进行一次fork(),这一步不是必须的,但通常都会这么做,它最大的意义是防止获得控制终端。

3、什么是进程?

  1. 一个正在执行的程序。
  2. 操作系统中正在运行的程序的一个实例。
  3. 可以分配给处理器并由处理器执行的一个实体。
  4. 操作系统中,执行任务的一个唯一标记。

4、Socket和进程的关系

PHP本身是单进程处理请求,而单进程一般只作用于短连接或HTTP协议的WEB开发中,
因为Socket长连接需要一直进程一直在服务器后台打开,所以无法与HTTP协议共用一个进程。
所以Socket开发都需要打开一个新的进程,并让其进入守护进程模式。
如果不让进程进入守护模式,在关闭CMD操作界面,又或者是页面时,该进程就会被操作系统杀死,
无法进入后台继续运行,这样Socket就会失效了。

5、什么是守护进程?

一个守护进程通常被认为是一个不对终端进行控制的后台任务。
它有三个很明显的特征:

  1. 在后台运行
  2. 与启动他的进程脱离
  3. 无须终端控制

最常见的实现方法:fork() -> setsid() -> ork()

6、PHP-Scoket的开发步骤

image.png

第二阶段:PHP-Socket编程

7、PHP与Socket相关的函数大全

下面我们先来认识下,原生PHP中,与Socket相关的函数都有哪些:

  1. socket_accept() 接受一个Socket连接
  2. socket_bind() socket绑定在一个IP地址和端口上
  3. socket_clear_error() 清除socket的错误或者最后的错误代码
  4. socket_close() 关闭一个socket资源
  5. socket_connect() 开始一个socket连接
  6. socket_create_listen() 在指定端口打开一个socket监听
  7. socket_create_pair() 产生一对没有区别的socket到一个数组里
  8. socket_create() 产生一个socket,相当于产生一个socket的数据结构
  9. socket_get_option() 获取socket选项
  10. socket_getpeername() 获取远程类似主机的ip地址
  11. socket_getsockname() 获取本地socketip地址
  12. socket_iovec_add() 添加一个新的向量到一个分散/聚合的数组
  13. socket_iovec_alloc() 这个函数创建一个能够发送接收读写的iovec数据结构
  14. socket_iovec_delete() 删除一个已经分配的iovec
  15. socket_iovec_fetch() 返回指定的iovec资源的数据
  16. socket_iovec_free() 释放一个iovec资源
  17. socket_iovec_set() 设置iovec的数据新值
  18. socket_last_error() 获取当前socket的最后错误代码
  19. socket_listen() 监听由指定socket的所有连接
  20. socket_read() 读取指定长度的数据
  21. socket_readv() 读取从分散/聚合数组过来的数据
  22. socket_recv() socket里结束数据到缓存
  23. socket_recvfrom() 接受数据从指定的socket,如果没有指定则默认当前socket
  24. socket_recvmsg() iovec里接受消息
  25. socket_select() 多路选择
  26. socket_send() 这个函数发送数据到已连接的socket
  27. socket_sendmsg() 发送消息到socket
  28. socket_sendto() 发送消息到指定地址的socket
  29. socket_set_block() socket里设置为块模式
  30. socket_set_nonblock() socket里设置为非块模式
  31. socket_set_option() 设置socket选项
  32. socket_shutdown() 这个函数允许你关闭读、写、或者指定的socket
  33. socket_strerror() 返回指定错误号的详细错误
  34. socket_write() 写数据到socket缓存
  35. socket_writev() 写数据到分散/聚合数组

8、简单的Socket通讯演示

Socket编程除了需要编写服务端的代码外,还需要编写客户端的代码,下面我们运用Linux服务器,
来编写一个连接Socket服务器的简单案例:
1、我们先在服务器的/var/www/下,随便挑个目录新建一个server.php文件,充当于服务端代码:
注意代码里涉及到服务器开放端口,如果你是用的阿里云服务器,请到服务器对应的安全组中开启端口号。
在Linux中,输入netstat -lntp可以查看已经被使用的端口号,如果你的端口已经被占用,就应该更换一个未被占用的。

  1. <?php
  2. /*
  3. +-------------------------------
  4. * @socket通信整个过程
  5. +-------------------------------
  6. * @socket_create 生成一个socket连接
  7. * @socket_bind 把socket绑定在一个IP地址和端口上
  8. * @socket_listen 监听由指定socket的所有连接
  9. * @socket_accept 接受一个Socket连接
  10. * @socket_read 读取指定长度的数据
  11. * @socket_write 写入数据到socket缓存
  12. * @socket_close 关闭一个socket资源
  13. +--------------------------------
  14. */
  15. # 设置为永久执行,直到程序执行完成
  16. set_time_limit(0);
  17. # 你的服务器(私有)IP地址
  18. $ip = '';
  19. # 填写一个端口,作为你的Socket的传输端口
  20. $port = ;
  21. # ① 生成一个socket连接 - 具体参数参考官方手册
  22. if (($socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) < 0) {
  23. echo "socket_create() 失败的原因是:".socket_strerror($sock)."\n";
  24. }
  25. # ② 把socket绑定在一个IP地址和端口上
  26. if (($ret = socket_bind($socket, $ip, $port)) < 0) {
  27. echo "socket_bind() 失败的原因是:".socket_strerror($ret)."\n";
  28. }
  29. # ③ 监听由指定socket的所有连接
  30. if (($ret = socket_listen($socket, 4)) < 0) {
  31. echo "socket_listen() 失败的原因是:".socket_strerror($ret)."\n";
  32. }
  33. # 历史Socket请求数,一般用于清除缓存
  34. $count = 0;
  35. # 死循环一直执行程序
  36. do {
  37. # 接收一个Socket连接请求
  38. if (($msgsock = socket_accept($socket)) < 0) {
  39. echo "socket_accept() failed: reason: " . socket_strerror($msgsock) . "\n";
  40. break;
  41. } else {
  42. # 握手成功
  43. # 最多只接收5个请求,然后就关闭连接
  44. if($count > 5){
  45. break;
  46. };
  47. # 发送内容给客户端
  48. $msg ="测试成功!\n";
  49. socket_write($msgsock, $msg, strlen($msg));
  50. echo "测试成功了啊\n";
  51. $buf = socket_read($msgsock, 8192);
  52. $talkback = "收到的信息:$buf\n";
  53. echo $talkback;
  54. $count++;
  55. }
  56. socket_close($msgsock);
  57. } while (true);
  58. socket_close($socket);

2、我们再在服务器的/var/www/下,随便挑个目录新建一个client.php文件,充当于客户端代码:

  1. <?php
  2. /*
  3. +-------------------------------
  4. * @socket通信整个过程
  5. +-------------------------------
  6. * @socket_create 生成一个socket连接
  7. * @socket_connect 开始请求一个socket连接
  8. * @socket_write 写入数据到socket缓存
  9. * @socket_read 读取指定长度的数据
  10. * @socket_close 关闭一个socket资源
  11. +--------------------------------
  12. */
  13. # 设置为永久执行,直到程序执行完成
  14. set_time_limit(0);
  15. # 你的服务器(私有)IP地址
  16. $ip = '';
  17. # 填写一个端口,作为你的Socket的传输端口
  18. $port = ;
  19. error_reporting(E_ALL);
  20. # ① 生成一个socket连接 - 具体参数参考官方手册
  21. if (($socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) < 0) {
  22. die ("socket_create() 失败的原因是:".socket_strerror($sock)."\n");
  23. } else {
  24. echo "OK.\n";
  25. }
  26. echo "试图连接 '$ip' 端口 '$port'...\n";
  27. # ② 开始请求一个socket连接
  28. if (($result = socket_connect($socket, $ip, $port)) < 0) {
  29. die("socket_connect() failed.\nReason: ($result) " . socket_strerror($result) . "\n");
  30. } else {
  31. echo "连接成功\n";
  32. }
  33. $str = "小黄牛!\r\n";
  34. $str .= "你真帅!\r\n";
  35. $content = '';
  36. # ③ 发送内容到服务端
  37. if (!socket_write($socket, $str, strlen($str))) {
  38. die("socket_write() failed: reason: " . socket_strerror($socket) . "\n");
  39. } else {
  40. echo "发送到服务器信息成功!\n";
  41. echo "发送的内容为:$str\n";
  42. }
  43. # ④ 读取服务器回复给我们的信息
  44. while ($content = socket_read($socket, 8192)) {
  45. echo "接收服务器回传信息成功!\n";
  46. echo "接受的内容为:".$content."\n";
  47. }
  48. echo "关闭SOCKET...\n";
  49. socket_close($socket);
  50. echo "关闭OK\n";

3、下面打开我们的Xshell,连接上服务器,不过这里有一个到注意的点,就是你要用Xshell打开两个标签,
一个先输入命令php /var/www/demo/server.php,启动服务端Socket端口,而且这个标签不能关闭,要一直开着,因为现在我们还没为这个端口开启守护进程,端口关闭,Socket就关了。
运行之后,可以看到下面的效果:
image.png
4、这时候我们再到第二个标签中,输入命令php /var/www/demo/client.php,执行客户端代码,向服务端发送Socket请求,运行之后,可以看到下面的效果:
image.png
会发现,信息发送成功啦!
5、最后我们再切换回第一个标签,发现服务端也成功接收到请求了:
image.png

9、服务端广播消息

可能有的同学会有疑问,如何Socket只是单纯的一对一用于接收客户端的请求消息,那不是很没用?
实际上,Socket除了可以一对一接收请求之外,他还可以操控同一个通道(端口)内的所有客户端成员的身份凭证,
例如:对所有成员推送消息、强制中断其中一个成员的Socket连接等。
在实际开发中,PHP使用原生Socket组件作为服务端通讯驱动就已经很勉强了,需要使用到死循环才能让Socket一直保持开启的状态。
而如果也使用PHP+Socket来充当客户端代码是很不现实的,因为我们没办法让客户端进入死循环,那么Socket就没办法长时间的开启,等待服务端推送消息给我们。
在Web网页中,JS组件提供了一个WebSocket类,它主要用于代替后端Socket进行客户端的通讯操作,极其方便,不过该JS类必须要在IE9+以上的浏览器中才有用,低版本的浏览器是用不了的。
1、下面我们先来封装一个服务端操作的class类,命名为:SocketServer.php:

  1. <?php
  2. class SocketServer {
  3. /**
  4. * Socket实例
  5. */
  6. private $socket;
  7. /**
  8. * 保存读的套接字
  9. */
  10. private $readGroup = [];
  11. /**
  12. * 保存写的套接字
  13. */
  14. private $writeGroup = [];
  15. /**
  16. * tcp协议中用于加密的字符串
  17. */
  18. private $mcrypt_key = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
  19. private $test = false;
  20. /**
  21. * 初始化实例
  22. * @param String $host ip地址
  23. * @param int $port 端口
  24. * @param int $backlog 最大连接数
  25. */
  26. public function __construct($host = '你的IP地址', $port = '你的端口号', $backlog = 100)
  27. {
  28. $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or die('socket创建失败');
  29. socket_bind($this->socket, $host,$port);
  30. socket_listen($this->socket, $backlog);
  31. $this->readGroup[] = $this->socket;
  32. }
  33. /**
  34. * 死循环让Socket持续保活
  35. */
  36. public function start() {
  37. while (true) {
  38. # 下面这3个参数,必须要先由参数赋值,因为从php5.3+后开始
  39. # socket_select的参数变成了一个引用,虽然没办法直接输入null或使用成员属性
  40. $socketArr = $this->readGroup;
  41. $writeGroup= $this->writeGroup;
  42. $e = NULL;
  43. # 阻塞,直到捕获到变化
  44. if (false !== socket_select($socketArr, $writeGroup, $e, null)) {
  45. # 遍历读的套接数组
  46. foreach ($socketArr as $socket) {
  47. # 如果是当前服务器的监听连接
  48. if($this->socket == $socket) {
  49. # 接收一个Socket连接
  50. $client = socket_accept($this->socket);
  51. # 添加客户端套接字
  52. $this->add_client($client);
  53. } else {
  54. # 获取客户端发送来的信息
  55. $msg = socket_read($socket, 1024);
  56. /*
  57. 如果检测到客户端发送的是握手协议,则向客户端发送握手协议
  58. ps:这样检测虽然不严谨,但能应对大部分客户端掉线,非正常关闭的情况,将就下
  59. */
  60. if(empty($msg)) {
  61. continue;
  62. } else if (preg_match('/Sec-WebSocket-Key: (.*)\r\n/', $msg, $matches)) {
  63. # 握手请求
  64. $upgrade = $this->createHandShake($matches[1]);
  65. socket_write($socket, $upgrade, strlen($upgrade));
  66. } else {
  67. # 接收到了客户端传递过来的Socket消息
  68. # 解码客户端发送的消息
  69. $client_info = $this->decodeMsg($msg);
  70. # 向其他客户端进行广播
  71. $this->send_to_other($client_info,$socket);
  72. }
  73. }
  74. }
  75. }
  76. }
  77. }
  78. /**
  79. * 保存一个客户端的套接字
  80. * @param resource $client 客户端的套接字
  81. */
  82. private function add_client($client) {
  83. $this->readGroup[] = $client;
  84. }
  85. /**
  86. * 发送内容到指定客户端
  87. * @param resource $socket 客户端套接字
  88. * @param String $data 要发送的消息
  89. */
  90. private function send_to_one($socket, $data) {
  91. # 先对信息进行编码处理
  92. $data = $this->encodeMsg($data);
  93. socket_write($socket, $data);
  94. }
  95. /**
  96. * 使用循环语句,将内容广播到所有客户端
  97. * @param String $data 要发送的消息
  98. */
  99. private function send_to_all($data) {
  100. $writeGroup = $this->readGroup;
  101. # 最开始的Socket链接,肯定是服务器自身,所以我们需要先移除
  102. unset($writeGroup[0]);
  103. # 处理内容编码
  104. $data = $this->encodeMsg($data);
  105. # 循环广播
  106. foreach ($writeGroup as $socket) {
  107. socket_write($socket, $data);
  108. }
  109. }
  110. /**
  111. * 广播至除发送消息外的客户端
  112. * @param String $data
  113. * @param resource $client
  114. */
  115. private function send_to_other($data, $client) {
  116. $writeGroup = $this->readGroup;
  117. # 最开始的Socket链接,肯定是服务器自身,所以我们需要先移除
  118. unset($writeGroup[0]);
  119. # 处理内容编码
  120. $data = $this->encodeMsg($data);
  121. # 循环广播
  122. foreach ($writeGroup as $socket) {
  123. # 如果是客户端自己,则移除
  124. if ($socket != $client) {
  125. socket_write($socket, $data);
  126. }
  127. }
  128. }
  129. /**
  130. * 计算返回客户端的握手协议
  131. * @param String $msg 客户端发送过来的协议
  132. * @return String $upgrade 返回给客户端的协议
  133. */
  134. private function createHandShake($client_key) {
  135. $key = base64_encode(sha1($client_key.$this->mcrypt_key, true));
  136. $upgrade = "HTTP/1.1 101 Switching Protocol\r\n" .
  137. "Upgrade: websocket\r\n" .
  138. "Connection: Upgrade\r\n" .
  139. "Sec-WebSocket-Accept: " . $key . "\r\n\r\n"; // 结尾一定要两个\r\n\r\n
  140. return $upgrade;
  141. }
  142. /**
  143. * 解码客户端发送过来的信息
  144. * @param binary $buffer 客户端传来的信息
  145. * @return String $decoded 解码后的字符串
  146. */
  147. private function decodeMsg($buffer) {
  148. $len = $masks = $data = $decoded = null;
  149. $len = ord($buffer[1]) & 127;
  150. if ($len === 126) {
  151. $masks = substr($buffer, 4, 4);
  152. $data = substr($buffer, 8);
  153. } else if ($len === 127) {
  154. $masks = substr($buffer, 10, 4);
  155. $data = substr($buffer, 14);
  156. } else {
  157. $masks = substr($buffer, 2, 4);
  158. $data = substr($buffer, 6);
  159. }
  160. for ($index = 0; $index < strlen($data); $index++) {
  161. $decoded .= $data[$index] ^ $masks[$index % 4];
  162. }
  163. return $decoded;
  164. }
  165. /**
  166. * 发送到客户端前进行编码
  167. * @param string $msg 发送到客户端的内容
  168. */
  169. private function encodeMsg($msg) {
  170. $head = str_split($msg, 125);
  171. if (count($head) == 1) {
  172. return "\x81" . chr(strlen($head[0])) . $head[0];
  173. }
  174. $info = "";
  175. foreach ($head as $value) {
  176. $info .= "\x81" . chr(strlen($value)) . $value;
  177. }
  178. return $info;
  179. }
  180. }

然后再新建一个start.php文件,这个文件是实例化Server类的,用于开启服务端Socket:

  1. <?php
  2. # 引入Socket封装类
  3. require 'SocketServer.php';
  4. $socketServer = new SocketServer();
  5. $socketServer->start();

这两个文件新建完成后,我们就可以使用linux命令,跟之前的案例一样,先触发这个index.php文件,让服务端启动保活。
最后再新建client.html文件,编写WebSocket代码,代替PHP充当客户端:

  1. <!DOCTYPE html>
  2. <html>
  3. <meta http-equiv="Content-type" content="text/html;charset=utf-8">
  4. <head>
  5. <title>客户端</title>
  6. <script src="https://www.junphp.com/api/kefu/js/JQuery v1.7.2.js" type="text/javascript"></script>
  7. <script>
  8. // 打开一个Socket连接
  9. var ws = new WebSocket('wss://blog.junphp.com/wss/');
  10. ws.onopen = function(){
  11. console.log("握手成功,打开socket连接了。。。");
  12. };
  13. // 接收服务端广播的消息通知
  14. ws.onmessage = function(e){
  15. var template = $("#other_template").html();
  16. var temp = $(template);
  17. temp.find(".info").children("span").text(e.data);
  18. $("#main").append(temp);
  19. };
  20. // 接收Socket断开时的消息通知
  21. ws.onclose = function(){
  22. console.log("断开socket连接了。。。");
  23. };
  24. // 接收Socket连接失败时的异常通知
  25. ws.onerror = function(e){
  26. console.log("ERROR:" + e.data);
  27. };
  28. // 向服务端发送消息
  29. sendMessage = function(){
  30. var data = $("#msg").val();
  31. ws.send(data);
  32. $("#msg").val("");
  33. //根据模板生成自己消息视图
  34. var template = $("#self_template").html();
  35. var temp =$(template);
  36. temp.find(".info").children("span").text(data);
  37. $("#main").append(temp);
  38. }
  39. </script>
  40. <!--系统广播的内容-->
  41. <script id="other_template" type="text/template">
  42. <div class="show_box">
  43. <div class="user box-left">别人说的话:</div>
  44. <div class="info info_other box-left">
  45. <span></span>
  46. <div class="angle-left info_other"></div>
  47. </div>
  48. </div>
  49. </script>
  50. <!--发送方内容-->
  51. <script id="self_template" type="text/template">
  52. <div class="show_box">
  53. <div class="user box-right">我的发送的:</div>
  54. <div class="info info_self box-right">
  55. <span></span>
  56. <div class="angle-right info_self"></div>
  57. </div>
  58. </div>
  59. </script>
  60. </head>
  61. <body>
  62. <div id="client">
  63. <div id="head"></div>
  64. <div id="main"></div>
  65. <div id="msg_box">
  66. <input id="msg" type="text" placeholder="随便说两句吧...">
  67. <button id="send">发送</button>
  68. </div>
  69. </div>
  70. <script>
  71. // 点击发送内容
  72. $("#send").click(function(){
  73. sendMessage();
  74. });
  75. // 回车发送内容
  76. $("#msg").on("keypress",function(e){
  77. if(e.keyCode == 13){
  78. sendMessage();
  79. }
  80. });
  81. </script>
  82. </body>

这时候我们用两个浏览器,记住是两个浏览器又不是两个标签,访问这个client.html文件,然后再里面输入内容进行调试,就能发现两边都能够进行简单聊天了。
如果你的服务器是nginx的驱动的环境,就需要进行一下反向代理配置,具体看老师的博客:https://blog.junphp.com/details/131.jsp。
如果是nginx,又是https的域名,就要看这篇了:https://blog.junphp.com/details/132.jsp。

10、简单了解一下 WebSocket

现在,很多网站为了实现推送技术,所用的技术都是轮询。
轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。
这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,
然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。
在这种情况下,HTML5定义了WebSocket协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。
WebSocket是一种在单个 TCP 连接上进行全双工通讯的协议。
使得客户端和服务器之间的数据交互变得更加简单,允许服务端主动向客户端推送数据。
在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
简单点说,WebSocket 就是减小客户端与服务器端建立连接的次数,减小系统资源开销,只需要一次 HTTP 握手,
整个通讯过程是建立在一次连接/状态中,也就避免了HTTP的非状态性,服务端会一直与客户端保持连接,直到你关闭请求,
同时由原本的客户端主动询问,转换为服务器有信息的时候推送。
当然,它还能做实时通信、更好的二进制支持、支持扩展、更好的压缩效果等这些优点。

11、ws 和 wss 又是什么鬼?

Websocket使用 ws 或 wss 的统一资源标志符,类似于 HTTP 或 HTTPS ,
其中 wss 表示在 TLS 之上的 Websocket ,相当于 HTTPS 了。
默认情况下,Websocket 的 ws 协议使用 80 端口;
运行在TLS之上时,wss 协议默认使用 443 端口。
其实说白了,wss 就是 ws 基于 SSL 的安全传输,与 HTTPS 一样的道理。

12、如何开启Socket守护进程

在实际开发中,我们不可能时时挂着Xshell打开服务端进程的,而如果我们关闭Xshell,那么就表示php的死循环关闭了,进程就会自动销毁,这时候客户端的Socket就会自动断开。
而Linux给我们提供了一种应对这种情况下,非常好用的解决方案,就是在服务端在开启Socket进程之前,先设置一个父进程,并将其设置为守护进程,最后再设置了一个子进程挂载Socket端口,这样就算我们关闭Xshell,这个进程也会一直在后台保活,就不会自动关闭。
这也是Socket编程最关键的一步。
下面我以:8、简单的Socket通讯演示中的代码为例,将其中server.php文件的代码修改为以下:

  1. <?php
  2. /*
  3. +-------------------------------
  4. * @socket通信整个过程
  5. +-------------------------------
  6. * @socket_create 生成一个socket连接
  7. * @socket_bind 把socket绑定在一个IP地址和端口上
  8. * @socket_listen 监听由指定socket的所有连接
  9. * @socket_accept 接受一个Socket连接
  10. * @socket_read 读取指定长度的数据
  11. * @socket_write 写入数据到socket缓存
  12. * @socket_close 关闭一个socket资源
  13. +--------------------------------
  14. */
  15. # 设置为永久执行,直到程序执行完成
  16. set_time_limit(0);
  17. /**
  18. * 假设,这是你服务端启动程序
  19. */
  20. function server() {
  21. # 你的服务器(私有)IP地址
  22. $ip = '172.18.77.92';
  23. # 填写一个端口,作为你的Socket的传输端口
  24. $port = 1935;
  25. # ① 生成一个socket连接 - 具体参数参考官方手册
  26. if (($socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) < 0) {
  27. echo "socket_create() 失败的原因是:".socket_strerror($sock)."\n";
  28. }
  29. # ② 把socket绑定在一个IP地址和端口上
  30. if (($ret = socket_bind($socket, $ip, $port)) < 0) {
  31. echo "socket_bind() 失败的原因是:".socket_strerror($ret)."\n";
  32. }
  33. # ③ 监听由指定socket的所有连接
  34. if (($ret = socket_listen($socket, 4)) < 0) {
  35. echo "socket_listen() 失败的原因是:".socket_strerror($ret)."\n";
  36. }
  37. # 历史Socket请求数,一般用于清除缓存
  38. $count = 0;
  39. # 死循环一直执行程序
  40. do {
  41. # 接收一个Socket连接请求
  42. if (($msgsock = socket_accept($socket)) < 0) {
  43. echo "socket_accept() failed: reason: " . socket_strerror($msgsock) . "\n";
  44. break;
  45. } else {
  46. # 握手成功
  47. # 最多只接收5个请求,然后就关闭连接
  48. if($count > 5){
  49. break;
  50. };
  51. # 发送内容给客户端
  52. $msg ="测试成功!\n";
  53. socket_write($msgsock, $msg, strlen($msg));
  54. echo "测试成功了啊\n";
  55. $buf = socket_read($msgsock, 8192);
  56. $talkback = "收到的信息:$buf\n";
  57. echo $talkback;
  58. $count++;
  59. }
  60. socket_close($msgsock);
  61. } while (true);
  62. socket_close($socket);
  63. }
  64. /**
  65. * 这是开启守护进程的程序
  66. */
  67. function run_server () {
  68. # ① fork 一个子进程号
  69. $pid1 = pcntl_fork();
  70. # 为0,表示在子进程执行线程内
  71. if ($pid1 == 0) {
  72. # ② setsid 将当前进程转换为守护进程
  73. posix_setsid();
  74. # ③ fork 再新建一个子进程号,就会挂载在父守护进程中
  75. $pid2 = pcntl_fork();
  76. if ($pid2 == 0) {
  77. server();
  78. } else {
  79. # 防止获得控制终端
  80. exit;
  81. }
  82. } else {
  83. # 挂起父进程,防止出现僵尸进程
  84. pcntl_wait($status);
  85. }
  86. }
  87. # 启动服务端Socket,同时开启守护进程
  88. run_server();

然后,我们关闭所有Xshell,然后重新打开一个Xshell标签,
先netstat -lntp一遍端口号,看看有没有被占用,
有的话则使用kill -9 端口对应的PID结束进程。
然后再使用php /var/www/demo/server.php,启动服务端Socket程序,并开启守护进程。
这时候你会发现,跟之前的启动不一样,这一次我们的Xshell并没有进入循环等待,而是退回到正常状态了:
image.png
这时候我们直接在Xshell里输入php /var/www/demo/client.php就可以进行调试啦。
基于这一个demo,同学们可以自己发散思维,实现复杂的聊天室功能,例如:腾讯的TIM等。