socket_select

(PHP 4 >= 4.1.0, PHP 5, PHP 7, PHP 8)

socket_select在给定的套接字数组上运行 select() 系统调用,并指定超时时间

描述

socket_select(
    ?array &$read,
    ?array &$write,
    ?array &$except,
    ?int $seconds,
    int $microseconds = 0
): int|false

socket_select() 接受套接字数组,并等待它们的状态发生变化。那些熟悉 BSD 套接字背景的人会认识到,这些套接字数组实际上是所谓的文件描述符集。三个独立的套接字数组将被监视。

参数

read

read 数组中列出的套接字将被监视,以查看是否有字符可供读取(更准确地说,是查看读取操作是否不会阻塞 - 特别是,套接字在文件结束时也是准备就绪的,在这种情况下 socket_read() 将返回一个零长度字符串)。

write

write 数组中列出的套接字将被监视,以查看写入操作是否不会阻塞。

except

except 数组中列出的套接字将被监视,以查看是否有异常发生。

seconds

secondsmicroseconds 共同构成 timeout 参数。 timeoutsocket_select() 返回之前经过时间的上限。 seconds 可以为零,导致 socket_select() 立即返回。这对于轮询很有用。如果 secondsnull(无超时),socket_select() 可以无限期阻塞。

microseconds

警告

退出时,数组将被修改以指示哪个套接字实际上发生了状态变化。

您不需要将每个数组都传递给 socket_select()。您可以省略它,并使用空数组或 null 代替。另外请不要忘记,这些数组是通过引用传递的,并且将在 socket_select() 返回后被修改。

注意:

由于当前 Zend 引擎的限制,无法直接将 null 作为常量修饰符传递给期望通过引用传递此参数的函数。相反,请使用一个临时变量或左端成员是一个临时变量的表达式

示例 #1 使用 nullsocket_select()

<?php
$e
= NULL;
socket_select($r, $w, $e, 0);
?>

返回值

成功时,socket_select() 返回修改后的数组中包含的套接字数量,如果在发生任何有趣的事情之前超时过期,则该数量可能为零。发生错误时,将返回 false。可以使用 socket_last_error() 获取错误代码。

注意:

请确保在检查错误时使用 === 运算符。由于 socket_select() 可能返回 0,因此与 == 的比较将被评估为 true

示例 #2 了解 socket_select() 的结果

<?php
$e
= NULL;
if (
false === socket_select($r, $w, $e, 0)) {
echo
"socket_select() 失败,原因: " .
socket_strerror(socket_last_error()) . "\n";
}
?>

示例

示例 #3 socket_select() 示例

<?php
/* 准备读取数组 */
$read = array($socket1, $socket2);
$write = NULL;
$except = NULL;
$num_changed_sockets = socket_select($read, $write, $except, 0);

if (
$num_changed_sockets === false) {
/* 错误处理 */
} else if ($num_changed_sockets > 0) {
/* 至少在一个套接字上发生了有趣的事情 */
}
?>

注释

注意:

请注意,一些套接字实现需要非常小心地处理。以下是一些基本规则

  • 您应该始终尝试在没有超时的情况下使用 socket_select()。如果没有任何数据可用,您的程序应该没有任何操作。依赖于超时的代码通常不可移植,并且难以调试。
  • 如果您不打算在 socket_select() 调用后检查套接字的结果并进行相应的响应,则不要将任何套接字添加到任何集合中。在 socket_select() 返回后,必须检查所有数组中的所有套接字。任何可供写入的套接字都必须写入,任何可供读取的套接字都必须从中读取。
  • 如果您读取/写入套接字返回的数组,请注意它们并不一定读取/写入您请求的全部数据。做好只能够读取/写入一个字节的准备。
  • 对于大多数套接字实现来说,使用 except 数组捕获的唯一异常是套接字上接收到的越界数据。

参见

添加注释

用户贡献注释 23 个注释

48
vardhan ( at ) rogers ( dot ) com
18 年前
一个使用 socket_select() 管理多个连接的简单 PHP 脚本。
使用 "telnet localhost 9050" 连接。它会将您通过 telnet 发送的消息广播给连接到服务器的其他用户 - 类似于聊天脚本

#!/usr/local/bin/php
<?php

$port
= 9050;

// 创建一个流式套接字,类型为 TCP/IP
$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

