第一阶段: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、什么是进程?
一个正在执行的程序。
操作系统中正在运行的程序的一个实例。
可以分配给处理器并由处理器执行的一个实体。
操作系统中,执行任务的一个唯一标记。
4、Socket和进程的关系
PHP本身是单进程处理请求,而单进程一般只作用于短连接或HTTP协议的WEB开发中,
因为Socket长连接需要一直进程一直在服务器后台打开,所以无法与HTTP协议共用一个进程。
所以Socket开发都需要打开一个新的进程,并让其进入守护进程模式。
如果不让进程进入守护模式,在关闭CMD操作界面,又或者是页面时,该进程就会被操作系统杀死,
无法进入后台继续运行,这样Socket就会失效了。
5、什么是守护进程?
一个守护进程通常被认为是一个不对终端进行控制的后台任务。
它有三个很明显的特征:
在后台运行
与启动他的进程脱离
无须终端控制
最常见的实现方法:fork() -> setsid() -> ork()
6、PHP-Scoket的开发步骤
第二阶段:PHP-Socket编程
7、PHP与Socket相关的函数大全
下面我们先来认识下,原生PHP中,与Socket相关的函数都有哪些:
socket_accept() 接受一个Socket连接
socket_bind() 把socket绑定在一个IP地址和端口上
socket_clear_error() 清除socket的错误或者最后的错误代码
socket_close() 关闭一个socket资源
socket_connect() 开始一个socket连接
socket_create_listen() 在指定端口打开一个socket监听
socket_create_pair() 产生一对没有区别的socket到一个数组里
socket_create() 产生一个socket,相当于产生一个socket的数据结构
socket_get_option() 获取socket选项
socket_getpeername() 获取远程类似主机的ip地址
socket_getsockname() 获取本地socket的ip地址
socket_iovec_add() 添加一个新的向量到一个分散/聚合的数组
socket_iovec_alloc() 这个函数创建一个能够发送接收读写的iovec数据结构
socket_iovec_delete() 删除一个已经分配的iovec
socket_iovec_fetch() 返回指定的iovec资源的数据
socket_iovec_free() 释放一个iovec资源
socket_iovec_set() 设置iovec的数据新值
socket_last_error() 获取当前socket的最后错误代码
socket_listen() 监听由指定socket的所有连接
socket_read() 读取指定长度的数据
socket_readv() 读取从分散/聚合数组过来的数据
socket_recv() 从socket里结束数据到缓存
socket_recvfrom() 接受数据从指定的socket,如果没有指定则默认当前socket
socket_recvmsg() 从iovec里接受消息
socket_select() 多路选择
socket_send() 这个函数发送数据到已连接的socket
socket_sendmsg() 发送消息到socket
socket_sendto() 发送消息到指定地址的socket
socket_set_block() 在socket里设置为块模式
socket_set_nonblock() socket里设置为非块模式
socket_set_option() 设置socket选项
socket_shutdown() 这个函数允许你关闭读、写、或者指定的socket
socket_strerror() 返回指定错误号的详细错误
socket_write() 写数据到socket缓存
socket_writev() 写数据到分散/聚合数组
8、简单的Socket通讯演示
Socket编程除了需要编写服务端的代码外,还需要编写客户端的代码,下面我们运用Linux服务器,
来编写一个连接Socket服务器的简单案例:
1、我们先在服务器的/var/www/下,随便挑个目录新建一个server.php文件,充当于服务端代码:
注意代码里涉及到服务器开放端口,如果你是用的阿里云服务器,请到服务器对应的安全组中开启端口号。
在Linux中,输入netstat -lntp可以查看已经被使用的端口号,如果你的端口已经被占用,就应该更换一个未被占用的。
<?php
/*
+-------------------------------
* @socket通信整个过程
+-------------------------------
* @socket_create 生成一个socket连接
* @socket_bind 把socket绑定在一个IP地址和端口上
* @socket_listen 监听由指定socket的所有连接
* @socket_accept 接受一个Socket连接
* @socket_read 读取指定长度的数据
* @socket_write 写入数据到socket缓存
* @socket_close 关闭一个socket资源
+--------------------------------
*/
# 设置为永久执行,直到程序执行完成
set_time_limit(0);
# 你的服务器(私有)IP地址
$ip = '';
# 填写一个端口,作为你的Socket的传输端口
$port = ;
# ① 生成一个socket连接 - 具体参数参考官方手册
if (($socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) < 0) {
echo "socket_create() 失败的原因是:".socket_strerror($sock)."\n";
}
# ② 把socket绑定在一个IP地址和端口上
if (($ret = socket_bind($socket, $ip, $port)) < 0) {
echo "socket_bind() 失败的原因是:".socket_strerror($ret)."\n";
}
# ③ 监听由指定socket的所有连接
if (($ret = socket_listen($socket, 4)) < 0) {
echo "socket_listen() 失败的原因是:".socket_strerror($ret)."\n";
}
# 历史Socket请求数,一般用于清除缓存
$count = 0;
# 死循环一直执行程序
do {
# 接收一个Socket连接请求
if (($msgsock = socket_accept($socket)) < 0) {
echo "socket_accept() failed: reason: " . socket_strerror($msgsock) . "\n";
break;
} else {
# 握手成功
# 最多只接收5个请求,然后就关闭连接
if($count > 5){
break;
};
# 发送内容给客户端
$msg ="测试成功!\n";
socket_write($msgsock, $msg, strlen($msg));
echo "测试成功了啊\n";
$buf = socket_read($msgsock, 8192);
$talkback = "收到的信息:$buf\n";
echo $talkback;
$count++;
}
socket_close($msgsock);
} while (true);
socket_close($socket);
2、我们再在服务器的/var/www/下,随便挑个目录新建一个client.php文件,充当于客户端代码:
<?php
/*
+-------------------------------
* @socket通信整个过程
+-------------------------------
* @socket_create 生成一个socket连接
* @socket_connect 开始请求一个socket连接
* @socket_write 写入数据到socket缓存
* @socket_read 读取指定长度的数据
* @socket_close 关闭一个socket资源
+--------------------------------
*/
# 设置为永久执行,直到程序执行完成
set_time_limit(0);
# 你的服务器(私有)IP地址
$ip = '';
# 填写一个端口,作为你的Socket的传输端口
$port = ;
error_reporting(E_ALL);
# ① 生成一个socket连接 - 具体参数参考官方手册
if (($socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) < 0) {
die ("socket_create() 失败的原因是:".socket_strerror($sock)."\n");
} else {
echo "OK.\n";
}
echo "试图连接 '$ip' 端口 '$port'...\n";
# ② 开始请求一个socket连接
if (($result = socket_connect($socket, $ip, $port)) < 0) {
die("socket_connect() failed.\nReason: ($result) " . socket_strerror($result) . "\n");
} else {
echo "连接成功\n";
}
$str = "小黄牛!\r\n";
$str .= "你真帅!\r\n";
$content = '';
# ③ 发送内容到服务端
if (!socket_write($socket, $str, strlen($str))) {
die("socket_write() failed: reason: " . socket_strerror($socket) . "\n");
} else {
echo "发送到服务器信息成功!\n";
echo "发送的内容为:$str\n";
}
# ④ 读取服务器回复给我们的信息
while ($content = socket_read($socket, 8192)) {
echo "接收服务器回传信息成功!\n";
echo "接受的内容为:".$content."\n";
}
echo "关闭SOCKET...\n";
socket_close($socket);
echo "关闭OK\n";
3、下面打开我们的Xshell,连接上服务器,不过这里有一个到注意的点,就是你要用Xshell打开两个标签,
一个先输入命令php /var/www/demo/server.php,启动服务端Socket端口,而且这个标签不能关闭,要一直开着,因为现在我们还没为这个端口开启守护进程,端口关闭,Socket就关了。
运行之后,可以看到下面的效果:
4、这时候我们再到第二个标签中,输入命令php /var/www/demo/client.php,执行客户端代码,向服务端发送Socket请求,运行之后,可以看到下面的效果:
会发现,信息发送成功啦!
5、最后我们再切换回第一个标签,发现服务端也成功接收到请求了:
9、服务端广播消息
可能有的同学会有疑问,如何Socket只是单纯的一对一用于接收客户端的请求消息,那不是很没用?
实际上,Socket除了可以一对一接收请求之外,他还可以操控同一个通道(端口)内的所有客户端成员的身份凭证,
例如:对所有成员推送消息、强制中断其中一个成员的Socket连接等。
在实际开发中,PHP使用原生Socket组件作为服务端通讯驱动就已经很勉强了,需要使用到死循环才能让Socket一直保持开启的状态。
而如果也使用PHP+Socket来充当客户端代码是很不现实的,因为我们没办法让客户端进入死循环,那么Socket就没办法长时间的开启,等待服务端推送消息给我们。
在Web网页中,JS组件提供了一个WebSocket类,它主要用于代替后端Socket进行客户端的通讯操作,极其方便,不过该JS类必须要在IE9+以上的浏览器中才有用,低版本的浏览器是用不了的。
1、下面我们先来封装一个服务端操作的class类,命名为:SocketServer.php:
<?php
class SocketServer {
/**
* Socket实例
*/
private $socket;
/**
* 保存读的套接字
*/
private $readGroup = [];
/**
* 保存写的套接字
*/
private $writeGroup = [];
/**
* tcp协议中用于加密的字符串
*/
private $mcrypt_key = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
private $test = false;
/**
* 初始化实例
* @param String $host ip地址
* @param int $port 端口
* @param int $backlog 最大连接数
*/
public function __construct($host = '你的IP地址', $port = '你的端口号', $backlog = 100)
{
$this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or die('socket创建失败');
socket_bind($this->socket, $host,$port);
socket_listen($this->socket, $backlog);
$this->readGroup[] = $this->socket;
}
/**
* 死循环让Socket持续保活
*/
public function start() {
while (true) {
# 下面这3个参数,必须要先由参数赋值,因为从php5.3+后开始
# socket_select的参数变成了一个引用,虽然没办法直接输入null或使用成员属性
$socketArr = $this->readGroup;
$writeGroup= $this->writeGroup;
$e = NULL;
# 阻塞,直到捕获到变化
if (false !== socket_select($socketArr, $writeGroup, $e, null)) {
# 遍历读的套接数组
foreach ($socketArr as $socket) {
# 如果是当前服务器的监听连接
if($this->socket == $socket) {
# 接收一个Socket连接
$client = socket_accept($this->socket);
# 添加客户端套接字
$this->add_client($client);
} else {
# 获取客户端发送来的信息
$msg = socket_read($socket, 1024);
/*
如果检测到客户端发送的是握手协议,则向客户端发送握手协议
ps:这样检测虽然不严谨,但能应对大部分客户端掉线,非正常关闭的情况,将就下
*/
if(empty($msg)) {
continue;
} else if (preg_match('/Sec-WebSocket-Key: (.*)\r\n/', $msg, $matches)) {
# 握手请求
$upgrade = $this->createHandShake($matches[1]);
socket_write($socket, $upgrade, strlen($upgrade));
} else {
# 接收到了客户端传递过来的Socket消息
# 解码客户端发送的消息
$client_info = $this->decodeMsg($msg);
# 向其他客户端进行广播
$this->send_to_other($client_info,$socket);
}
}
}
}
}
}
/**
* 保存一个客户端的套接字
* @param resource $client 客户端的套接字
*/
private function add_client($client) {
$this->readGroup[] = $client;
}
/**
* 发送内容到指定客户端
* @param resource $socket 客户端套接字
* @param String $data 要发送的消息
*/
private function send_to_one($socket, $data) {
# 先对信息进行编码处理
$data = $this->encodeMsg($data);
socket_write($socket, $data);
}
/**
* 使用循环语句,将内容广播到所有客户端
* @param String $data 要发送的消息
*/
private function send_to_all($data) {
$writeGroup = $this->readGroup;
# 最开始的Socket链接,肯定是服务器自身,所以我们需要先移除
unset($writeGroup[0]);
# 处理内容编码
$data = $this->encodeMsg($data);
# 循环广播
foreach ($writeGroup as $socket) {
socket_write($socket, $data);
}
}
/**
* 广播至除发送消息外的客户端
* @param String $data
* @param resource $client
*/
private function send_to_other($data, $client) {
$writeGroup = $this->readGroup;
# 最开始的Socket链接,肯定是服务器自身,所以我们需要先移除
unset($writeGroup[0]);
# 处理内容编码
$data = $this->encodeMsg($data);
# 循环广播
foreach ($writeGroup as $socket) {
# 如果是客户端自己,则移除
if ($socket != $client) {
socket_write($socket, $data);
}
}
}
/**
* 计算返回客户端的握手协议
* @param String $msg 客户端发送过来的协议
* @return String $upgrade 返回给客户端的协议
*/
private function createHandShake($client_key) {
$key = base64_encode(sha1($client_key.$this->mcrypt_key, true));
$upgrade = "HTTP/1.1 101 Switching Protocol\r\n" .
"Upgrade: websocket\r\n" .
"Connection: Upgrade\r\n" .
"Sec-WebSocket-Accept: " . $key . "\r\n\r\n"; // 结尾一定要两个\r\n\r\n
return $upgrade;
}
/**
* 解码客户端发送过来的信息
* @param binary $buffer 客户端传来的信息
* @return String $decoded 解码后的字符串
*/
private function decodeMsg($buffer) {
$len = $masks = $data = $decoded = null;
$len = ord($buffer[1]) & 127;
if ($len === 126) {
$masks = substr($buffer, 4, 4);
$data = substr($buffer, 8);
} else if ($len === 127) {
$masks = substr($buffer, 10, 4);
$data = substr($buffer, 14);
} else {
$masks = substr($buffer, 2, 4);
$data = substr($buffer, 6);
}
for ($index = 0; $index < strlen($data); $index++) {
$decoded .= $data[$index] ^ $masks[$index % 4];
}
return $decoded;
}
/**
* 发送到客户端前进行编码
* @param string $msg 发送到客户端的内容
*/
private function encodeMsg($msg) {
$head = str_split($msg, 125);
if (count($head) == 1) {
return "\x81" . chr(strlen($head[0])) . $head[0];
}
$info = "";
foreach ($head as $value) {
$info .= "\x81" . chr(strlen($value)) . $value;
}
return $info;
}
}
然后再新建一个start.php文件,这个文件是实例化Server类的,用于开启服务端Socket:
<?php
# 引入Socket封装类
require 'SocketServer.php';
$socketServer = new SocketServer();
$socketServer->start();
这两个文件新建完成后,我们就可以使用linux命令,跟之前的案例一样,先触发这个index.php文件,让服务端启动保活。
最后再新建client.html文件,编写WebSocket代码,代替PHP充当客户端:
<!DOCTYPE html>
<html>
<meta http-equiv="Content-type" content="text/html;charset=utf-8">
<head>
<title>客户端</title>
<script src="https://www.junphp.com/api/kefu/js/JQuery v1.7.2.js" type="text/javascript"></script>
<script>
// 打开一个Socket连接
var ws = new WebSocket('wss://blog.junphp.com/wss/');
ws.onopen = function(){
console.log("握手成功,打开socket连接了。。。");
};
// 接收服务端广播的消息通知
ws.onmessage = function(e){
var template = $("#other_template").html();
var temp = $(template);
temp.find(".info").children("span").text(e.data);
$("#main").append(temp);
};
// 接收Socket断开时的消息通知
ws.onclose = function(){
console.log("断开socket连接了。。。");
};
// 接收Socket连接失败时的异常通知
ws.onerror = function(e){
console.log("ERROR:" + e.data);
};
// 向服务端发送消息
sendMessage = function(){
var data = $("#msg").val();
ws.send(data);
$("#msg").val("");
//根据模板生成自己消息视图
var template = $("#self_template").html();
var temp =$(template);
temp.find(".info").children("span").text(data);
$("#main").append(temp);
}
</script>
<!--系统广播的内容-->
<script id="other_template" type="text/template">
<div class="show_box">
<div class="user box-left">别人说的话:</div>
<div class="info info_other box-left">
<span></span>
<div class="angle-left info_other"></div>
</div>
</div>
</script>
<!--发送方内容-->
<script id="self_template" type="text/template">
<div class="show_box">
<div class="user box-right">我的发送的:</div>
<div class="info info_self box-right">
<span></span>
<div class="angle-right info_self"></div>
</div>
</div>
</script>
</head>
<body>
<div id="client">
<div id="head"></div>
<div id="main"></div>
<div id="msg_box">
<input id="msg" type="text" placeholder="随便说两句吧...">
<button id="send">发送</button>
</div>
</div>
<script>
// 点击发送内容
$("#send").click(function(){
sendMessage();
});
// 回车发送内容
$("#msg").on("keypress",function(e){
if(e.keyCode == 13){
sendMessage();
}
});
</script>
</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文件的代码修改为以下:
<?php
/*
+-------------------------------
* @socket通信整个过程
+-------------------------------
* @socket_create 生成一个socket连接
* @socket_bind 把socket绑定在一个IP地址和端口上
* @socket_listen 监听由指定socket的所有连接
* @socket_accept 接受一个Socket连接
* @socket_read 读取指定长度的数据
* @socket_write 写入数据到socket缓存
* @socket_close 关闭一个socket资源
+--------------------------------
*/
# 设置为永久执行,直到程序执行完成
set_time_limit(0);
/**
* 假设,这是你服务端启动程序
*/
function server() {
# 你的服务器(私有)IP地址
$ip = '172.18.77.92';
# 填写一个端口,作为你的Socket的传输端口
$port = 1935;
# ① 生成一个socket连接 - 具体参数参考官方手册
if (($socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) < 0) {
echo "socket_create() 失败的原因是:".socket_strerror($sock)."\n";
}
# ② 把socket绑定在一个IP地址和端口上
if (($ret = socket_bind($socket, $ip, $port)) < 0) {
echo "socket_bind() 失败的原因是:".socket_strerror($ret)."\n";
}
# ③ 监听由指定socket的所有连接
if (($ret = socket_listen($socket, 4)) < 0) {
echo "socket_listen() 失败的原因是:".socket_strerror($ret)."\n";
}
# 历史Socket请求数,一般用于清除缓存
$count = 0;
# 死循环一直执行程序
do {
# 接收一个Socket连接请求
if (($msgsock = socket_accept($socket)) < 0) {
echo "socket_accept() failed: reason: " . socket_strerror($msgsock) . "\n";
break;
} else {
# 握手成功
# 最多只接收5个请求,然后就关闭连接
if($count > 5){
break;
};
# 发送内容给客户端
$msg ="测试成功!\n";
socket_write($msgsock, $msg, strlen($msg));
echo "测试成功了啊\n";
$buf = socket_read($msgsock, 8192);
$talkback = "收到的信息:$buf\n";
echo $talkback;
$count++;
}
socket_close($msgsock);
} while (true);
socket_close($socket);
}
/**
* 这是开启守护进程的程序
*/
function run_server () {
# ① fork 一个子进程号
$pid1 = pcntl_fork();
# 为0,表示在子进程执行线程内
if ($pid1 == 0) {
# ② setsid 将当前进程转换为守护进程
posix_setsid();
# ③ fork 再新建一个子进程号,就会挂载在父守护进程中
$pid2 = pcntl_fork();
if ($pid2 == 0) {
server();
} else {
# 防止获得控制终端
exit;
}
} else {
# 挂起父进程,防止出现僵尸进程
pcntl_wait($status);
}
}
# 启动服务端Socket,同时开启守护进程
run_server();
然后,我们关闭所有Xshell,然后重新打开一个Xshell标签,
先netstat -lntp一遍端口号,看看有没有被占用,
有的话则使用kill -9 端口对应的PID结束进程。
然后再使用php /var/www/demo/server.php,启动服务端Socket程序,并开启守护进程。
这时候你会发现,跟之前的启动不一样,这一次我们的Xshell并没有进入循环等待,而是退回到正常状态了:
这时候我们直接在Xshell里输入php /var/www/demo/client.php就可以进行调试啦。
基于这一个demo,同学们可以自己发散思维,实现复杂的聊天室功能,例如:腾讯的TIM等。