PHP Conference Japan 2024

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 参数。timeout 是在 socket_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 数组捕获的唯一异常是在套接字上接收到的超出范围的数据。

参见

添加注释

用户贡献的注释 22 条注释

vardhan ( at ) rogers ( dot ) com
19 年前
一个使用 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);

// 将套接字绑定到本地地址和端口 $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";

// 从 clients-with-data 数组中删除监听套接字
$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");

}
// 广播循环结束

}

}
// 读取循环结束
}

// 关闭监听套接字
socket_close($sock);
?>
malcolm.murphy
8年前
上面 [Viorel] 提到了这一点,但我认为值得重复……

[vardhan (at) rogers (dot) com] 提供的示例虽然其他方面都很好,但使用 int(0) 作为 $tv_sec 的值,这将导致迭代循环尽可能快地执行,从而消耗所有可用的 CPU 时间。

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

将超时设置为显式 null 值基本上等同于将其设置为无限大。脚本仅在每次发生事件时才执行一次 while 循环。
Richard Neill
20年前
使用 socket_select 监视套接字数组的输入,然后使用 PHP_NORMAL_READ 使用 socket_read(),这可能是个坏主意。

虽然这看起来很理想,但如果有人发送给你缺少尾随 \n \r 的格式错误的输入,你最终可能会得到一个永久阻塞的程序。猜猜我是怎么发现的。
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("缓冲区对于 $sock 非空,长度: ".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("接收: $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("消息被截断: 重新发送: $msg");
} else {
return
true;
}
}
return
false;
}
?>
danny at dansheps dot com
16年前
补充一点,由于笔记中的信息有些旧。现在似乎保留了密钥。

因此,如果您依赖于知道需要处理哪些密钥,并且像我一样认为它没有保留。那么它确实保留了。
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);

希望这有帮助。
antti r
8年前
截至2015年12月和PHP 5.3.3,socket_select()的最大套接字数仍然是1024,并且不能通过进程文件限制(bash ulimit -n)来提高。需要重新编译PHP才能提高限制。或者可以选择fork或多个二进制文件。
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);
?>
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% (如果没有任务则立即返回)
simon dot riget at gmail dot com
10年前
一个用PHP编写的非常简单的HTTP Web服务器
在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");

// 将主机套接字绑定到本地主机或 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);
?>
magemerlin at list dot ru
14年前
如果你使用的是Flash客户端 - 你应该了解一些特定功能

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

if ('<policy-file-request/>'==substr($data, 0, 22))
{
echo "策略请求。\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();
}
否则
{
// 此处为与客户端的常规IO操作
}

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

俄罗斯示例在此 - http://www.flasher.ru/forum/showpost.php?p=901346&postcount=7
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()只返回一个结果。但是可能会有10000个结果排队,如果你的周转时间太慢(服务器繁忙、其他休眠等),你将永远无法在近实时的情况下处理所有结果。
qartis at gmail dot com
17年前
关于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)
匿名用户
17年前
如果要使用简单的分数值作为超时

<?php
socket_select
(..., floor($timeout), ceil($timeout*1000000));
?>
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;
}
}
?>
Whosawhatsis at that google email thingy
18年前
解决键未保留问题的另一个方法是使用另一个数组来查找套接字,该数组使用其资源标识符作为键。在某些情况下可以使用array_flip()获得此结果,但如果每个套接字都与一个对象相关联,则此方法特别有用。在这种情况下,你可以让对象的构造函数使用其套接字资源标识符作为键将指向自身的指针添加到查找数组中,并使用以下代码执行与socket_select()返回的每个套接字关联的对象的读取方法

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

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

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

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

最好保留键以确定需要从中接收流的哪个流,但是你必须使用一些精巧的foreach循环来确定要检查哪些套接字。
drenintell
19 年前
我之前在2005年4月28日10:19发布的帖子的续集位于
http://ca3.php.net/manual/en/function.socket-select.php

如下所示:(链接分为两部分)

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

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

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

socket_select($reads);

foreach (
$reads as $read) {
/* 执行一些操作 */
}
?>
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));
}
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
"[错误]socket_select() 失败
("
.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);
daveb at optusnet dot com dot au
22年前
如果您以前没有做过任何网络编程,PHP 的 socket_select() 可能会让您觉得有点奇怪。我在 http://dave.dapond.com/socketselect.php.txt 编写了一个简单的 php “对讲机”脚本,以演示 select 的多套接字用法。
To Top