// 设置选项以重用端口
socket_set_option($sock, SOL_SOCKET, SO_REUSEADDR, 1);

// 将套接字“绑定”到“localhost”的地址,端口为 $port
// 这意味着此端口上的所有连接现在都由我们负责发送/接收数据、断开连接等。
socket_bind($sock, 0, $port);

// 开始监听连接
socket_listen($sock);

// 创建一个所有将连接到我们的客户端的列表。
// 将监听套接字添加到此列表中
$clients = array($sock);

while (
true) {
// 创建一个副本,以便 $clients 不受 socket_select() 修改
$read = $clients;

// 获取所有具有要读取数据的客户端的列表
// 如果没有具有数据的客户端,则转到下一轮迭代
if (socket_select($read, $write = NULL, $except = NULL, 0) < 1)
continue;

// 检查是否有客户端尝试连接
if (in_array($sock, $read)) {
// 接受客户端,并将其添加到 $clients 数组中
$clients[] = $newsock = socket_accept($sock);

// 向客户端发送欢迎消息
socket_write($newsock, "no noobs, but ill make an exception :)\n".
"There are ".(count($clients) - 1)." client(s) connected to the server\n");

socket_getpeername($newsock, $ip);
echo
"New client connected: {$ip}\n";

// 从具有数据的客户端数组中删除监听套接字
$key = array_search($sock, $read);
unset(
$read[$key]);
}

// 循环遍历所有具有要读取数据的客户端
foreach ($read as $read_sock) {
// 读取直到换行符或 1024 字节
// socket_read 在客户端断开连接时显示错误,因此静默错误消息
$data = @socket_read($read_sock, 1024, PHP_NORMAL_READ);

// 检查客户端是否已断开连接
if ($data === false) {
// 从 $clients 数组中删除客户端
$key = array_search($read_sock, $clients);
unset(
$clients[$key]);
echo
"client disconnected.\n";
// 继续读取下一个客户端,如果有的话
continue;
}

// 修剪尾部/开头的空格
$data = trim($data);

// 检查修剪空格后是否有任何数据
if (!empty($data)) {

// 将此发送到 $clients 数组中的所有客户端(第一个除外,它是监听套接字)
foreach ($clients as $send_sock) {

// 如果是监听套接字或收到消息的客户端,则转到列表中的下一个
if ($send_sock == $sock || $send_sock == $read_sock)
continue;

// 将消息写入客户端 - 在消息末尾添加一个换行符
socket_write($send_sock, $data."\n");

}
// 广播 foreach 的结尾

}

}
// 读取 foreach 的结尾
}

// 关闭监听套接字
socket_close($sock);
?>
8
malcolm.murphy
8 年前
如 [Viorel] 所述,我认为这值得重复...

[vardhan ( at ) rogers ( dot ) com] 提供的示例虽然在其他方面很出色,但它使用 int(0) 作为 $tv_sec 的值,这会导致迭代以最快速度循环,从而消耗所有可用的 CPU 时间。

*** 理想情况下,$tv_sec 应该始终设置为 NULL ***,尤其是在循环中使用 socket_select 时。如果您必须暂时停止监听事件以执行其他任务,则超时应该尽可能长,以减少 CPU 负载(另一个注释建议通过设置较低的 $tv_usec 值来防止 100% 的 CPU 使用率,但这只是稍微减轻了问题,并没有解决问题)。

将超时设置为显式的空值与将其设置为无穷大基本相同。脚本仅在每次有事件时执行一次 while 循环。
2
Richard Neill
19 年前
使用 socket_select 监视套接字数组以获取输入,然后使用 PHP_NORMAL_READ 使用 socket_read() 可能不是一个好主意。

虽然这看起来很理想,但如果您收到缺少尾随 \n \r 的格式错误的输入,您最终可能会得到一个永久阻塞的程序。猜猜我是怎么发现的。
1
jean at briskula dot si
13 年前
正如之前所说,一些客户端需要 \0 字符来结束传输,例如 Flash 的 XMLSocket。

您还应该准备好读取比您请求的更少的数据。

这是一个套接字缓冲区的示例 - 它是一个数组,其键是套接字资源,值为时间戳和接收数据的数组。

我发现发送数据的最佳实践是在其后添加一个换行符和一个零字符(\n\0),因为您可能会有不同类型的客户端,它们在从套接字读取数据时行为不同。有些需要 \n 来触发事件,有些需要 \0。

