PHP Conference Japan 2024

socket_recv

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

socket_recv从已连接的套接字接收数据

描述

socket_recv(
    Socket $socket,
    ?string &$data,
    int $length,
    int $flags
): int|false

函数 socket_recv()socket 中接收 length 字节的数据到 data 中。 socket_recv() 可用于从已连接的套接字收集数据。此外,可以指定一个或多个标志来修改函数的行为。

data 是通过引用传递的,因此它必须在参数列表中指定为变量。由 socket_recv()socket 读取的数据将返回到 data 中。

参数

socket

socket 必须是 Socket 实例,该实例先前由 socket_create() 创建。

data

接收到的数据将被提取到用 data 指定的变量中。如果发生错误,如果连接重置,或者如果没有可用数据,则 data 将被设置为 null

length

最多将从远程主机获取 length 字节。

flags

flags 的值可以是以下标志的任意组合,并使用二进制 OR (|) 运算符连接。

flags 的可能值
标志 描述
MSG_OOB 处理带外数据。
MSG_PEEK 从接收队列的开头接收数据,而不将其从队列中删除。
MSG_WAITALL 阻塞直到至少接收到 length 个字节。但是,如果捕获到信号或远程主机断开连接,则函数可能会返回较少的数据。
MSG_DONTWAIT 设置此标志后,即使函数通常会阻塞,它也会返回。

返回值

socket_recv() 返回接收到的字节数,如果发生错误则返回 false。可以通过调用 socket_last_error() 来检索实际的错误代码。此错误代码可以传递给 socket_strerror() 以获取错误的文本说明。

变更日志

版本 描述
8.0.0 socket 现在是 Socket 实例;之前,它是一个 resource

范例

示例 #1 socket_recv() 示例

此示例是对 示例 中第一个示例的简单重写,以使用 socket_recv()

<?php
error_reporting
(E_ALL);

echo
"<h2>TCP/IP 连接</h2>\n";

/* 获取 WWW 服务的端口。 */
$service_port = getservbyname('www', 'tcp');

/* 获取目标主机的 IP 地址。 */
$address = gethostbyname('www.example.com');

/* 创建 TCP/IP 套接字。 */
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (
$socket === false) {
echo
"socket_create() 失败:原因: " . socket_strerror(socket_last_error()) . "\n";
} else {
echo
"OK.\n";
}

echo
"尝试连接到 '$address' 端口 '$service_port'...";
$result = socket_connect($socket, $address, $service_port);
if (
$result === false) {
echo
"socket_connect() 失败。\n原因:($result) " . socket_strerror(socket_last_error($socket)) . "\n";
} else {
echo
"OK.\n";
}

$in = "HEAD / HTTP/1.1\r\n";
$in .= "Host: www.example.com\r\n";
$in .= "Connection: Close\r\n\r\n";
$out = '';

echo
"发送 HTTP HEAD 请求...";
socket_write($socket, $in, strlen($in));
echo
"OK.\n";

echo
"读取响应:\n\n";
$buf = 'This is my buffer.';
if (
false !== ($bytes = socket_recv($socket, $buf, 2048, MSG_WAITALL))) {
echo
"从 socket_recv() 读取 $bytes 字节。关闭套接字...";
} else {
echo
"socket_recv() 失败;原因: " . socket_strerror(socket_last_error($socket)) . "\n";
}
socket_close($socket);

echo
$buf . "\n";
echo
"OK.\n\n";
?>

以上示例将输出类似以下内容

<h2>TCP/IP Connection</h2>
OK.
Attempting to connect to '208.77.188.166' on port '80'...OK.
Sending HTTP HEAD request...OK.
Reading response:

Read 123 bytes from socket_recv(). Closing socket...HTTP/1.1 200 OK
Date: Mon, 14 Sep 2009 08:56:36 GMT
Server: Apache/2.2.3 (Red Hat)
Last-Modified: Tue, 15 Nov 2005 13:24:10 GMT
ETag: "b80f4-1b6-80bfd280"
Accept-Ranges: bytes
Content-Length: 438
Connection: close
Content-Type: text/html; charset=UTF-8

