对于想要使用 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);
?>
(PHP 5, PHP 7, PHP 8)
stream_socket_client — 打开 Internet 或 Unix 域套接字连接
$address
,&$error_code
= null
,&$error_message
= null
,$timeout
= null
,$flags
= STREAM_CLIENT_CONNECT
,$context
= null
启动到由 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_CONNECT
和 STREAM_CLIENT_PERSISTENT
。
context
使用 stream_context_create() 创建的有效上下文资源。
如果失败,error_code
和 error_message
参数将填充系统级 connect()
调用中发生的实际系统级错误。 如果 error_code
中返回的值为 0
并且函数返回 false
,则表明错误发生在 connect()
调用之前。 这很可能是由于初始化套接字时出现问题造成的。 请注意,error_code
和 error_message
参数将始终通过引用传递。
版本 | 描述 |
---|---|
8.0.0 |
timeout 和 context 现在可以为空。 |
示例 #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() 检索可用传输的列表。 有关内置传输的列表,请参见 支持的套接字传输列表。
对于想要使用 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);
?>
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() 遵循类似的方案,但它没有此特定功能。
# 有些人可能会发现知道您的 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";
}
?>
如果您只需要检查流是否有数据,可以使用 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);
?>
我来到这里是因为 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;
}
?>
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, ...);
?>