对于接收数据,有时您会收到分割的数据 - 这是因为缓冲区已满(在我的示例中为 8192 字节)或者它在更低级别传输过程中被破坏。

有时您可以一次读取两条消息,但它们之间有一个零字符,因此您可以使用 preg_split() 来分割消息。第二条消息可能不完整,因此将其添加到您的缓冲区中。

<?php
const message_delimiter = "\n\0";

/*
* 清理超过 1 小时的套接字缓冲区
*/
function clear_buffer() {
foreach(
$this->buffer as $key=>$val) {
if(
time() - $val['ts'] > 3600) {
unset(
$this->buffer[$key]);
}
}
}

/*
* 将数据添加到缓冲区
*/
function buffer_add($sock,$data) {
if(!isset(
$this->buffer[$sock])) {
$this->buffer[$sock]['data'] = '';
}

$this->buffer[$sock]['data'] .= $data;
$this->buffer[$sock]['ts'] = time();
}

function
buffer_get($sock) {
// 以字符串结尾分割缓冲区
$lines = preg_split('/\0/',$this->buffer[$sock]['data']);

// 将缓冲区重置为最后一行输入
// 如果缓冲区已完全发送,最后一行输入应该是
// 空字符串
$this->buffer[$sock]['data'] = trim($lines[count($lines)-1]);

if(!empty(
$this->buffer[$sock]['data'])) {
debug("buffer is not empty for $sock, len: ".strlen($this->buffer[$sock]['data']));
}

// 删除最后一行输入(不完整数据)
// 解析所有完整数据
unset($lines[count($lines)-1]);

// 仅返回完全发送的数据
return $lines;
}

function
read(&$sock,$len=8192,$flag=MSG_DONTWAIT) {
$lines = array();

$this->clear_buffer();

$bytes_read = @socket_recv($sock,$read_data,$len,$flag);

if (
$bytes_read === false || $bytes_read == 0) {
return
false;
} else {
debug("recv: $read_data");
$this->buffer_add($sock,$read_data);
return
$this->buffer_get($sock);
}
}

/*
* 写入套接字
* 在末尾添加换行符和空字符
* 一些客户端在收到换行符之前不会读取
*
* 如果数据被截断,尝试发送剩余的数据
*/
function write(&$sock,$msg) {
$msg = $msg.self::message_delimiter;
$length = strlen($msg);
while(
true) {
$sent = @socket_write($sock,$msg,$length);
if(
$sent <= 0) {
return
false;
}
if(
$sent < $length) {
$msg = substr($msg, $sent);
$length -= $sent;
debug("Message truncated: Resending: $msg");
} else {
return
true;
}
}
return
false;
}
?>
1
danny at dansheps dot com
15 年前
补充一点,因为笔记中的信息有点旧了。现在似乎保留了键。

所以,如果你依赖于知道哪些键需要处理,并且像我一样认为它没有保留。好吧,确实保留了。
2
calimero dot NOSPAM at NOSPAM dot creatixnet dot com
21 年前
请注意,超时参数对脚本的 CPU 使用率有重要影响。

将超时设置为 0 将导致 CPU 不断循环,没有时间休息和处理系统上的其他正在运行的进程,导致脚本运行时系统负载大幅增加。

我个人使用 15 毫秒的值作为这个参数。这确保了良好的监听频率,同时让系统负载保持清零。

示例
$read = array($ListeningSocket);
$num_changed_sockets = socket_select($read, $write = NULL, $except = NULL, 0, 10);

希望这有帮助。
2
antti r
8 年前
截至 2015 年 12 月和 PHP 5.3.3,socket_select() 的最大套接字数仍然是 1024,无法通过进程文件限制(bash ulimit -n)提高。需要重新编译 PHP 来提高限制。或者也可以选择 fork 或多个二进制文件。
2
John
14 年前
文档中描述 socket_select() 处理轮询读取套接字的方式相当含糊。

它说它检查读取是否不会“阻塞”,但对 socket_select() 的整体描述说它检查阻塞状态的更改。不幸的是,这些是相互矛盾的。