OK.

添加注释

用户贡献的注释 13 个注释

dgk at tcde dot ru
20 年前
我使用了 socket_select 和 socket_recv 以及 while 循环,并在远程端关闭连接时遇到了麻烦。下面的代码产生了无限循环,并且 socket_select 立即返回(这导致了高 CPU 时间消耗)。

<?

socket_set_nonblock($my_socket);
$streams = array($my_socket/*, ... */);

$lastAccess = time();
while (socket_select($streams, $write = NULL, $except = NULL, SLEEP_TIME_SECONDS, SLEEP_TIME_MILLISECONDS) !== FALSE) {
if (in_array($my_socket, $streams)) {
while (@socket_recv($my_socket, $data, 8192, 0)) {
echo $data;
}
$lastAccess = time();
} else {
if (time()-$lastAccess > LAST_ACCESS_TIMEOUT) {
break;
}
}
// ...
$streams = array($my_socket/*, ... */);
}

?>

解决方案很简单,但很难找到,因为 socket_recv 没有记录。如果数据不存在,socket_recv 返回 FALSE,如果套接字已关闭(由远程端断开连接),则返回 0。所以,我只需要检查 socket_recv 的返回值。这个问题现在听起来很愚蠢,但我花了一些时间才找到它。
我希望这能帮一些人省些头发;)
ss-130 at yandex dot ru
11 年前
<?php
$er
= error_reporting(0);
$bytes = socket_recv($socket,$buffer,1,MSG_WAITALL);
error_reporting($er);

// 这里有重大错误
// 这些状态是错误的并且被交换了,关闭的套接字必须使用“FALSE”
// 但实际上他交换了值:
// https://php.net/manual/en/function.socket-recv.php
//
if($bytes===false){ // 没有可用的数据,套接字未关闭
echo 'WS_READ_ERR1: '.socket_strerror(socket_last_error($socket)).PHP_EOL;
// 打印当没有可用数据时:
// WS_READ_ERR1: Resource temporarily unavailable
continue;
}else if(
$bytes===0){ // 套接字已关闭
echo 'WS_READ_ERR2: '.socket_strerror(socket_last_error($socket)).PHP_EOL;
// 打印当套接字关闭时:
// WS_READ_ERR2: Success
$process->close();
}

?>
m_lajos at hotmail dot com
10 年前
根据错误报告页面,针对缺少的 MSG_DONTWAIT 标志的解决方法

<?php if(!defined('MSG_DONTWAIT')) define('MSG_DONTWAIT', 0x40); ?>
rathamahata at rathamahata dot net
19 年前
看起来那些神秘的标志只是传递给你的操作系统系统调用的 recv(2) 标志,仅此而已……

