在 PHP 中,socket_recv 和 socket_select 函数是处理网络编程中非常常用的工具,尤其是当你需要进行非阻塞式的 I/O 操作时。使用这两个函数,你可以避免因等待网络数据而导致的阻塞,从而提高程序的效率和响应能力。本文将详细介绍如何利用这两个函数实现非阻塞接收。
在传统的阻塞式接收中,当你使用 socket_recv 等函数接收数据时,程序会停在那一行代码,直到有数据到达或者发生超时才会继续执行。这意味着,如果没有数据到达,程序就会“卡住”,无法进行其他操作。
而非阻塞接收则是指程序不会在等待数据的过程中停止执行,它会在接收数据时进行检查,若没有数据,则可以继续进行其他操作。这样一来,程序在进行 I/O 操作时就不会因为等待数据而阻塞。
socket_select 函数提供了一种检查多个 socket 是否就绪的方式,它可以帮助我们避免阻塞式的等待。当我们调用 socket_select 时,它会检查给定的 socket 是否有数据可读,若有,则返回相应的 socket 对象。你可以通过该函数在多个 socket 上执行非阻塞接收操作。
<span><span><span class="hljs-keyword">int</span></span><span> </span><span><span class="hljs-title function_ invoke__">socket_select</span></span><span> ( </span><span><span class="hljs-keyword">array</span></span><span> &</span><span><span class="hljs-variable">$read</span></span><span>, </span><span><span class="hljs-keyword">array</span></span><span> &</span><span><span class="hljs-variable">$write</span></span><span>, </span><span><span class="hljs-keyword">array</span></span><span> &</span><span><span class="hljs-variable">$except</span></span><span>, </span><span><span class="hljs-keyword">int</span></span><span> </span><span><span class="hljs-variable">$tv_sec</span></span><span>, </span><span><span class="hljs-keyword">int</span></span><span> </span><span><span class="hljs-variable">$tv_usec</span></span><span> )
</span></span>
$read:一个数组,包含所有需要检测是否可读的 socket。
$write:一个数组,包含所有需要检测是否可写的 socket。
$except:一个数组,包含所有需要检测是否有异常的 socket。
$tv_sec 和 $tv_usec:指定超时时间(秒和微秒)。若设为 null,则为无限等待。
<span><span><span class="hljs-keyword">int</span></span><span> </span><span><span class="hljs-title function_ invoke__">socket_recv</span></span><span> ( resource </span><span><span class="hljs-variable">$socket</span></span><span> , </span><span><span class="hljs-keyword">string</span></span><span> &</span><span><span class="hljs-variable">$buf</span></span><span> , </span><span><span class="hljs-keyword">int</span></span><span> </span><span><span class="hljs-variable">$length</span></span><span> , </span><span><span class="hljs-keyword">int</span></span><span> </span><span><span class="hljs-variable">$flags</span></span><span> )
</span></span>
$socket:要接收数据的 socket 资源。
$buf:接收到的数据存储在这个变量中。
$length:要接收的最大字节数。
$flags:控制接收操作的标志,通常为 0。
以下是一个使用 socket_recv 搭配 socket_select 实现非阻塞接收的完整示例:
<span><span><span class="hljs-meta"><?php</span></span><span>
</span><span><span class="hljs-comment">// 创建一个 TCP socket</span></span><span>
</span><span><span class="hljs-variable">$server</span></span><span> = </span><span><span class="hljs-title function_ invoke__">socket_create</span></span><span>(AF_INET, SOCK_STREAM, SOL_TCP);
</span><span><span class="hljs-comment">// 设置服务器地址和端口</span></span><span>
</span><span><span class="hljs-variable">$address</span></span><span> = </span><span><span class="hljs-string">'127.0.0.1'</span></span><span>;
</span><span><span class="hljs-variable">$port</span></span><span> = </span><span><span class="hljs-number">12345</span></span><span>;
</span><span><span class="hljs-comment">// 绑定 socket</span></span><span>
</span><span><span class="hljs-title function_ invoke__">socket_bind</span></span><span>(</span><span><span class="hljs-variable">$server</span></span><span>, </span><span><span class="hljs-variable">$address</span></span><span>, </span><span><span class="hljs-variable">$port</span></span><span>);
</span><span><span class="hljs-comment">// 开始监听</span></span><span>
</span><span><span class="hljs-title function_ invoke__">socket_listen</span></span><span>(</span><span><span class="hljs-variable">$server</span></span><span>);
</span><span><span class="hljs-comment">// 设置超时时间</span></span><span>
</span><span><span class="hljs-variable">$timeout</span></span><span> = </span><span><span class="hljs-number">10</span></span><span>; </span><span><span class="hljs-comment">// 超时 10 秒</span></span><span>
</span><span><span class="hljs-variable">$read</span></span><span> = [</span><span><span class="hljs-variable">$server</span></span><span>]; </span><span><span class="hljs-comment">// 用于检测是否有连接</span></span><span>
</span><span><span class="hljs-variable">$write</span></span><span> = </span><span><span class="hljs-variable">$except</span></span><span> = [];
</span><span><span class="hljs-comment">// 进入循环,等待客户端连接</span></span><span>
</span><span><span class="hljs-keyword">while</span></span><span> (</span><span><span class="hljs-literal">true</span></span><span>) {
</span><span><span class="hljs-comment">// 使用 socket_select 来检测是否有新连接</span></span><span>
</span><span><span class="hljs-variable">$changed</span></span><span> = </span><span><span class="hljs-title function_ invoke__">socket_select</span></span><span>(</span><span><span class="hljs-variable">$read</span></span><span>, </span><span><span class="hljs-variable">$write</span></span><span>, </span><span><span class="hljs-variable">$except</span></span><span>, </span><span><span class="hljs-variable">$timeout</span></span><span>);
</span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-variable">$changed</span></span><span> === </span><span><span class="hljs-literal">false</span></span><span>) {
</span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-string">"socket_select failed\n"</span></span><span>;
</span><span><span class="hljs-keyword">break</span></span><span>;
}
</span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-variable">$changed</span></span><span> > </span><span><span class="hljs-number">0</span></span><span>) {
</span><span><span class="hljs-comment">// 检查是否有新的客户端连接</span></span><span>
</span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-title function_ invoke__">in_array</span></span><span>(</span><span><span class="hljs-variable">$server</span></span><span>, </span><span><span class="hljs-variable">$read</span></span><span>)) {
</span><span><span class="hljs-variable">$client</span></span><span> = </span><span><span class="hljs-title function_ invoke__">socket_accept</span></span><span>(</span><span><span class="hljs-variable">$server</span></span><span>);
</span><span><span class="hljs-variable">$read</span></span><span>[] = </span><span><span class="hljs-variable">$client</span></span><span>; </span><span><span class="hljs-comment">// 新连接加入读取数组</span></span><span>
</span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-string">"Client connected\n"</span></span><span>;
}
</span><span><span class="hljs-comment">// 遍历所有连接的 socket,检查是否有数据可读</span></span><span>
</span><span><span class="hljs-keyword">foreach</span></span><span> (</span><span><span class="hljs-variable">$read</span></span><span> </span><span><span class="hljs-keyword">as</span></span><span> </span><span><span class="hljs-variable">$sock</span></span><span>) {
</span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-variable">$sock</span></span><span> !== </span><span><span class="hljs-variable">$server</span></span><span>) {
</span><span><span class="hljs-variable">$data</span></span><span> = </span><span><span class="hljs-string">''</span></span><span>;
</span><span><span class="hljs-variable">$bytes_received</span></span><span> = </span><span><span class="hljs-title function_ invoke__">socket_recv</span></span><span>(</span><span><span class="hljs-variable">$sock</span></span><span>, </span><span><span class="hljs-variable">$data</span></span><span>, </span><span><span class="hljs-number">1024</span></span><span>, MSG_DONTWAIT);
</span><span><span class="hljs-keyword">if</span></span><span> (</span><span><span class="hljs-variable">$bytes_received</span></span><span> === </span><span><span class="hljs-number">0</span></span><span>) {
</span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-string">"Client disconnected\n"</span></span><span>;
</span><span><span class="hljs-title function_ invoke__">socket_close</span></span><span>(</span><span><span class="hljs-variable">$sock</span></span><span>);
</span><span><span class="hljs-variable">$read</span></span><span> = </span><span><span class="hljs-title function_ invoke__">array_diff</span></span><span>(</span><span><span class="hljs-variable">$read</span></span><span>, [</span><span><span class="hljs-variable">$sock</span></span><span>]); </span><span><span class="hljs-comment">// 从读取列表中移除该 socket</span></span><span>
} </span><span><span class="hljs-keyword">elseif</span></span><span> (</span><span><span class="hljs-variable">$bytes_received</span></span><span> > </span><span><span class="hljs-number">0</span></span><span>) {
</span><span><span class="hljs-keyword">echo</span></span><span> </span><span><span class="hljs-string">"Received data: <span class="hljs-subst">$data</span></span></span><span>\n";
}
}
}
}
</span><span><span class="hljs-comment">// 这里可以添加更多的处理逻辑,比如检查写入操作、超时处理等</span></span><span>
}
</span><span><span class="hljs-title function_ invoke__">socket_close</span></span><span>(</span><span><span class="hljs-variable">$server</span></span><span>);
</span><span><span class="hljs-meta">?></span></span><span>
</span></span>
创建和绑定 socket:使用 socket_create 创建一个 TCP socket,并用 socket_bind 绑定到指定的地址和端口。
socket_select 监听多个 socket:在 socket_select 中传入监听的 $read 数组,$server socket 会被用来接受新的客户端连接。$timeout 参数设置为 10 秒,即在 10 秒内,如果没有数据传入或新连接,socket_select 会返回。
接收数据:如果有数据可读,通过 socket_recv 进行非阻塞接收。如果客户端断开连接,关闭对应的 socket,并从 $read 数组中移除。
非阻塞模式:在本例中,我们使用了 MSG_DONTWAIT 标志来实现非阻塞接收,意味着如果没有数据可读,socket_recv 不会阻塞程序。
socket_select 的使用:socket_select 用于检查多个 socket 的状态。在非阻塞模式下,它可以帮助你避免一直等待某个 socket 的数据,而是可以在有数据时立即处理。
内存管理:使用 socket_select 时,务必确保从 $read 数组中移除已经关闭的 socket,避免内存泄漏和无效的 socket 检测。
通过结合使用 socket_recv 和 socket_select,你可以非常方便地实现非阻塞接收。这种方式不仅可以避免程序因等待数据而阻塞,同时也能够高效地处理多个 socket 连接,实现更高效的网络通信。
非阻塞接收是一个重要的技术,尤其在构建高并发、低延迟的网络服务时非常有用。通过对 PHP socket 编程的灵活运用,可以提高应用程序的性能和可扩展性。