用 PHP 实现聊天室出现问题,请教下问题所在。 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
awanganddong
V2EX    PHP

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

  •  
  •   awanganddong 2019-10-11 16:56:31 +08:00 4926 次点击
    这是一个创建于 2263 天前的主题,其中的信息可能已经有所发展或是发生改变。

    问题描述:

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

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

    我大概想了下

    其实是分两种情况的

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

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


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

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


    谢谢大家了
    lolizeppelin
        16
    lolizeppelin  
       2019-10-12 09:50:40 +08:00
    这些都是基础的系统调用,任何语言都一样的,无非都是多路复用阻塞非阻塞的基础知识
    纠结到 php 上反而容易混乱,你应该去读 c 相关的文档
    其实这些去看 python 文档也不错,比较接近 c 的语法也容易理解
    awanganddong
        17
    awanganddong  
    OP
       2019-10-12 11:43:20 +08:00
    php 和 c 实现是大致一样,只不过 c 指针不可控。从 php 入手可以浅入深出(重要的是我学的就是 php 啊)。
    毕竟牵扯到底层函数 php 都是移植 c 的。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2749 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 34ms UTC 13:38 PVG 21:38 LAX 05:38 JFK 08:38
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86