
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 @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 的。 |