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_create() 创建的 Socket 实例。

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
19 年前
我使用 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);

// MEGA BUG HERE
// 这些状态是错误的,并且被交换了,关闭的套接字必须是 "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
18 年前
看起来那些神秘的标志只是传递给您的操作系统系统调用的 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
5 年前
看起来这些标志只是传递给您操作系统的底层 recv() 函数,因此在 Windows 上没有 MSG_DONTWAIT 标志,并且您不应该自己定义它,在这种情况下,它根本不起作用。
Anonymous
3 年前
<?php

namespace Safe;

use
Safe\Exceptions\SocketsException;

/**
* 在使用 socket_create 创建套接字 socket 后
* 使用 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
*
*/
function 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
*
*/
function socket_addrinfo_bind($addr)
{
error_clear_last();
$result = \socket_addrinfo_bind($addr);
if (
$result === null) {
throw
SocketsException::createFromPhpError();
}
return
$result;
}
Anonymous
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
19 年前
为了从 Linux 和 Windows 操作系统上的套接字读取数据,并且 Flash 作为客户端,我使用以下函数。$length 是块的大小,而不是要读取的最大长度。它将继续读取,直到出现 EOL 字符或客户端断开连接(或者在发生错误的情况下),因此它也适用于较大的数据包。

function read($descriptor, $length = 1024) {
$this->method = "read";
if(!$client){
echo("No valid socket descriptor !\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){
//error
return false;
}elseif ($flag==0){
//Client disconnected
return false;
}else{
return $read;
}

}
Anonymous
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
9 年前
socket_recv()
如果客户端没有返回数据,则返回 FALSE
如果客户端断开连接,则返回 0(零)

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

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

我非常确定——99.99%。
示例
<?php
function receive($socket)
{
// !
// 在以下所有情况下,我们假设
// socket_select() 返回当前套接字为 "changed"
// !

$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(
'timeout after ' . ((!$received_bytes) ? 0 : $received_bytes) . ' bytes',
0 // your eCode here
);
}
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){
// 客户端已连接,但发送无数据?
// 不:
// 在这种情况下,客户端必须是 "crashed",因为 -
// 不可能 "发送无数据"(零字节)
// socket_select() 现在会永远返回这个套接字为 "changed"
throw new Exception(
'client crashed',
0 // your eCode here
);
}else{
// 客户端返回数据
return $received_data;
}
}
elseif(
$read === $socket_recv_return_values['client_disconnected']){
// 客户端已断开连接
if($received_bytes < 1){
// 客户端在发送任何字节之前/没有发送任何字节的情况下断开连接
throw new Exception(
'client disconnected',
0 // your eCode here
);
}
else{
// *this value* ^= $socket_recv_return_values['client_disconnected']
//
// 客户端在发送数据后断开连接(我们正在循环中!)
// socket_select() 将永远返回这个套接字为 "changed",并且 -
// socket_recv() 将永远返回 *this value*。
// 我们将 "很快" 再次 "回来" 查看:
// socket_recv() 返回 *this value* 并且没有收到字节
// 这会导致上面的断开连接异常
return $received_data;
}
}
}
}
?>
e-vela at bol dot com dot br
7 years ago
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