如果套接字的缓冲区中已经有数据,那么在该套接字上调用 socket_select() 将永远不会返回(假设超时为 null),并且会永远阻塞。:-( 这是因为阻塞状态不会改变。它只是保持“非阻塞”状态。

重要的是要记住,不要对可能已经存在数据的套接字调用 select()。

一个例子...
<?php
//... $socket 已经在...
$done = false;
$n = 0;
do{
$tmp = 0;
$r = $w = $e = array();
$r = array($socket);
socket_select($r,$w,$e,null);
$n = socket_recv($socket, $tmp, 1024, 0);

//$done = true; // 有什么东西决定我们已经读完...
}while(!$done);
?>
这可能不工作... socket_select() 始终被调用... 但是我们可能在输入缓冲区中存在数据。

我们需要确保上一次我们读取时,没有读取任何东西... (空缓冲区)
<?php
//... $socket 已经在...
$done = false;
$n = 0;
do{
$tmp = 0;
$r = $w = $e = array();
$r = array($socket);
if(
$n === 0) socket_select($r,$w,$e,null);
$n = socket_recv($socket, $tmp, 1024, 0);

//$done = true; // 有什么东西决定我们已经读完...
}while(!$done);
?>
2
Viorel
11 年前
你可能想使用

// 获取所有要读取数据的客户端列表
// 如果没有数据客户端,则进行下一次迭代
if (socket_select($read, $write = NULL, $except = NULL, NULL) < 1)
continue;

而不是
if (socket_select($read, $write = NULL, $except = NULL, 0) < 1)
continue;
这将使你的 CPU 占用率达到 100%(如果没有要执行的操作,则立即返回)
0
simon dot riget at gmail dot com
10 年前
一个用 PHP 编写的非常简单的 HTTP 服务器
在 shell 中使用 php <文件名> 运行它,并在浏览器中使用 <服务器地址>:8080/test 进行测试

<?php
// 减少显示的警告数量
error_reporting(E_ALL ^ E_NOTICE);

// 设置监听套接字
$host_socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if(!
$host_socket) die("无法启动事件服务器。socket_create: ". socket_strerror(socket_last_error())."\n");

// 设置重用端口选项
if(! socket_set_option($host_socket, SOL_SOCKET, SO_REUSEADDR, 1) )
die(
"无法启动事件服务器。socket_set_option: ". socket_strerror(socket_last_error())."\n");

// 将主机套接字绑定到 localhost 或 0.0.0.0 端口 8080
if(! socket_bind($host_socket,"0.0.0.0",8080) )
die(
"无法启动事件服务器。socket_bind: ". socket_strerror(socket_last_error())."\n");

// 开始监听连接
if(! socket_listen($host_socket,10) )
die(
"无法启动事件服务器。socket_listen: ".socket_strerror(socket_last_error())."\n");

while (
true) {
// 创建要监听更改的套接字列表,包括主机
$read = array($host_socket);

// 获取所有要读取数据的客户端列表
$ready=@socket_select($read, $write = NULL, $except = NULL,0);
if (
$ready=== false)
die(
"无法监听客户端: ". socket_strerror(socket_last_error()));

// 客户端请求服务
elseif($ready>0){
// 接受新的客户端
$newsocket = socket_accept($host_socket);

// 从套接字读取
$input = socket_read($newsocket, 1024);
if(
$input){
unset(
$client_header);
// 读取头部;拆分为安全行
$line=explode("\n",preg_replace('/[^A-Za-z0-9\-+\n :;=%*?.,\/_]/','',substr($input,0,2000)));
// 将请求行拆分为其各部分
list($client_header["method"],$client_header["url"],$client_header["protocol"])=explode(" ",$line[0]);
// 再次删除请求行。
unset($line[0]);
// 创建头部键值对数组
foreach($line as $l){
list(
$key,$val)=explode(": ",$l);
if(
$key) $client_header[strtolower($key)]=$val;
}
// 获取客户端 IP
socket_getpeername($newsocket, $client_header['ip']);

// 解码 URL
$client_header+=(array)parse_url($client_header['url']);
parse_str($client_header['query'],$client_header['arg']);

print_r($client_header);

// 提供文件
if(strpos($client_header['path'],".html") && file_exists(__DIR__.$client_header['path'])){
echo
"发送 HTML 页面到客户端\n";
socket_write($newsocket,"$client_header[protocol] 200 OK\r\n");
socket_write($newsocket,"Content-type: text/html; charset=utf-8\r\n\r\n");
socket_write($newsocket,file_get_contents(__DIR__.$client_header['path'])."\r\n\r\n");
socket_close($newsocket);
}elseif(
$client_header['path']=="/test"){
echo
"发送测试 HTML 页面到客户端\n";
socket_write($newsocket,"<!DOCTYPE HTML><html><head><html><body><h1>它正在工作!</h1>玩得开心\r\n");
socket_write($newsocket,"<pre>请求头部: ". print_r($client_header,true) . "</pre>\r\n");
socket_write($newsocket,"</body></html>\r\n\r\n");
socket_close($newsocket);
}else{
echo
"$client_header[protocol] 404 未找到\r\n";
socket_write($newsocket,"$client_header[protocol] 404 未找到\r\n\r\n");
socket_close($newsocket);
}
}
}
}
socket_close($host_socket);
?>
0
magemerlin at list dot ru
14 年前
如果你使用的是 Flash 客户端,你应该了解一些特定功能

1) 当客户端连接到服务器时,它会向你发送 "<policy-file-request/>"."\0" 字符串。服务器应该回答一个 XML 策略文件,然后断开与该客户端的连接。代码类似于

