用 PHP 实现聊天室出现问题,请教下问题所在。

2019-10-11 16:56:31 +08:00
 awanganddong

问题描述:

     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;
            }
        }
4269 次点击
所在节点    PHP
17 条回复
lllllliu
2019-10-11 17:15:11 +08:00
我没记错的话,一次只能处理一个 Client 吧。要 fork 子进程来处理通信吧。
有条件的话直接用现成的库吧。
awanganddong
2019-10-11 17:21:04 +08:00
专门为了研究 socket 写的 demo。不是为了用在线上的。
haiyang416
2019-10-11 18:01:09 +08:00
注释掉这行:$readFds[$i] = $newConn;
你刚 accept 就把它加入到 $readFds 数组,这时它还没有数据读取,接着马上在来一个阻塞的 socket_read,
你的程序会一直阻塞到这个连接有数据发送才会执行后面的代码。
并且每个新连接过来都会出现这个情况。
awanganddong
2019-10-11 18:25:46 +08:00
@haiyang416

你的意思是说,我将套接字放入$readFds 数组中,这时候会执行 count($readFds)>0 里边的逻辑( A ),这时候对进程来说是阻塞的,同时如果有新的连接进入,就必须等 A 处理结束,才可以进行。?
liqihang
2019-10-11 18:27:40 +08:00
@lllllliu 如果用 IO 多路复用的话,一个进程也能服务多个 client,看上去应该是 @haiyang416 说的阻塞问题
wo642436249
2019-10-11 18:37:52 +08:00
浪费时间,浪费生命,直接上 swoole 吧
awanganddong
2019-10-11 18:39:37 +08:00
就是 @haiyang416 说的问题

我大概想了下

其实是分两种情况的

1.客户端首次连接,然后执行代码块( in_array($fd,$readFds))
2.客户端再次连接,然后执行代码块( count($readFds) > 0 )
awanganddong
2019-10-11 18:46:25 +08:00
但是现在又出现让我困惑的问题
telnet 连接后,发送消息,代码是从那个位置开始走的。
按照实际是从 socket_select 这里开始走的
haiyang416
2019-10-11 18:50:58 +08:00
@awanganddong 跟情况无关,你的理解有问题。在 socket_select 之后 $readFds 里都是可以用于读取的“句柄”,它已经被 socket_select 函数修改了,这时你不应该自己往这个数组里加入新的数据,除非你可以确定它是有数据可读的。你只需要把新的连接加入到 $conn 数组,等待 while 循环再次调用 socket_select 即可。
awanganddong
2019-10-11 21:19:12 +08:00
@haiyang416 你能帮我解释下下边这个情况吗。

就是 telnet 初次连接的时候,打印 socket_select 下$readFds 里边为服务器的 fd 与客户端 fd,
然后 telnet 发送消息,就只剩下客服端的 fd。


socket_select 处理第一次连接和发送消息有什么不同呢。不理解
haiyang416
2019-10-11 23:21:03 +08:00
@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];
Seanfuck
2019-10-11 23:36:28 +08:00
读和写要分开,可读不一定能写的,写要单独 foreach 那个 writefds。读写的数据要暂存一下,可写时才写出去
simonlu9
2019-10-12 00:14:52 +08:00
@haiyang416 他的代码有 $readFds = array_merge($conn, [$fd]); 这句,$conn 是一个 sokect 数组,所以可以保持每次 select 都是在链接的客户端,我测试过没问题,a 发消息,b,c,d 都可以收到
simonlu9
2019-10-12 00:32:30 +08:00
@haiyang416 不好意思,你说的是对的, $readFds[$i] = $newConn; 这句代码有问题,如果新连接 A 到刚 accept 马上 read 会导致堵塞代码的,所以当之后的连接都感应不了,只有等 A 发消息后,其他的连接才能 accept,然后才能收到消息
,处理这种错误最好每次读完消息都把它剔除,然后再加入 select
awanganddong
2019-10-12 09:34:33 +08:00
@haiyang416

整个流程明白了
接下来我还要理解下 socket_select 更近一些


谢谢大家了
lolizeppelin
2019-10-12 09:50:40 +08:00
这些都是基础的系统调用,任何语言都一样的,无非都是多路复用阻塞非阻塞的基础知识
纠结到 php 上反而容易混乱,你应该去读 c 相关的文档
其实这些去看 python 文档也不错,比较接近 c 的语法也容易理解
awanganddong
2019-10-12 11:43:20 +08:00
php 和 c 实现是大致一样,只不过 c 指针不可控。从 php 入手可以浅入深出(重要的是我学的就是 php 啊)。
毕竟牵扯到底层函数 php 都是移植 c 的。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/608313

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX