stream_socket_client

(PHP 5, PHP 7, PHP 8)

stream_socket_client打开 Internet 或 Unix 域套接字连接

描述

stream_socket_client(
    string $address,
    int &$error_code = null,
    string &$error_message = null,
    ?float $timeout = null,
    int $flags = STREAM_CLIENT_CONNECT,
    ?resource $context = null
): resource|false

启动到由 address 指定的目的地进行的流或数据报连接。 创建的套接字类型由使用标准 URL 格式指定的传输确定:transport://target。 对于 TCP 和 UDP 等 Internet 域套接字 (AF_INET),address 参数的 target 部分应由主机名或 IP 地址后跟冒号和端口号组成。 对于 Unix 域套接字,target 部分应指向文件系统上的套接字文件。

注意:

默认情况下,流将以阻塞模式打开。 你可以使用 stream_set_blocking() 将其切换到非阻塞模式。

参数

address

要连接到的套接字的地址。

error_code

如果连接失败,将设置为系统级错误号。

error_message

如果连接失败,将设置为系统级错误消息。

timeout

connect() 系统调用应该超时之前的秒数。 默认情况下,使用 default_socket_timeout

注意: 此参数仅在不进行异步连接尝试时适用。

注意:

要为套接字上的读取/写入数据设置超时,请使用 stream_set_timeout(),因为 timeout 仅在进行套接字连接时适用。

flags

位掩码字段,可以设置为任何连接标志的组合。 目前连接标志的选择仅限于 STREAM_CLIENT_CONNECT (默认)、STREAM_CLIENT_ASYNC_CONNECTSTREAM_CLIENT_PERSISTENT

context

使用 stream_context_create() 创建的有效上下文资源。

返回值

成功时将返回一个流资源,该资源可与其他文件函数一起使用(例如 fgets()fgetss()fwrite()fclose()feof()),失败时返回 false

错误/异常

如果失败,error_codeerror_message 参数将填充系统级 connect() 调用中发生的实际系统级错误。 如果 error_code 中返回的值为 0 并且函数返回 false,则表明错误发生在 connect() 调用之前。 这很可能是由于初始化套接字时出现问题造成的。 请注意,error_codeerror_message 参数将始终通过引用传递。

变更日志

版本 描述
8.0.0 timeoutcontext 现在可以为空。

范例

示例 #1 stream_socket_client() 例子

<?php
$fp
= stream_socket_client("tcp://www.example.com:80", $errno, $errstr, 30);
if (!
$fp) {
echo
"$errstr ($errno)<br />\n";
} else {
fwrite($fp, "GET / HTTP/1.0\r\nHost: www.example.com\r\nAccept: */*\r\n\r\n");
while (!
feof($fp)) {
echo
fgets($fp, 1024);
}
fclose($fp);
}
?>

示例 #2 使用 UDP 连接

从本地主机上的 UDP 服务“daytime”(端口 13)检索日期和时间。

<?php
$fp
= stream_socket_client("udp://127.0.0.1:13", $errno, $errstr);
if (!
$fp) {
echo
"ERROR: $errno - $errstr<br />\n";
} else {
fwrite($fp, "\n");
echo
fread($fp, 26);
fclose($fp);
}
?>

注释

警告

UDP 套接字有时似乎在没有错误的情况下打开,即使远程主机不可访问。 错误只有在您读取或写入套接字的数据时才会出现。 造成这种情况的原因是 UDP 是一种“无连接”协议,这意味着操作系统在实际需要发送或接收数据之前不会尝试为套接字建立链接。

注意: 当指定数值 IPv6 地址(例如 fe80::1)时,您必须将 IP 包含在方括号中——例如,tcp://[fe80::1]:80

注意:

根据环境的不同,Unix 域或可选的连接超时可能不可用。 可以使用 stream_get_transports() 检索可用传输的列表。 有关内置传输的列表,请参见 支持的套接字传输列表

参见

添加笔记

用户贡献笔记 6 笔记

22
nicholas at nicholaswilliams dot net
15 年前
对于想要使用 stream_socket_client() 连接到本地 UNIX 套接字但找不到如何操作的文档的用户,以下是一个(粗略的)示例

<?php

$sock
= stream_socket_client('unix:///full/path/to/my/socket.sock', $errno, $errstr);

fwrite($sock, 'SOME COMMAND'."\r\n");

echo
fread($sock, 4096)."\n";

fclose($sock);

?>
10
Vasil Rangelov a.k.a. boen_robot
13 年前
remote_socket 参数在其末尾(好吧... 在端口之后)也可以包含一个“/”后跟一个唯一标识符。这在您想要创建到相同 transport://host:port 组合的多个持久连接时特别有用。

示例
<?php
$socket
= stream_socket_client('tcp://mysql.example.com:3306/root', $errorno, $errorstr, $timeout, STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT);
?>

请注意,虽然 (p)fsockopen() 遵循类似的方案,但它没有此特定功能。
6
wbeaver at afilias dot info
16 年前
# 有些人可能会发现知道您的 caCert
# 必须是 pem 格式,并且 PHP 似乎喜欢
# 将您的密钥、证书和 cacert pem 合并
# 在一个文件中(否则我会遇到各种“未知链”
# 错误)
#
# 所以,(Linux 用户),按如下方式合并您的组件
# (其中当前工作目录是存储
# 证书组件的目录)
#
# cat key.pem >certchain.pem
# cat cert.pem >>certchain.pem
# cat cacert.pem >>certchain.pem
#
# 然后,php....
##################################

<?php

$host
= 'host.domain.tld';
$port = 1234;
$timeout = 10;

$cert = '/path/to/your/certchain/certchain.pem';
$context = stream_context_create(array('ssl'=>array('local_cert'=> $cert,
)));

if (
$fp = stream_socket_client('ssl://'.$host.':'.$port, $errno, $errstr, 30,
STREAM_CLIENT_CONNECT, $context)) {
fwrite($fp, "\n");
echo
fread($fp,8192);
fclose($fp);
} else {
echo
"ERROR: $errno - $errstr<br />\n";
}

?>
2
Daniel
9 年前
如果您只需要检查流是否有数据,可以使用 stream_get_content 和 strtr 函数。stream_get_content
将流的剩余部分读入字符串。
<?php

$addr
= gethostbyname('www.example.com');

$client = stream_socket_client("tcp://$addr:80", $errno, $errorMessage);

if(
$client === false){
throw new
UnexpectedValueException("Failed to connect: $errorMessage");
}

fwrite($client, "GET / HTTP/1.0\r\nhost: 'www.example.com'\r\nAccept: */*\r\n\r\n");

$variable = stream_get_content($client);

if(
strstr($variable,'data your looking for'))
echo
"The data you are looking for is here";
else
echo
"data not found";

fclose($client);
?>
2
robin at gareus dot org
15 年前
我来到这里是因为 fsockopen() 在 PHP5 中不支持任何 SSL 证书检查。

虽然 curl 很好,但我使用 stream_socket_client() 通过 HTTPS 发出 XML-RPC POST 请求,并且由于我没有找到任何执行此操作的 PHP 代码,因此我将附加一个包含 HTTP-Digest Auth 的示例(例如 trac 的 WikiRPCInterface2)

<?php
#################################################
# $host: hostname ; eg 'example.org'
# $path: request' eg '/index.php?id=123'
# $data_to_send : data to POST after the HTTP header.
#
# if $opts is an empty array() a standard HTTP to port 80 request is performed.
#
# set auth['type']='basic' to use plain-text auth,
# digest-auth will be handled automatically if $auth['username'] is set and a 401
# status is encountered. - use auth['type']='nodigest' to override.
#
##
function httpPost($host, $path, $data_to_send,
$opts=array('cert'=>"", 'headers'=>0, 'transport' =>'ssl', 'port'=>443),
$auth=array('username'=>"", 'password'=>"", 'type'=>"")
) {
$transport=''; $port=80;
if (!empty(
$opts['transport'])) $transport=$opts['transport'];
if (!empty(
$opts['port'])) $port=$opts['port'];
$remote=$transport.'://'.$host.':'.$port;

$context = stream_context_create();
$result = stream_context_set_option($context, 'ssl', 'verify_host', true);
if (!empty(
$opts['cert'])) {
$result = stream_context_set_option($context, 'ssl', 'cafile', $opts['cert']);
$result = stream_context_set_option($context, 'ssl', 'verify_peer', true);
} else {
$result = stream_context_set_option($context, 'ssl', 'allow_self_signed', true);
}
$fp = stream_socket_client($remote, $err, $errstr, 60, STREAM_CLIENT_CONNECT, $context);

if (!
$fp) {
trigger_error('httpPost error: '.$errstr);
return
NULL;
}

$req='';
$req.="POST $path HTTP/1.1\r\n";
$req.="Host: $host\r\n";
if (
$auth['type']=='basic' && !empty($auth['username'])) {
$req.="Authorization: Basic ";
$req.=base64_encode($auth['username'].':'.$auth['password'])."\r\n";
}
elseif (
$auth['type']=='digest' && !empty($auth['username'])) {
$req.='Authorization: Digest ';
foreach (
$auth as $k => $v) {
if (empty(
$k) || empty($v)) continue;
if (
$k=='password') continue;
$req.=$k.'="'.$v.'", ';
}
$req.="\r\n";
}
$req.="Content-type: text/xml\r\n";
$req.='Content-length: '. strlen($data_to_send) ."\r\n";
$req.="Connection: close\r\n\r\n";

fputs($fp, $req);
fputs($fp, $data_to_send);

while(!
feof($fp)) { $res .= fgets($fp, 128); }
fclose($fp);

if (
$auth['type']!='nodigest'
&& !empty($auth['username'])
&&
$auth['type']!='digest' # prev. digest AUTH failed.
&& preg_match("/^HTTP\/[0-9\.]* 401 /", $res)) {
if (
1 == preg_match("/WWW-Authenticate: Digest ([^\n\r]*)\r\n/Us", $res, $matches)) {
foreach (
split(",", $matches[1]) as $i) {
$ii=split("=",trim($i),2);
if (!empty(
$ii[1]) && !empty($ii[0])) {
$auth[$ii[0]]=preg_replace("/^\"/",'', preg_replace("/\"$/",'', $ii[1]));
}
}
$auth['type']='digest';
$auth['uri']='https://'.$host.$path;
$auth['cnonce']=randomNonce();
$auth['nc']=1;
$a1=md5($auth['username'].':'.$auth['realm'].':'.$auth['password']);
$a2=md5('POST'.':'.$auth['uri']);
$auth['response']=md5($a1.':'
.$auth['nonce'].':'.$auth['nc'].':'
.$auth['cnonce'].':'.$auth['qop'].':'.$a2);
return
httpPost($host, $path, $data_to_send, $opts, $auth);
}
}

if (
1 != preg_match("/^HTTP\/[0-9\.]* ([0-9]{3}) ([^\r\n]*)/", $res, $matches)) {
trigger_error('httpPost: invalid HTTP reply.');
return
NULL;
}

if (
$matches[1] != '200') {
trigger_error('httpPost: HTTP error: '.$matches[1].' '.$matches[2]);
return
NULL;
}

if (!
$opts['headers']) {
$res=preg_replace("/^.*\r\n\r\n/Us",'',$res);
}
return
$res;
}
?>
-12
bisho at onirica dot com
14 年前
stream_socket_client 用于直接套接字,比使用起来容易得多,因为您可以直接使用 fwrite / fget / fclose 函数,但我发现很难找到如何连接到 UNIX 域套接字。要使用的 URL 是“udg:///path/to/socket”。

例如,要记录到日志套接字(如 syslog),您可以使用

<?php
$socket
= stream_socket_client('udg:///dev/log',
$errorno,
$errorstr,
$timeout);
fwrite($socket, ...);
?>
To Top