if ('<policy-file-request/>'==substr($data, 0, 22))
{
echo "policy 请求。\n";
flush();ob_flush();
$msg = '<'.'?xml version="1.0"?>
<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
<allow-access-from domain="*" to-ports="*" />
</cross-domain-policy>'."\0";
echo "告诉客户端 (crossdomain.xml) ... ";
flush();ob_flush();
socket_write($read_sock, $msg, strlen($msg));
echo "OK\n";
flush();ob_flush();

echo "关闭 ... ";
flush();ob_flush();
socket_close($read_sock);
echo "OK\n";
flush();ob_flush();
}
else
{
// 这里是对客户端的正常 IO 操作
}

2) 每个发送到客户端的输出都应该以 "\0" 结尾(如果在 Flash 客户端中使用 XMLSocket) - 否则 Flash 不会生成 onData 事件

俄罗斯的示例在这里 - http://www.flasher.ru/forum/showpost.php?p=901346&postcount=7
0
eidberger at jakota dot de
15 年前
我刚刚注意到,在使用 UDP 时,你必须循环 socket_select() 来获取所有排队的包。

<?php
while (socket_select ($aRead, $aWrite, $aExcept, 1) > 0) {
foreach (
$aReadUdp as $oSocket) {
$this->clientReadUdp ($oSocket);
}
}
?>

这一点很重要,因为每次对 UDP 调用 socket_select() 都会只返回一个结果。但可能会有 10,000 个结果排队,如果你的处理时间过慢(服务器繁忙、其他休眠等等),你将永远无法在接近实时的情况下处理完所有结果。
0
qartis at gmail dot com
16 年前
关于 vardhan ( at ) rogers ( dot ) com 发布的代码,在以下这行
if (socket_select($read, $write = NULL, $except = NULL, 0) < 1)
超时参数被意外地设置为 0,而不是 NULL。这意味着 select 调用将立即返回,而不是无限期阻塞。

将 socket_select 行更改为以下内容,以获得巨大成功
if (socket_select($read, $write = NULL, $except = NULL, NULL) < 1)
0
Anonymous
17 年前
如果你想使用一个简单的分数值来表示超时

<?php
socket_select
(..., floor($timeout), ceil($timeout*1000000));
?>
1
julian dot haupt at gmx dot de
22 年前
你好,
我刚刚创建了一个类,它与 Perl 的 IO::Select 类似,以便使套接字选择变得非常容易

你的脚本应该看起来像这样

<?php
$server
= new Server;
$client = new Client;

for (;;) {
foreach (
$select->can_read(0) as $socket) {

if (
$socket == $client->socket) {
// 新的客户端套接字
$select->add(socket_accept($client->socket));
}
else {
// $socket 上有数据可读
}
}
}
?>

当然,你应该实现一些例程来检测已断开的套接字并将其从 select 对象中移除。

你也可以执行输出缓冲,并在主循环中检查准备好写入的套接字。