ext/sockets/sockets.c:PHP_FUNCTION(socket_recv)
...
if ((retval = recv(php_sock->bsd_socket, recv_buf, len, flags)) < 1) {
efree(recv_buf);
...

对于 Linux,你可以键入 `man 2 recv`,你将看到这些标志的完整描述。

Sergey S. Kosrtyliov <[email protected]>
http://www.rathamahata.net/
bastiaan at [no-spam] megabass dot nl
20 年前
如果你想清空/取消设置 $buffer,但未能做到这一点,请尝试使用 0 作为标志。
PHP_NORMAL_READ 和 PHP_BINARY_READ 分别持有 1 和 2 作为值。
davide dot renzi at gmail dot com
12 年前
在 PHP 5.* 版本中存在一个错误:MSG_DONTWAIT 标志未定义(参见 https://bugs.php.net/bug.php?id=48326
lexkrstn at gmail dot com
6 年前
看起来这些标志只是传递给操作系统的底层 recv() 函数,因此 Windows 上没有 MSG_DONTWAIT 标志,并且在这种情况下你不应该自己定义它,它根本不起作用。
匿名
3 年前
<?php

命名空间 Safe;

使用
Safe\Exceptions\SocketsException;

/**
* 在使用 socket_create 创建套接字后,
* 使用 socket_bind 绑定到名称,并使用
* socket_listen 告知监听连接,
* 此函数将接受该套接字上的传入连接。
* 建立成功连接后,将返回一个新的套接字资源,
* 该资源可用于通信。如果套接字上有
* 多个排队的连接,则将使用第一个连接。
* 如果没有待处理的连接,则 socket_accept 将阻塞,
* 直到出现连接。如果套接字
* 已使用 socket_set_blocking 或
* socket_set_nonblock 设置为非阻塞,则将返回 FALSE。
*
* socket_accept 返回的套接字资源
* 不能用于接受新连接。
* 但是,原始的监听套接字
* 仍然打开,并且可以
* 重用。
*
* @param resource $socket 使用 socket_create 创建的有效套接字资源。
* @return resource 成功时返回一个新的套接字资源。实际的
* 错误代码可以通过调用
* socket_last_error 获取。此错误代码可以传递给
* socket_strerror 以获取错误的文本说明。
* @throws SocketsException
*
*/
函数 socket_accept($socket)
{
error_clear_last();
$result = \socket_accept($socket);
if (
$result === false) {
throw
SocketsException::createFromPhpError();
}
return
$result;
}

/**
* 创建一个套接字资源,并将其绑定到提供的 AddrInfo 资源。此函数的返回值
* 可与 socket_listen 一起使用。
*
* @param resource $addr 由 socket_addrinfo_lookup 创建的资源。
* @return resource 成功时返回套接字资源。
* @throws SocketsException
*
*/
函数 socket_addrinfo_bind($addr)
{
error_clear_last();
$result = \socket_addrinfo_bind($addr);
if (
$result === null) {
throw
SocketsException::createFromPhpError();
}
return
$result;
}
匿名用户
19 年前
我很高兴 Bastion 留下上面关于神秘 int 标记的帖子。他刚刚帮助我解决了一个我花了六个小时才解决的问题。这是我的代码

for($ct=1; $ct<=$numrecs; $ct++) {
$rec = "";
$nr=socket_recv($fp,$rec,77,0);
print "Rec # $ct -->";
print "$rec";
print "<br>";
}

这段代码非常简单,它只是循环遍历我的所有记录并打印出来。所有记录都是 77 字节,并且都以句点结尾。前 36 条记录完美打印,然后在第 37 条记录时出现问题。记录开始出现偏移。第 37 条记录的最后几个字符最终打印在第 38 条记录上。发送端的数据是完美的,所以我知道问题出在 socked_recv 上。

阅读上面帖子后,我尝试更改 int 标记。将标记更改为 2 有效
$nr=socket_recv($fp,$rec,77,2);

现在一切都完美对齐了。我一直将 int 标记保留为 0,因为它没有文档记录。

Martin K.
engine at [NO SPAM] illusiononly dot com
20 年前
为了从 Linux 和 Windows 操作系统上的套接字读取数据,并将 Flash 作为客户端,我使用了下面的函数。$length 是块的大小,而不是要读取的最大长度。它将继续读取,直到出现 EOL 字符或客户端断开连接(或发生错误),因此它也适用于更大的数据包。

function read($descriptor, $length = 1024) {
$this->method = "read";
if(!$client){
echo("无效的套接字描述符!\n");
return false;
}
$read ='';
while(($flag=socket_recv($descriptor, $buf, $length,0))>0){
$asc=ord(substr($buf, -1));
if ($asc==0) {
$read.=substr($buf,0,-1);
break;
}else{
$read.=$buf;
}
}
if ($flag<0){
//错误
return false;
}elseif ($flag==0){
//客户端断开连接
return false;
}else{
return $read;
}

}
匿名用户
19 年前
我之前的帖子不正确。将 int 标记设置为 2 显然重置了文件位置指针,因此我读取的是第一条记录的重复。

我的变通方法最终如下

for($ct=1; $ct<=$numrecs; $ct++) {
$rec = "";
$nr=socket_recv($fp,$rec,76,0);

//获取额外的字节。
$terminator = "";
while ($terminator != ".") {
$nr=socket_recv($fp,$terminator,1,0);
}

$custarray[]=substr($rec,0,76);
}

Martin K.
cottton at i-stats dot net
10 年前
socket_recv()
如果客户端没有返回数据,则返回 FALSE
如果客户端断开连接,则返回 0(零)

还(假设 socket_select() “给了”我们一个“更改”的套接字)
如果
socket_recv() 返回 FALSE
并且没有收到任何字节
那么
客户端“崩溃”(称之为断开连接)。

否则如果
socket_recv() 返回 0(零)
并且没有收到任何字节
那么
客户端“正常”断开连接。

我非常确定——99.99%。
示例
<?php
函数 receive($socket)
{
// !
// 在所有以下情况下,我们假设
// socket_select() 将当前套接字返回为“已更改”
// !

$timeout = 3; // 设置超时时间

/* 重要 */
$socket_recv_return_values['no_data_received'] = false;
$socket_recv_return_values['client_disconnected'] = 0;

$start = time();
$received_data = null;
$received_bytes = null;
socket_set_nonblock($socket);
socket_clear_error();
while(
(
$t_out=((time()-$start) >= $timeout)) === false
and ($read=@socket_recv($socket, $buf, 4096, 0)) >= 1
){
$received_data = (isset($received_data)) ? $received_data . $buf : $buf;
$received_bytes = (isset($received_bytes)) ? $received_bytes + $read : $read;
}
$last_error = socket_last_error($socket);
socket_set_block($socket);

if(
$t_out === true){
throw new
Exception(
'超时后 ' . ((!$received_bytes) ? 0 : $received_bytes) . ' 字节',
0 // 您的错误代码
);
}
elseif(
$last_error !== false and $last_error !== 0){
throw new
Exception(
socket_strerror($last_error),
$last_error
);
}
else{
if(
$read === $socket_recv_return_values['no_data_received']){
// 客户端没有返回数据
// 但我们在循环中,可能之前获得了一些数据:
if($received_bytes < 1){
// 客户端已连接但未发送任何数据?
// 不:
// 在这种情况下,客户端必须“崩溃”,因为 -
// 不可能“发送无数据”(零字节)
// socket_select() 现在将此套接字返回为“已更改”"永远"
throw new Exception(
'客户端崩溃',
0 // 您的错误代码
);
}else{
// 客户端返回了数据
return $received_data;
}
}
elseif(
$read === $socket_recv_return_values['client_disconnected']){
// 客户端断开连接
if($received_bytes < 1){
// 客户端在发送任何字节之前/没有发送任何字节就断开连接
throw new Exception(
'客户端断开连接',
0 // 您的错误代码
);
}
else{
// *此值* ^= $socket_recv_return_values['client_disconnected']
//
// 客户端在发送数据后断开连接(我们在循环中!)
// socket_select() 将永远返回此套接字为“已更改”,并且 -
// socket_recv() 将永远返回*此值*。
// 我们将“很快”再次“返回”以查看:
// socket_recv() 返回*此值* 并且没有收到任何字节
// 这会导致上面的断开连接异常
return $received_data;
}
}
}
}
?>
e-vela at bol dot com dot br
7 年前
MSG_PEEK 用法示例:此函数告知套接字是否有可读数据,但保留它以备将来读取。

<?php
// 针对缺少 define 的临时解决方案
if(!defined('MSG_DONTWAIT')) define('MSG_DONTWAIT', 0x40);

// 检查套接字中是否有可用数据的函数
function SocketHasData($socket) {
// 基于以下事实:
// $result=0 -> 断开连接,$result=false -> 无数据

$data = ''; // 我们需要一个缓冲区,但不会使用它

// MSG_PEEK 表示保留队列中的数据,以便之后可以实际读取
$result = socket_recv($socket, $data, 1, MSG_PEEK | MSG_DONTWAIT );

if (
$result === false) return false; // 如果没有数据,则返回 false
return true; // 否则返回 true
}
?>
To Top