Last active
April 25, 2019 01:48
-
-
Save RomanStone/8ec2472d2c90440ea1c11cda2581d058 to your computer and use it in GitHub Desktop.
An example of pure PHP Server (non-block, socket_select)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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