<?php
class select {
var
$sockets;

function
select($sockets) {

$this->sockets = array();

foreach (
$sockets as $socket) {
$this->add($socket);
}
}

function
add($add_socket) {
array_push($this->sockets,$add_socket);
}

function
remove($remove_socket) {
$sockets = array();

foreach (
$this->sockets as $socket) {
if(
$remove_socket != $socket)
$sockets[] = $socket;
}

$this->sockets = $sockets;
}

function
can_read($timeout) {
$read = $this->sockets;
socket_select($read,$write = NULL,$except = NULL,$timeout);
return
$read;
}

function
can_write($timeout) {
$write = $this->sockets;
socket_select($read = NULL,$write,$except = NULL,$timeout);
return
$write;
}
}
?>
0
Whosawhatsis at that google email thingy
18 年前
解决键未保留问题的另一种方法是使用一个额外的数组来查找套接字,该数组使用它们的资源标识符作为键。在某些情况下,可以使用 array_flip() 获取此标识符,但如果每个套接字都与一个对象相关联,则此方法特别有用。在这种情况下,你可以让对象的构造函数将指向自身的指针添加到查找数组中,并使用套接字资源标识符作为键,并使用以下代码来执行与 socket_select() 返回的每个套接字相关联对象的 read 方法

<?php
socket_select
($reads, $writes, $excepts, 0);

foreach (
$sockets as $socket) {
$lookuparray[$socket]->read();
}
?>
0
crimson at NOSPAMtechnologist dot com
18 年前
请注意,在通过此函数运行后,结果数组不会保留键(PHP 4.3.2)。

之前
数组
(
[Client_Socket] => 资源 ID #6
[Server_Socket] => 资源 ID #9
)

之后
数组
(
[0] => 资源 ID #6
[1] => 资源 ID #9
)

如果能够保留键,以便确定需要从哪个流中接收数据,那就太好了,但是你需要使用一些巧妙的 foreach 循环来确定要检查哪些套接字。
0
drenintell
19 年前
我的上一篇帖子 2005-04-28 10:19 的后续帖子
http://ca3.php.net/manual/en/function.socket-select.php

这里是:(链接分为两部分)

'http://gtkphp.org/php_socket_select_hangs
_explanation_and_solution.html'
-1
ludvig dot ericson at gmail dot com
18 年前
关于下面的评论,不,它不会,这是一个系统调用,我相信保留键相当困难。

此外,socket_select 应该像用户输入的数组一样使用,你不知道你输入了什么。

<?php
$reads
= $clients;
$reads[] = $server;

socket_select($reads);

