Skip to content

Instantly share code, notes, and snippets.

@RomanStone
Last active April 25, 2019 01:48
Show Gist options
  • Save RomanStone/8ec2472d2c90440ea1c11cda2581d058 to your computer and use it in GitHub Desktop.
Save RomanStone/8ec2472d2c90440ea1c11cda2581d058 to your computer and use it in GitHub Desktop.
An example of pure PHP Server (non-block, socket_select)
<?php
$addr = '127.0.0.1';
$port = 10080;
// Use: curl -vv "http://127.0.0.1:10080/"
// 0 prints dots in infinite response (on client's side), for concurrent test
// 1 close connection after server/client negotiation
$close_on_sucess = 0;
$sockopts = array(
// level, optname, optval
array('SOL_SOCKET', 'SO_REUSEADDR', 1),
array('SOL_SOCKET', 'SO_REUSEPORT', 1),
array('SOL_SOCKET', 'SO_RCVBUF', 8192),
array('SOL_SOCKET', 'SO_SNDBUF', 8192),
array('SOL_SOCKET', 'SO_SNDTIMEO', array('sec' => 0,'usec' => 500000)),
array('SOL_SOCKET', 'SO_RCVTIMEO', array('sec' => 0,'usec' => 500000)),
array('SOL_SOCKET', 'SO_LINGER', array('l_onoff' => 1, 'l_linger' => 1)),
//array('SOL_SOCKET', 'SO_KEEPALIVE', 1),
//array('SOL_SOCKET', 'TCP_NODELAY', 1),
//array('SOL_SOCKET', 'SOMAXCONN', 1024),
);
// info: https://www.php.net/manual/function.socket-create.php
$server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
foreach ($sockopts as $opt) {
if (defined($opt[1])) {
// info: https://www.php.net/manual/function.socket-set-option.php
socket_set_option($server, constant($opt[0]), constant($opt[1]), $opt[2]);
}
}
// comment next lines and test
// info: https://www.php.net/manual/function.socket-set-nonblock.php
socket_set_nonblock($server);
// info: https://php.net/manual/function.socket-bind.php
if (socket_bind($server, $addr, $port)) {
// info: https://www.php.net/manual/function.socket-listen.php
if (socket_listen($server)) {
//$read = NULL;
//$write = NULL;
$except = NULL;
$tv_sec = NULL;
$tv_usec = NULL;
$data = array();
$pool = array();
$pool[] = $server;
$end_server = 0;
fwrite(STDOUT, '[' . date('d-M-Y H:i:s T') . '] Server Startted at http://' . $addr . ':' . $port . '/' . PHP_EOL);
fwrite(STDOUT, 'curl -vv "http://' . $addr . ':' . $port . '/"' . PHP_EOL);
fflush(STDOUT);
// SERVER LOOP START
do {
$read = $pool;
$write = array_diff($pool, array($server));
// info: https://www.php.net/manual/function.socket-select.php
if (($num = socket_select($read, $write, $except, $tv_sec, $tv_usec)) > 0) {
// for socket_read() and socket_accept()
if ($read) {
foreach($read as $rsock) {
if ($rsock !== $server) {
if (($key = array_search($rsock, $pool)) !== FALSE) {
if ($rsock === $pool[$key]) {
$id = intval($rsock);
if (!$data[$id]->req->head['eof']) {
// info: https://www.php.net/manual/function.socket-read.php
if (($chr = @socket_read($rsock, 1)) !== FALSE) {
$data[$id]->req->head['plain'] .= $chr;
// If http header EOF
if (substr($data[$id]->req->head['plain'], -4, 4) == "\r\n\r\n") {
$data[$id]->req->head['eof'] = 1;
fwrite(STDOUT, '[' . date('d-M-Y H:i:s T') . '] Request Header' . PHP_EOL);
fwrite(STDOUT, $data[$id]->req->head['plain']);
fflush(STDOUT);
// Create response
if ($close_on_sucess) {
// HTML Response
$data[$id]->res->body['eof'] = 0;
$data[$id]->res->body['plain'] = implode(PHP_EOL, array(
'<!DOCTYPE HTML>',
'<html>',
'<head><title>Pure PHP Server</title></head>',
'<body><h1>PHPServer/' . PHP_VERSION .' Sockets/' . PHP_OS . '</h1></body>',
'</html>'
)
);
$data[$id]->res->head['plain'] = implode("\r\n", array(
'HTTP/1.1 200 OK',
'Connection: close',
'Content-Type: text/html; charset=utf-8',
'Content-Length: ' . strlen($data[$id]->res->body['plain']),
)
);
$data[$id]->res->head['plain'] .= "\r\n\r\n";
}
else {
// plaintext response (dots)
$data[$id]->res->head['plain'] = implode("\r\n",
array('HTTP/1.1 200 OK','Connection: close','Content-Type: text/plain; charset=utf-8')). "\r\n\r\n";
}
}
}
else {
unset($data[$id]); socket_close($rsock); unset($pool[$key]);
}
}
else {
// CHECK CONN. STATUS
if ($data[$id]->res->head['eof'] && $data[$id]->res->body['eof']) {
if (($chr = @socket_read($rsock, 1)) === FALSE) {
fwrite(STDOUT, '[' . date('d-M-Y H:i:s T') . '] Read Error. Abort socket[' . $id . ']' . PHP_EOL); fflush(STDOUT);
unset($data[$id]); socket_close($rsock); unset($pool[$key]);
}
}
}
}
}
}
else {
if ($client = socket_accept($server)) {
$pool[] = $client;
$id = intval($client);
fwrite(STDOUT, '[' . date('d-M-Y H:i:s T') . '] New socket[' . $id . '] connected' . PHP_EOL); fflush(STDOUT);
$data[$id] = (object)array(
'req' => (object)array(
'head' => array('eof' => 0, 'plain' => '', 'assoc' => array()),
'body' => array('eof' => 1, 'plain' => '', 'assoc' => array())
),
'res' => (object)array(
'head' => array('eof' => 0, 'plain' => '', 'assoc' => array()),
'body' => array('eof' => 1, 'plain' => '', 'assoc' => array())
)
);
}
}
}
}
// for socket_write()
if ($write) {
foreach($write as $wsock) {
if ($wsock !== $server) {
if (($key = array_search($wsock, $pool)) !== FALSE) {
if ($wsock === $pool[$key]) {
$id = intval($wsock);
if ($data[$id]->req->head['eof']) {
if (!$data[$id]->res->head['eof']) {
// info: https://www.php.net/manual/function.socket-write.php
if (($bytes = @socket_write($wsock, substr($data[$id]->res->head['plain'], 0, 1), 1)) !== FALSE) {
$data[$id]->res->head['plain'] = substr($data[$id]->res->head['plain'], 1);
$data[$id]->res->head['eof'] = strlen($data[$id]->res->head['plain']) == 0;
}
else {
fwrite(STDOUT, '[' . date('d-M-Y H:i:s T') . '] Closing socket[' . $id . ']' . PHP_EOL); fflush(STDOUT);
unset($data[$id]); socket_close($wsock); unset($pool[$key]);
}
}
elseif (!$data[$id]->res->body['eof']) {
if (($bytes = @socket_write($wsock, substr($data[$id]->res->body['plain'], 0, 1), 1)) !== FALSE) {
$data[$id]->res->body['plain'] = substr($data[$id]->res->body['plain'], 1);
$data[$id]->res->body['eof'] = strlen($data[$id]->res->body['plain']) == 0;
}
else {
fwrite(STDOUT, '[' . date('d-M-Y H:i:s T') . '] Closing socket[' . $id . ']' . PHP_EOL); fflush(STDOUT);
unset($data[$id]); socket_close($wsock); unset($pool[$key]);
}
}
else {
if ($close_on_sucess) {
fwrite(STDOUT, '[' . date('d-M-Y H:i:s T') . '] Closing socket[' . $id . ']' . PHP_EOL); fflush(STDOUT);
unset($data[$id]); socket_close($wsock); unset($pool[$key]);
}
else {
if (($bytes = @socket_write($wsock, '.', 1)) === FALSE) {
fwrite(STDOUT, '[' . date('d-M-Y H:i:s T') . '] Write Error. Abort socket[' . $id . ']' . PHP_EOL); fflush(STDOUT);
unset($data[$id]); socket_close($wsock); unset($pool[$key]);
}
}
}
}
}
}
}
}
}
}
elseif ($num === 0) {
fwrite(STDOUT, '[' . date('d-M-Y H:i:s T') . '] select() Server is cactus. Abort' . PHP_EOL); fflush(STDOUT);
$end_server = 1;
}
elseif ($num === FALSE) {
fwrite(STDOUT, '[' . date('d-M-Y H:i:s T') . '] select() Server is very cactus. Abort' . PHP_EOL); fflush(STDOUT);
$end_server = 1;
}
else {
continue;
}
// SERVER LOOP END
} while(!$end_server);
socket_close($server);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment