1.用 telnet 127.0.0.1 9999 首次访问,则显示第一个用户访问
2.依次打开另外界面进行,则不显示内容
3.但是如果我在第一个 telnet 输入内容 这时候才会出现第二个用户访问
4.发送消息,也是如此
举例:比如 A 发消息,B、C、D 可以收到。但是实际上呢,A 发消息后,B、C、D 必须也发一条消息后,才能收到其他人的消息。
$host = "0.0.0.0";
$port = "9999";
$fd = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($fd, $host, $port);
socket_listen($fd);
$conn = [];
$writeFds = [];
$e = null;
echo PHP_EOL . PHP_EOL . "欢迎来到 select 聊天室" . PHP_EOL . PHP_EOL;
echo " tcp://{$host}:{$port}" . PHP_EOL;
echo $fd;
while (true) {
$readFds = array_merge($conn, [$fd]);
if (socket_select($readFds, $writeFds, $e, null) > 0) {
if (in_array($fd, $readFds)) {
$newConn = socket_accept($fd);
$i = intval($newConn);
$conn[$i] = $newConn;
$readFds[$i] = $newConn;
$writeFds[$i] = $newConn;
$key = array_search($fd, $readFds);
unset($readFds[$key]);
echo "第" . $i . "个用户来到聊天室" . PHP_EOL;
}
if (count($readFds) > 0) {
foreach ($readFds as $rfd) {
$line = socket_read($rfd, 2048);
foreach ($conn as $value) {
if ($rfd == $value) {
continue;
}
socket_write($value, $line, strlen($line));
}
}
}
} else {
continue;
}
}
1
lllllliu 2019-10-11 17:15:11 +08:00
我没记错的话,一次只能处理一个 Client 吧。要 fork 子进程来处理通信吧。
有条件的话直接用现成的库吧。 |
2
awanganddong OP 专门为了研究 socket 写的 demo。不是为了用在线上的。
|
3
haiyang416 2019-10-11 18:01:09 +08:00
注释掉这行:$readFds[$i] = $newConn;
你刚 accept 就把它加入到 $readFds 数组,这时它还没有数据读取,接着马上在来一个阻塞的 socket_read, 你的程序会一直阻塞到这个连接有数据发送才会执行后面的代码。 并且每个新连接过来都会出现这个情况。 |
4
awanganddong OP @haiyang416
你的意思是说,我将套接字放入$readFds 数组中,这时候会执行 count($readFds)>0 里边的逻辑( A ),这时候对进程来说是阻塞的,同时如果有新的连接进入,就必须等 A 处理结束,才可以进行。? |
5
liqihang 2019-10-11 18:27:40 +08:00
@lllllliu 如果用 IO 多路复用的话,一个进程也能服务多个 client,看上去应该是 @haiyang416 说的阻塞问题
|
6
wo642436249 2019-10-11 18:37:52 +08:00 via Android
浪费时间,浪费生命,直接上 swoole 吧
|
7
awanganddong OP 就是 @haiyang416 说的问题
我大概想了下 其实是分两种情况的 1.客户端首次连接,然后执行代码块( in_array($fd,$readFds)) 2.客户端再次连接,然后执行代码块( count($readFds) > 0 ) |
8
awanganddong OP 但是现在又出现让我困惑的问题
telnet 连接后,发送消息,代码是从那个位置开始走的。 按照实际是从 socket_select 这里开始走的 |
9
haiyang416 2019-10-11 18:50:58 +08:00
@awanganddong 跟情况无关,你的理解有问题。在 socket_select 之后 $readFds 里都是可以用于读取的“句柄”,它已经被 socket_select 函数修改了,这时你不应该自己往这个数组里加入新的数据,除非你可以确定它是有数据可读的。你只需要把新的连接加入到 $conn 数组,等待 while 循环再次调用 socket_select 即可。
|
10
awanganddong OP @haiyang416 你能帮我解释下下边这个情况吗。
就是 telnet 初次连接的时候,打印 socket_select 下$readFds 里边为服务器的 fd 与客户端 fd, 然后 telnet 发送消息,就只剩下客服端的 fd。 socket_select 处理第一次连接和发送消息有什么不同呢。不理解 |
11
haiyang416 2019-10-11 23:21:03 +08:00 1
@awanganddong
$readFds 里面装的就是所有需要监听的描述符,可能有服务器的 fd,也可能有客户端连接的 fd,在经过 socket_select 之后,该函数会删除 $readFds 里暂时不可读的 fd。 $readFds = [$fd]; 当前有新连接 socket_accept 之后增加了 $conn1,再次进入 while 循环。 -------------------------------------------------------- $readFds = [$fd, $conn1]; 当前又有新连接 socket_accept 之后增加了 $conn2,再次进入 while 循环。 -------------------------------------------------------- $readFds = [$fd, $conn1, $conn2]; 比如当前没有新连接,$conn1 收到了消息,$conn2 没有收到消息, 那么 socket_select 函数就会把 $fd 和 $conn2 从数组中删除,即 $readFds = [$conn1]; 处理完 $conn1 后会再次进入 while 循环。 -------------------------------------------------------- $readFds = [$fd, $conn1, $conn2]; 当前又有新连接,$conn1 和 $conn2 没有收到消息 那么 socket_select 函数就会把 $conn1 和 $conn2 从数组中删除,即 $readFds = [$fd]; socket_accept 之后增加了 $conn3,再次进入 while 循环。 -------------------------------------------------------- $readFds = [$fd, $conn1, $conn2, $conn3]; |
12
Seanfuck 2019-10-11 23:36:28 +08:00 via iPhone
读和写要分开,可读不一定能写的,写要单独 foreach 那个 writefds。读写的数据要暂存一下,可写时才写出去
|
13
simonlu9 2019-10-12 00:14:52 +08:00
@haiyang416 他的代码有 $readFds = array_merge($conn, [$fd]); 这句,$conn 是一个 sokect 数组,所以可以保持每次 select 都是在链接的客户端,我测试过没问题,a 发消息,b,c,d 都可以收到
|
14
simonlu9 2019-10-12 00:32:30 +08:00
@haiyang416 不好意思,你说的是对的, $readFds[$i] = $newConn; 这句代码有问题,如果新连接 A 到刚 accept 马上 read 会导致堵塞代码的,所以当之后的连接都感应不了,只有等 A 发消息后,其他的连接才能 accept,然后才能收到消息
,处理这种错误最好每次读完消息都把它剔除,然后再加入 select |
15
awanganddong OP |
16
lolizeppelin 2019-10-12 09:50:40 +08:00
这些都是基础的系统调用,任何语言都一样的,无非都是多路复用阻塞非阻塞的基础知识
纠结到 php 上反而容易混乱,你应该去读 c 相关的文档 其实这些去看 python 文档也不错,比较接近 c 的语法也容易理解 |
17
awanganddong OP php 和 c 实现是大致一样,只不过 c 指针不可控。从 php 入手可以浅入深出(重要的是我学的就是 php 啊)。
毕竟牵扯到底层函数 php 都是移植 c 的。 |