foreach (
$reads as $read) {
/* 做一些事情 */
}
?>
-1
juanfe0245 at gmail dot com
4 年前
<?php
//Creamos un socket de transmisión de tipo TCP / IP.
$servidorSocket=socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
//Configuramos la opción para reutilizar el puerto.
socket_set_option($servidorSocket,SOL_SOCKET,SO_REUSEADDR,1);
/*
Vinculamos el socket a nuestro puerto y dirección IP.
Ahora, todas las conecciones en este puerto son nuestro responsabilidad para recibir y enviar datos.
*/
socket_bind($servidorSocket,'localhost',9000);
//Comenzamos a escuchar conexiones.
socket_listen($servidorSocket);
/*
Creamos un arreglo con todos los clientes que se conectarán a nuestro servidor.
Añadimos nuestro socket servidor al arreglo de clientes.
*/
$clientes=[$servidorSocket];
$null=null;
while(
true){
//Creamos una copia de $clientes, debido a que $clientes no se modifica por la función socket_select().
$nuevoClienteLector=$clientes;
//Obtenemos una lista de todos los clientes que tienen datos para leer.
socket_select($nuevoClienteLector,$null,$null,10);
//Verificamos si hay un cliente tratando de conectarse.
if(in_array($servidorSocket,$nuevoClienteLector)){
//Aceptamos el cliente y lo añadimos al arreglo de $clientes.
$nuevoCliente=socket_accept($servidorSocket);
echo
"Socket aceptado.\n";
$clientes[]=$nuevoCliente;
//Obtenemos el encabezado del cliente y realizamos la comprobación del handshake.
$encabezado=socket_read($nuevoCliente,1024);
handshake($nuevoCliente,$encabezado);
//Eliminamos el cliente del arreglo $nuevoClienteLector.
$nuevoClienteIndex=array_search($servidorSocket,$nuevoClienteLector);
unset(
$nuevoClienteLector[$nuevoClienteIndex]);
}
//Recorremos todos los clientes que tienen datos por leer.
foreach($nuevoClienteLector as $cliente){
//Recibimos información del cliente conectado.
while(socket_recv($cliente,$datosCliente,1024,0)>=1){
//Decodificamos el mensaje que viene en bytes.
$mensaje=decodificar($datosCliente);
//Enviamos el mensaje a todos los sockets conectados.
enviar($clientes,$mensaje);
echo
$mensaje."\n";
break
2;
}
//Verificamos si el cliente esta desconectado.
$datosCliente=@socket_read($cliente,1024,PHP_NORMAL_READ);
if(
$datosCliente===false){
$clienteIndex=array_search($cliente,$clientes);
//Eliminamos el cliente del arreglo $clientes.
unset($clientes[$clienteIndex]);
echo
"Cliente desconectado.\n";
}
}
}
function
enviar($clientes,$mensaje){
$mensaje=codificar($mensaje);
foreach(
$clientes as $cliente){
@
socket_write($cliente,$mensaje,strlen($mensaje));
}
}
socket_close($servidorSocket);
function
codificar($socketData){
$b1 = 0x80 | (0x1 & 0x0f);
$length = strlen($socketData);

if(
$length <= 125)
$header = pack('CC', $b1, $length);
elseif(
$length > 125 && $length < 65536)
$header = pack('CCn', $b1, 126, $length);
elseif(
$length >= 65536)
$header = pack('CCNN', $b1, 127, $length);
return
$header.$socketData;
}
function
decodificar($socketData) {
$length = ord($socketData[1]) & 127;
if(
$length == 126) {
$masks = substr($socketData, 4, 4);
$data = substr($socketData, 8);
}
elseif(
$length == 127) {
$masks = substr($socketData, 10, 4);
$data = substr($socketData, 14);
}
else {
$masks = substr($socketData, 2, 4);
$data = substr($socketData, 6);
}
$socketData = "";
for (
$i = 0; $i < strlen($data); ++$i) {
$socketData .= $data[$i] ^ $masks[$i%4];
}
return
$socketData;
}
function
handshake($client_socket_resource,$received_header) {
$headers = array();
$lines = preg_split("/\r\n/", $received_header);
foreach(
$lines as $line){
$line = chop($line);
if(
preg_match('/\A(\S+): (.*)\z/', $line, $matches)) $headers[$matches[1]] = $matches[2];
}
$secKey = $headers['Sec-WebSocket-Key'];
$secAccept = base64_encode(pack('H*', sha1($secKey . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')));
$buffer = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" .
"Upgrade: websocket\r\n" .
"Connection: Upgrade\r\n" .
"Sec-WebSocket-Accept:$secAccept\r\n\r\n";
socket_write($client_socket_resource,$buffer,strlen($buffer));
}
-2
renmengyang567 at gmail dot com
5 年前
<解释>
一个简单的 socket-select 示例 ^_^

<?php
$sock
= socket_create(AF_INET, SOCK_STREAM, getprotobyname('tcp'));
socket_set_option($sock, SOL_SOCKET, SO_REUSEADDR, 1);
socket_bind($sock, '127.0.0.1',5000);
socket_listen($sock,1024);
$all_sock[(int)$sock] = $sock;

while (
true) {
$read = $write = $except = [];
$tv_sec = 5;
$tv_usec = 5000;
$read = $all_sock;

$changed = socket_select($read, $write, $except, $tv_sec, $tv_sec);

if (
false == $changed)
print
"[error]socket_select() failed
("
.socket_strerror(socket_last_error()) . ")\n";

if (
$changed < 1)
continue;

//var_dump($read);
//sleep(2);
foreach ($read as $rsock) {
// 服务端连接
if ($rsock === $sock) {
$client = socket_accept($sock);
$all_sock[(int)$client] = $client;

} else {
//客户端连接
$msg= socket_read($rsock, 400,PHP_NORMAL_READ);
if (
$msg !== '') {
var_dump($msg);
}
}
}
}
socket_close($sock);
-10
Aaron (aaron at acephalo dot us)
14 年前
socket_select() 也可以用作更精细的 sleep()

<?php
# 半秒睡眠
$undef = array();
socket_select($undef, $undef, $undef, 0, "500000");
?>
-4
daveb at optusnet dot com dot au
21 年前
如果你以前没有做过网络编程,PHP 的 socket_select() 可能会让你觉得有点奇怪。我写了一个简单的 php “partyline” 脚本,来演示在 http://dave.dapond.com/socketselect.php.txt 中 select 的多套接字用法
To Top