CakeFest 2024: The Official CakePHP Conference

stream_socket_server

(PHP 5, PHP 7, PHP 8)

stream_socket_serverインターネットドメインまたは 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 フォーマットによって指定された トランスポートによって決定されます:

TCP や UDP といったインターネットドメインのソケット (AF_INET) には、remote_socket パラメータの ターゲット の部分は、ホスト名または IP アドレスと、 それに続くコロンで区切られたポート番号から構成されていなければなりません。 Unix ドメインのソケットの場合は、ターゲット の部分は、ファイルシステムにおけるソケットのファイルを指定しなくては いけません。

システムの種類によって、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_BINDflags パラメータとして使用する必要があります。

context

戻り値

作成したストリーム、あるいはエラー時に false を返します。

変更履歴

バージョン 説明
8.0.0 context は、nullable になりました。

例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);
}
?>

下記の例は、PHP のスクリプトが、どうやって stream_socket_client() で示したような、 問い合わせに応答するタイムサーバーとして機能するかを示したものです。

注意: 1024 番よりも小さいポート番号のサーバーソケットを作成する場合、 多くのシステムでは root 権限が必要となります。

例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) アドレスを角カッコでくくらなくてはなりません。たとえば、 tcp://[fe80::1]:80.

参考

  • stream_socket_client() - インターネットドメインまたは Unix ドメインのソケット接続を開く
  • stream_set_blocking() - ストリームのブロックモードを有効にする / 解除する
  • stream_set_timeout() - ストリームにタイムアウトを設定する
  • fgets() - ファイルポインタから 1 行取得する
  • fgetss() - ファイルポインタから 1 行取り出し、HTML タグを取り除く
  • fwrite() - バイナリセーフなファイル書き込み処理
  • fclose() - オープンされたファイルポインタをクローズする
  • feof() - ファイルポインタがファイル終端に達しているかどうか調べる
  • Curl 拡張モジュール
add a note

User Contributed Notes 10 notes

up
37
Heretic86 at roadrunner dot com
3 years ago
This is an example of how to set up stream_socket_server to connect with multiple Secure Websockets on WSS (wss://) that uses SSL / TLS as a Transport.

This runs on a Windows Apache Server with a registered domain and SSL Cert from LetsEncrypt via Certbot.

Run this script from a console "php server.php", and javascript html as a client with:
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);
}
}
?>
up
30
blackmac01 at gmail dot com
8 years ago
I'm writing an HTTP server and I need SSL support, but getting this to work correctly with PHP streams took a bit of trial and error. For anyone who is trying to get an HTTP SSL server working with stream_socket_server:

1) Your SSL context will need to contain 'local_cert'. If you did not include your private key with your local_cert, you'll also need to specify 'local_pk' which is your RSA key. Your keys and certs should be PEM encoded, which means base-64. If your certificate has intermediary certs, you will need to specify those in the correct order: Your signed cert, intermediary cert 1, intermediary cert 2, etc. Each cert in the list needs to validate the one above it, but you do not need to include the CA Root that your SSL signer provided; that should already be included with the client's software (i.e. trust root certs).

You can append your private key in the file with your certs, however I keep mine in its own file. If you see the word "encrypted" when you view your key with a text viewer, you need to enter the correct passphrase and specify the context "passphrase", otherwise you can leave that one out.

As a server, verify_peer is irrelevant and should be set to false (should always be true if you are acting as an SSL client). Both cafile and capath contexts are not needed for functioning as a SSL/TLS server, but they are needed if you are making SSL connections with PHP as the client.

Lastly, the 'ciphers' context should be set to a list of secure ciphers. Search for "mozilla recommended ciphers" and choose the string of ciphers that works for you, because not all openssl supported ciphers are secure. I went with the "intermediate" list, which provides high security and compatibility.

2) When you create the binding for stream_socket_server(), make sure that you choose the tcp:// wrapper. DO NOT USE ssl:// or tls://. Anything other than tcp:// will not work correctly AS A SERVER, those transports are what you use when making connections with PHP as a client.

Remember that the encryption does not start until after an SSL handshake completes, so the server has to listen in non-encrypted mode for new connections, and encryption doesn't start until certs are exchanged and a cipher is selected. When a new connection arrives you accept it with stream_socket_accept() and then use stream_socket_enable_crypto() to start the SSL session.

3) Keep in mind that the SSL handshake takes time, and that the stream_socket wrappers are high level and not as responsive as the socket extension due to the additional overhead they incur. For this reason you will need to enable blocking for accepting new connections.

<enable blocking on ServerListenStream>
newConnStream = stream_socket_accept(ServerListenStream);
<disable blocking on ServerListenStream>

<enable blocking on newConnStream >
stream_socket_enable_crypto(newConnStream, true, STREAM_CRYPTO_METHOD_SSLv23_SERVER);
<disable blocking on newConnStream >

Note that this is mainly for HTTP. If you are trying to do something like SMTP then your script will have to react to the "starttls" command, but it would be similar to the above except that you would wait for the "starttls" command before invoking the stream_socket_enable_crypto() function on the client's stream.

TLS 1.0 is generally the way to go, SSLv3 is insecure and SSLv2 is buggy. If you use the mozilla recommend cipher list in your context, you'll be fine. Hope this helps someone out!
up
21
davidm at marketo dot com
14 years ago
In some specialized scenarios, you may want to create an AF_INET socket (UDP or TCP) but let the system select an unused port for you. This is a standard feature of internet sockets but it doesn't seem to be documented how to do this for the stream_socket_server function. It appears you can get this behavior by selecting zero for the port number, for example, my test below printed "127.0.0.1:4960".

<?php
$sock
= stream_socket_server("udp://127.0.0.1:0");
$name = stream_socket_get_name($sock);
echo
$name;
?>
up
13
frxstrem
13 years ago
Using the OpenSSL extension, PHP can automatically generate self-signed SSL certificates, which can be used for basic authentication and encryption (although I would recommend to use a signed certificate instead) for SSL servers.

I have extended the script by 'e at osterman dot com' to automatically create self-signed certificates:

<?php
// Hello World! SSL HTTP Server.
// Tested on PHP 5.1.2-1+b1 (cli) (built: Mar 20 2006 04:17:24)

// Certificate data:
$dn = array(
"countryName" => "UK",
"stateOrProvinceName" => "Somerset",
"localityName" => "Glastonbury",
"organizationName" => "The Brain Room Limited",
"organizationalUnitName" => "PHP Documentation Team",
"commonName" => "Wez Furlong",
"emailAddress" => "wez@example.com"
);

// Generate certificate
$privkey = openssl_pkey_new();
$cert = openssl_csr_new($dn, $privkey);
$cert = openssl_csr_sign($cert, null, $privkey, 365);

// Generate PEM file
# Optionally change the passphrase from 'comet' to whatever you want, or leave it empty for no passphrase
$pem_passphrase = 'comet';
$pem = array();
openssl_x509_export($cert, $pem[0]);
openssl_pkey_export($privkey, $pem[1], $pem_passphrase);
$pem = implode($pem);

// Save PEM file
$pemfile = './server.pem';
file_put_contents($pemfile, $pem);

$context = stream_context_create();

// local_cert must be in PEM format
stream_context_set_option($context, 'ssl', 'local_cert', $pemfile);
// Pass Phrase (password) of private key
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);

// Create the server socket
$server = stream_socket_server('ssl://0.0.0.0:9001', $errno, $errstr, STREAM_SERVER_BIND|STREAM_SERVER_LISTEN, $context);

while(
true)
{
$buffer = '';
print
"waiting...";
$client = stream_socket_accept($server);
print
"accepted " . stream_socket_get_name( $client, true) . "\n";
if(
$client )
{
// Read until double CRLF
while( !preg_match('/\r?\n\r?\n/', $buffer) )
$buffer .= fread($client, 2046);
// Respond to client
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
"error.\n";
}
}

?>
up
4
peterjb at me dot com
14 years ago
I had a horrible time trying to shove a TLS socket into an existing TCP program. It appears to me that functions like stream_socket_recvfrom and stream_socket_sendto don't work with TLS/SSL (which may be obvious to PHP gurus...sorry if it is, I'm in a bit over my head here).

In the end I ended up doing all my IO with fread() and fwrite(), which solved all my problems.
up
9
andrey at php dot net
19 years ago
Just a small example how to use this function and also stream_select() to make a server that accepts more than one connections (can have many clients connected):
In master we hold all opened connections. Just before calling stream select we copy the array to $read and then pass it ot stream_select(). In case that we may read from at least one socket, $read will contain socket descriptors. $master is needed not to lose references to the opened connections we have.
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);
}
?>

Thanks
up
0
Anonymous
4 years ago
Some examples of socket server uses address 127.0.0.1 as below:

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

While it works for localhost testing, when connecting to the socket server via external element, the IP should be "0.0.0.0" as below:

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
up
0
Aurelien Marchand
13 years ago
In the case of AF_UNIX sockets, note the named sockets that will be created respects your umask(). So if you wanted your named socket to be writeable to all, do umask(0) prior to calling stream_socket_server().

AM
up
-6
Neil Munro
17 years ago
If you want a high speed socket server, use the low-level sockets instead (socket_create/bind/listen). The stream_socket_server version appears to have internal fixed 8k buffers that will overflow if you don't keep up by reading.

This is a serious problem if you an application that reads the socket for messages and then, say, saves the result in a database. The delay while it is busy processing means you can't read the data in time unless you get involved in muti-threading.

With the the low-level functions, the OS quietly buffers TCP/IP packets so there is no problem (tested on Windows XP Professional).
up
-5
Julien Picalausa
14 years ago
You may have noticed that, unlike socket_listen, stream_socket_server doesn't have a backlog parameter. From the source code of php 5.2.9, it looks like the backlog parameter to the actual listen call is hardcoded to be 5. If this value doesn't suit your needs, you'll have to use the lower-level socket functions.
To Top