stream_socket_server

(PHP 5, PHP 7, PHP 8)

stream_socket_server创建 Internet 或 Unix 域服务器套接字

描述

stream_socket_server(
    string $address,
    int &$error_code = null,
    string &$error_message = null,
    int $flags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN,
    ?resource $context = null
): resource|false

在指定的 address 上创建流或数据报套接字。

此函数仅创建套接字,要开始接受连接,请使用 stream_socket_accept()

参数

address

创建的套接字类型由使用标准 URL 格式指定的传输确定:transport://target

对于 Internet 域套接字 (AF_INET)(如 TCP 和 UDP),remote_socket 参数的 target 部分应包含主机名或 IP 地址,后跟冒号和端口号。对于 Unix 域套接字,target 部分应指向文件系统上的套接字文件。

根据环境,Unix 域套接字可能不可用。可以使用 stream_get_transports() 获取可用传输的列表。有关内置传输的列表,请参见 支持的套接字传输列表

error_code

如果提供了可选的 error_codeerror_message 参数,则将设置它们以指示在系统级 socket()bind()listen() 调用中发生的实际系统级错误。如果 error_code 中返回的值为 0,并且函数返回了 false,则表示错误发生在 bind() 调用之前。这很可能是由于初始化套接字时出现问题。请注意,error_codeerror_message 参数始终将通过引用传递。

error_message

参见 error_code 描述。

flags

一个位掩码字段,可以设置为套接字创建标志的任何组合。

注意:

对于 UDP 套接字,您必须使用 STREAM_SERVER_BIND 作为 flags 参数。

context

返回值

返回创建的流,或在发生错误时返回 false

变更日志

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

示例

示例 #1 使用 TCP 服务器套接字

<?php
$socket
= stream_socket_server("tcp://0.0.0.0:8000", $errno, $errstr);
if (!
$socket) {
echo
"$errstr ($errno)<br />\n";
} else {
while (
$conn = stream_socket_accept($socket)) {
fwrite($conn, 'The local time is ' . date('n/j/Y g:i a') . "\n");
fclose($conn);
}
fclose($socket);
}
?>

下面的示例展示了如何充当时间服务器,该服务器可以响应时间查询,如 stream_socket_client() 上的示例所示。

注意: 大多数系统需要 root 权限才能在低于 1024 的端口上创建服务器套接字。

示例 #2 使用 UDP 服务器套接字

<?php
$socket
= stream_socket_server("udp://127.0.0.1:1113", $errno, $errstr, STREAM_SERVER_BIND);
if (!
$socket) {
die(
"$errstr ($errno)");
}

do {
$pkt = stream_socket_recvfrom($socket, 1, 0, $peer);
echo
"$peer\n";
stream_socket_sendto($socket, date("D M j H:i:s Y\r\n"), 0, $peer);
} while (
$pkt !== false);

?>

注意

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

参见

添加注释

用户贡献的注释 10 个注释

37
Heretic86 at roadrunner dot com
3 年前
这是一个关于如何设置 stream_socket_server 以使用 WSS(wss://)上的多个安全 Websocket 连接的示例,WSS 使用 SSL/TLS 作为传输。

这在 Windows Apache 服务器上运行,该服务器已注册域,并通过 Certbot 从 Let's Encrypt 获取 SSL 证书。

从控制台运行此脚本“php server.php”,以及带有以下代码的 JavaScript HTML 作为客户端
socket = new WebSocket('wss://php.net:1234');

<?php
$host
= '192.168.1.2';
$port = 1234;
$path = 'C:/Certbot/live/php.net/';
$transport = 'tlsv1.3';
$ssl = ['ssl' => [
'local_cert' => $path . 'cert.pem', // SSL Certificate
'local_pk' => $path . 'privkey.pem', // SSL Keyfile
'disable_compression' => true, // TLS compression attack vulnerability
'verify_peer' => false, // Set this to true if acting as an SSL client
'ssltransport' => $transport, // Transport Methods such as 'tlsv1.1', tlsv1.2'
] ];
$ssl_context = stream_context_create($ssl);
$server = stream_socket_server($transport . '://' . $host . ':' . $port, $errno, $errstr, STREAM_SERVER_BIND|STREAM_SERVER_LISTEN, $ssl_context);
if (!
$server) { die("$errstr ($errno)"); }
$clients = array($server);
$write = NULL;
$except = NULL;
while (
true) {
$changed = $clients;
stream_select($changed, $write, $except, 10);
if (
in_array($server, $changed)) {
$client = @stream_socket_accept($server);
if (!
$client){ continue; }
$clients[] = $client;
$ip = stream_socket_get_name( $client, true );
echo
"New Client connected from $ip\n";

stream_set_blocking($client, true);
$headers = fread($client, 1500);
handshake($client, $headers, $host, $port);
stream_set_blocking($client, false);

send_message($clients, mask($ip . ' connected'));
$found_socket = array_search($server, $changed);
unset(
$changed[$found_socket]);
}
foreach (
$changed as $changed_socket) {
$ip = stream_socket_get_name( $changed_socket, true );
$buffer = stream_get_contents($changed_socket);
if (
$buffer == false) {
echo
"Client Disconnected from $ip\n";
@
fclose($changed_socket);
$found_socket = array_search($changed_socket, $clients);
unset(
$clients[$found_socket]);
}
$unmasked = unmask($buffer);
if (
$unmasked != "") { echo "\nReceived a Message from $ip:\n\"$unmasked\" \n"; }
$response = mask($unmasked);
send_message($clients, $response);
}
}
fclose($server);

function
unmask($text) {
$length = @ord($text[1]) & 127;
if(
$length == 126) { $masks = substr($text, 4, 4); $data = substr($text, 8); }
elseif(
$length == 127) { $masks = substr($text, 10, 4); $data = substr($text, 14); }
else {
$masks = substr($text, 2, 4); $data = substr($text, 6); }
$text = "";
for (
$i = 0; $i < strlen($data); ++$i) { $text .= $data[$i] ^ $masks[$i % 4]; }
return
$text;
}
function
mask($text) {
$b1 = 0x80 | (0x1 & 0x0f);
$length = strlen($text);
if(
$length <= 125)
$header = pack('CC', $b1, $length);
elseif(
$length > 125 && $length < 65536)
$header = pack('CCn', $b1, 126, $length);
elseif(
$length >= 65536)
$header = pack('CCNN', $b1, 127, $length);
return
$header.$text;
}
function
handshake($client, $rcvd, $host, $port){
$headers = array();
$lines = preg_split("/\r\n/", $rcvd);
foreach(
$lines as $line)
{
$line = rtrim($line);
if(
preg_match('/\A(\S+): (.*)\z/', $line, $matches)){
$headers[$matches[1]] = $matches[2];
}
}
$secKey = $headers['Sec-WebSocket-Key'];
$secAccept = base64_encode(pack('H*', sha1($secKey . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')));
//hand shaking header
$upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" .
"Upgrade: websocket\r\n" .
"Connection: Upgrade\r\n" .
"WebSocket-Origin: $host\r\n" .
"WebSocket-Location: wss://$host:$port\r\n".
"Sec-WebSocket-Version: 13\r\n" .
"Sec-WebSocket-Accept:$secAccept\r\n\r\n";
fwrite($client, $upgrade);
}
function
send_message($clients, $msg){
foreach(
$clients as $changed_socket){
@
fwrite($changed_socket, $msg);
}
}
?>
30
blackmac01 at gmail dot com
8 年前
我正在编写一个 HTTP 服务器,需要 SSL 支持,但使用 PHP 流正确实现这一点需要一些反复试验。对于任何尝试使用 stream_socket_server 使 HTTP SSL 服务器正常工作的人

1) 您的 SSL 上下文将需要包含 'local_cert'。如果您没有将私钥与 local_cert 包含在一起,您还需要指定 'local_pk',即您的 RSA 密钥。您的密钥和证书应为 PEM 编码,这意味着 base-64。如果您的证书包含中间证书,则需要按正确顺序指定它们:您的签名证书、中间证书 1、中间证书 2 等。列表中的每个证书都需要验证上面的证书,但您不需要包含 SSL 签名者提供的 CA 根证书;这应该已经包含在客户端软件中(即信任根证书)。

您可以将私钥附加在包含证书的文件中,但我将我的私钥保存在单独的文件中。如果您在使用文本查看器查看密钥时看到“加密”一词,则需要输入正确的密码并指定上下文“passphrase”,否则您可以省略它。

作为服务器,verify_peer 与之无关,应设置为 false(如果您充当 SSL 客户端,则应始终为 true)。cafile 和 capath 上下文对于充当 SSL/TLS 服务器来说不是必需的,但如果您使用 PHP 作为客户端建立 SSL 连接,则需要它们。

最后,'ciphers' 上下文应设置为安全密码列表。搜索“mozilla 推荐密码”,并选择适合您的密码字符串,因为并非所有 openssl 支持的密码都是安全的。我选择了“intermediate”列表,它提供了较高的安全性和兼容性。

2) 当您为 stream_socket_server() 创建绑定时,请确保您选择 tcp:// 协议。不要使用 ssl:// 或 tls://。除了 tcp:// 之外的任何内容都不会作为服务器正确工作,这些传输方式是您在使用 PHP 作为客户端进行连接时使用的。

请记住,加密只有在 SSL 握手完成之后才会开始,因此服务器必须以非加密模式监听新连接,并且只有在交换证书并选择密码之后才会开始加密。当新的连接到来时,您使用 stream_socket_accept() 接受它,然后使用 stream_socket_enable_crypto() 启动 SSL 会话。

3) 请记住,SSL 握手需要时间,并且 stream_socket 协议是高级的,不像 socket 扩展那样响应迅速,因为它们会产生额外的开销。为此,您需要为接受新连接启用阻塞。

<在 ServerListenStream 上启用阻塞>
newConnStream = stream_socket_accept(ServerListenStream);
<在 ServerListenStream 上禁用阻塞>

<在 newConnStream 上启用阻塞>
stream_socket_enable_crypto(newConnStream, true, STREAM_CRYPTO_METHOD_SSLv23_SERVER);
<在 newConnStream 上禁用阻塞>

请注意,这主要用于 HTTP。如果您尝试执行 SMTP 等操作,则您的脚本必须对“starttls”命令做出反应,但它类似于上述操作,只是您需要在客户端的流上调用 stream_socket_enable_crypto() 函数之前等待“starttls”命令。

TLS 1.0 通常是首选,SSLv3 不安全,SSLv2 有漏洞。如果您在上下文中使用 mozilla 推荐的密码列表,您会没事的。希望这对某人有所帮助!
21
davidm at marketo dot com
14 年前
在某些特殊情况下,您可能希望创建一个 AF_INET 套接字(UDP 或 TCP),但让系统为您选择一个未使用的端口。这是互联网套接字的标准功能,但似乎没有记录如何为 stream_socket_server 函数执行此操作。看起来您可以通过将端口号选择为零来获得此行为,例如,我的以下测试打印了“127.0.0.1:4960”。

<?php
$sock
= stream_socket_server("udp://127.0.0.1:0");
$name = stream_socket_get_name($sock);
echo
$name;
?>
13
frxstrem
14 年前
使用 OpenSSL 扩展,PHP 可以自动生成自签名 SSL 证书,可用于基本身份验证和加密(尽管我建议使用签名证书而不是自签名证书)用于 SSL 服务器。

我通过“e at osterman dot com”扩展了脚本,以自动创建自签名证书

<?php
// Hello World! SSL HTTP 服务器。
// 在 PHP 5.1.2-1+b1 (cli) (build: 2006 年 3 月 20 日 04:17:24) 上测试

// 证书数据:
$dn = array(
"countryName" => "UK",
"stateOrProvinceName" => "Somerset",
"localityName" => "Glastonbury",
"organizationName" => "The Brain Room Limited",
"organizationalUnitName" => "PHP Documentation Team",
"commonName" => "Wez Furlong",
"emailAddress" => "[email protected]"
);

// 生成证书
$privkey = openssl_pkey_new();
$cert = openssl_csr_new($dn, $privkey);
$cert = openssl_csr_sign($cert, null, $privkey, 365);

// 生成 PEM 文件
# 可选地将密码从 'comet' 更改为您想要的任何内容,或将其留空以不使用密码
$pem_passphrase = 'comet';
$pem = array();
openssl_x509_export($cert, $pem[0]);
openssl_pkey_export($privkey, $pem[1], $pem_passphrase);
$pem = implode($pem);

// 保存 PEM 文件
$pemfile = './server.pem';
file_put_contents($pemfile, $pem);

$context = stream_context_create();

// local_cert 必须是 PEM 格式
stream_context_set_option($context, 'ssl', 'local_cert', $pemfile);
// 私钥的密码(密码)
stream_context_set_option($context, 'ssl', 'passphrase', $pem_passphrase);

stream_context_set_option($context, 'ssl', 'allow_self_signed', true);
stream_context_set_option($context, 'ssl', 'verify_peer', false);

// 创建服务器套接字
$server = stream_socket_server('ssl://0.0.0.0:9001', $errno, $errstr, STREAM_SERVER_BIND|STREAM_SERVER_LISTEN, $context);

while(
true)
{
$buffer = '';
print
"等待...";
$client = stream_socket_accept($server);
print
"已接受 " . stream_socket_get_name( $client, true) . "\n";
if(
$client )
{
// 读取直到双 CRLF
while( !preg_match('/\r?\n\r?\n/', $buffer) )
$buffer .= fread($client, 2046);
// 响应客户端
fwrite($client, "200 OK HTTP/1.1\r\n"
. "Connection: close\r\n"
. "Content-Type: text/html\r\n"
. "\r\n"
. "Hello World! " . microtime(true)
.
"<pre>{$buffer}</pre>");
fclose($client);
} else {
print
"错误。\n";
}
}

?>
4
peterjb at me dot com
14 年前
我费了很大的劲才把 TLS 套接字塞进现有的 TCP 程序中。在我看来,像 stream_socket_recvfrom 和 stream_socket_sendto 这样的函数不适用于 TLS/SSL(这对于 PHP 大师来说可能很明显...抱歉,如果它很明显,我在这里有点不知所措)。

最后,我最终用 fread() 和 fwrite() 进行了所有 IO 操作,这解决了我的所有问题。
9
andrey at php dot net
20 年前
只是一个关于如何使用此函数以及 stream_select() 创建一个服务器的小例子,该服务器可以接受多个连接(可以有多个客户端连接)
在主数组中,我们保存所有已打开的连接。 在调用 `stream_select` 之前,我们将数组复制到 `$read` 中,然后将其传递给 `stream_select()` 函数。 如果至少有一个套接字可以读取,则 `$read` 将包含套接字描述符。 我们需要 `$master` 来防止丢失对已打开连接的引用。
stream_server.php
<?php

$master
= array();
$socket = stream_socket_server("tcp://0.0.0.0:8000", $errno, $errstr);
if (!
$socket) {
echo
"$errstr ($errno)<br />\n";
} else {
$master[] = $socket;
$read = $master;
while (
1) {
$read = $master;
$mod_fd = stream_select($read, $_w = NULL, $_e = NULL, 5);
if (
$mod_fd === FALSE) {
break;
}
for (
$i = 0; $i < $mod_fd; ++$i) {
if (
$read[$i] === $socket) {
$conn = stream_socket_accept($socket);
fwrite($conn, "Hello! The time is ".date("n/j/Y g:i a")."\n");
$master[] = $conn;
} else {
$sock_data = fread($read[$i], 1024);
var_dump($sock_data);
if (
strlen($sock_data) === 0) { // connection closed
$key_to_del = array_search($read[$i], $master, TRUE);
fclose($read[$i]);
unset(
$master[$key_to_del]);
} else if (
$sock_data === FALSE) {
echo
"Something bad happened";
$key_to_del = array_search($read[$i], $master, TRUE);
unset(
$master[$key_to_del]);
} else {
echo
"The client has sent :"; var_dump($sock_data);
fwrite($read[$i], "You have sent :[".$sock_data."]\n");
fclose($read[$i]);
unset(
$master[array_search($read[$i], $master)]);
}
}
}
}
}
?>
stream_client.php
<?php
$fp
= stream_socket_client("tcp://127.0.0.1:8000", $errno, $errstr, 30);
if (!
$fp) {
echo
"$errstr ($errno)<br />\n";
} else {
fwrite($fp, "Aloha");
while (!
feof($fp)) {
var_dump(fgets($fp, 1024));
}
fclose($fp);
}
?>

谢谢
0
匿名
5 年前
以下是一些套接字服务器使用地址 `127.0.0.1` 的示例

stream_socket_server("tcp://127.0.0.1:5353", $errno, $errorMessage);

虽然这适用于本地主机测试,但当通过外部元素连接到套接字服务器时,IP 地址应为 `0.0.0.0`,如下所示

stream_socket_server("tcp://0.0.0.0:5353", $errno, $errorMessage);

root@server:~# netstat -anp | grep 5353
tcp 0 0 0.0.0.0:5353 0.0.0.0:* LISTEN 26353/php
0
Aurelien Marchand
13 年前
对于 AF_UNIX 套接字,请注意,创建的命名套接字将遵循您的 umask()。 因此,如果您希望您的命名套接字对所有人可写,请在调用 `stream_socket_server()` 之前执行 `umask(0)`。

AM
-6
Neil Munro
18 年前
如果您需要一个高速套接字服务器,请使用底层套接字(`socket_create` / `bind` / `listen`)。 `stream_socket_server` 版本似乎具有内部固定大小为 8k 的缓冲区,如果数据读取不及时,就会发生溢出。

如果您的应用程序读取套接字上的消息,然后将结果保存到数据库中,这是一个严重的问题。 处理过程中的延迟意味着您无法及时读取数据,除非您使用多线程。

使用底层函数,操作系统会静默地缓冲 TCP/IP 数据包,因此不会出现问题(在 Windows XP Professional 上测试过)。
-5
Julien Picalausa
15 年前
您可能已经注意到,与 `socket_listen` 不同,`stream_socket_server` 没有 backlog 参数。 从 PHP 5.2.9 的源代码来看,实际 listen 调用中的 backlog 参数被硬编码为 5。 如果此值不符合您的需求,则必须使用底层套接字函数。
To Top