PHP Conference Japan 2024

socket_recv

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

socket_recv從已連線的通訊端接收資料

說明

socket_recv(
    Socket $socket,
    ?字串 &$data,
    整數 $length,
    整數 $flags
): 整數|false

函式 socket_recv() 會從 socket 接收 length 位元組的資料到 data 中。socket_recv() 可用於從已連線的 socket 收集資料。此外,可以指定一個或多個旗標來修改函式的行為。

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 實例;以前它是 資源

範例

範例 #1 socket_recv() 範例

此範例是 範例 中第一個範例的簡單改寫,使用 socket_recv()

<?php
error_reporting
(E_ALL);

echo
"<h2>TCP/IP Connection</h2>\n";

/* Get the port for the WWW service. */
$service_port = getservbyname('www', 'tcp');

/* Get the IP address for the target host. */
$address = gethostbyname('www.example.com');

/* Create a TCP/IP socket. */
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (
$socket === false) {
echo
"socket_create() failed: reason: " . socket_strerror(socket_last_error()) . "\n";
} else {
echo
"OK.\n";
}

echo
"Attempting to connect to '$address' on port '$service_port'...";
$result = socket_connect($socket, $address, $service_port);
if (
$result === false) {
echo
"socket_connect() failed.\nReason: ($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
"Sending HTTP HEAD request...";
socket_write($socket, $in, strlen($in));
echo
"OK.\n";

echo
"Reading response:\n\n";
$buf = 'This is my buffer.';
if (
false !== ($bytes = socket_recv($socket, $buf, 2048, MSG_WAITALL))) {
echo
"Read $bytes bytes from socket_recv(). Closing socket...";
} else {
echo
"socket_recv() failed; reason: " . 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 則註解

31
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,如果 socket 被遠端斷開連線,則返回 0。所以我只需要檢查 socket_recv 的返回值即可。現在這個問題聽起來很蠢,但我花了一些時間才找到答案。
希望這可以幫某些人省下一些頭髮 ;)
11
ss-130 at yandex dot ru
11 年前
<?php
$er
= error_reporting(0);
$bytes = socket_recv($socket,$buffer,1,MSG_WAITALL);
error_reporting($er);

// 這裡有個大 BUG
// 這些狀態是錯誤的且被交換了,已關閉的 socket 應該返回 "FALSE"
// 但實際上它交換了值:
// https://php.dev.org.tw/manual/en/function.socket-recv.php
//
if($bytes===false){ // 沒有可用資料,socket 未關閉
echo 'WS_READ_ERR1: '.socket_strerror(socket_last_error($socket)).PHP_EOL;
// 沒有可用資料時印出:
// WS_READ_ERR1: Resource temporarily unavailable
continue;
}else if(
$bytes===0){ // socket 已關閉
echo 'WS_READ_ERR2: '.socket_strerror(socket_last_error($socket)).PHP_EOL;
// socket 關閉時印出:
// WS_READ_ERR2: Success
$process->close();
}

?>
7
m_lajos at hotmail dot com
10 年前
根據錯誤報告頁面,這是缺少 MSG_DONTWAIT 旗標的解決方法

<?php if(!defined('MSG_DONTWAIT')) define('MSG_DONTWAIT', 0x40); ?>
2
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 <rathamahata@rathamahata.net>
http://www.rathamahata.net/
1
bastiaan at [no-spam] megabass dot nl
20 年前
如果你想要清空/取消設定 $buffer,但卻失敗了,請嘗試使用 0 作為旗標。
PHP_NORMAL_READ 和 PHP_BINARY_READ 的值分別為 1 和 2。
1
davide dot renzi at gmail dot com
12 年前
PHP 5.* 版本中存在一個錯誤:未定義 MSG_DONTWAIT 旗標(參見 https://bugs.php.net/bug.php?id=48326
2
lexkrstn at gmail dot com
6 年前
這些旗標似乎只是傳遞給作業系統底層的 recv() 函式,因此 Windows 上沒有 MSG_DONTWAIT 旗標,而且在這種情況下不應該自行定義它,因為它不會生效。
-1
匿名
3 年前
<?php

namespace Safe;

use
Safe\Exceptions\SocketsException;

/**
* 使用 socket_create 建立 socket 後,使用 socket_bind 將其綁定到名稱,並使用 socket_listen 告知其監聽連線後,此函式將接受該 socket 上的連入連線。一旦成功建立連線,就會返回一個新的 socket 資源,可用於通訊。如果 socket 上有多個連線排隊,則會使用第一個連線。如果沒有待處理的連線,socket_accept 將會阻塞,直到出現連線為止。如果使用 socket_set_blocking 或 socket_set_nonblock 將 socket 設定為非阻塞模式,則會返回 FALSE。
*
* socket_accept 返回的 socket 資源不能用於接受新的連線。但是,原始的監聽 socket 仍保持開啟狀態,可以重複使用。
*
* @param resource $socket 使用 socket_create 建立的有效 socket 資源。
* @return resource 成功時返回新的 socket 資源。實際的錯誤碼可以通過呼叫 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;
}

/**
* 建立 Socket 資源,並將其綁定到提供的 AddrInfo 資源。此函式的返回值可以用於 socket_listen。
*
* @param resource $addr 從 socket_addrinfo_lookup 建立的資源。
* @return resource 成功時返回 Socket 資源。
* @throws SocketsException
*
*/
function socket_addrinfo_bind($addr)
{
error_clear_last();
$result = \socket_addrinfo_bind($addr);
if (
$result === null) {
throw
SocketsException::createFromPhpError();
}
return
$result;
}
0
匿名
19 年前
我很高興 Bastion 留下上面關於神秘的 int 旗標的貼文。他正好解決了我花了六個小時才解決的問題。這是我的程式碼:

for($ct=1; $ct<=$numrecs; $ct++) {
$rec = "";
$nr=socket_recv($fp,$rec,77,0);
print "記錄 # $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.
-2
engine at [NO SPAM] illusiononly dot com
20 年前
為了在 Linux 和 Windows 作業系統上從以 Flash 作為客戶端的 socket 讀取資料,我使用以下函式。 $length 是區塊的大小,而不是要讀取的最大長度。它會持續讀取直到出現 EOL 字元或客戶端斷線(或發生錯誤),所以它也適用於較大的封包。

function read($descriptor, $length = 1024) {
$this->method = "read";
if(!$client){
echo("沒有有效的 socket 描述元 !\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;
}

}
-2
匿名
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.
-3
cottton at i-stats dot net
10 年前
socket_recv()
如果客戶端沒有傳回資料,則傳回 FALSE
如果客戶端斷線,則傳回 0 (零)

另外(假設 socket_select()「給了」我們一個「已變更」的 socket)
如果
socket_recv() 傳回 FALSE
且沒有收到任何位元組
那麼
客戶端「當機」(稱之為斷線)。

否則,如果
socket_recv() 傳回 0 (零)
且沒有收到任何位元組
那麼
客戶端「正常」斷線。

我非常確定 -- 99.99%。
範例
<?php
function receive($socket)
{
// !
// on all following cases we assume that
// socket_select() returned the current socket as "changed"
// !

$timeout = 3; // set your timeout

/* important */
$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']){
// client returned NO DATA
// but we were in a loop and could have got some data before:
if($received_bytes < 1){
// client is connected but sent NO DATA ?
// no:
// in this case the client must be "crashed" because -
// it is not possible to "send no data" (zero bytes)
// socket_select() now returns this socket as "changed" "forever"
throw new Exception(
'client crashed',
0 // your eCode here
);
}else{
// client returned DATA
return $received_data;
}
}
elseif(
$read === $socket_recv_return_values['client_disconnected']){
// client disconnected
if($received_bytes < 1){
// client disconnected before/without sending any bytes
throw new Exception(
'client disconnected',
0 // your eCode here
);
}
else{
// *this value* ^= $socket_recv_return_values['client_disconnected']
//
// client disconnected AFTER sending data (we were in a loop!)
// socket_select() will return this socket "forever" as "changed" and -
// socket_recv() will return *this value* "forever".
// we will be "back" again "very soon" to see:
// socket_recv() returns *this value* AND no bytes received
// which results in disconnect-exception above
return $received_data;
}
}
}
}
?>
-3
e-vela at bol dot com dot br
7 年前
MSG_PEEK 的使用範例:這個函式會告知 socket 是否有可供讀取的資料,但會保留這些資料以供日後讀取。

<?php
// 處理缺少 define 的變通方法
if( !defined('MSG_DONTWAIT')) define('MSG_DONTWAIT', 0x40);

// 檢查 socket 中是否有可用資料的函式
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