Выполняет роль прокладки для обмена данными, между мордой в браузере и приложением требующим непрерывное соединение. Я сделал его что бы прикрутить морду к phpdbg. Можно так же использовать для создания вэб-морды например к xdebug или другой хрени. Плюс он может принимать сигналы и работать с консолью. После запуска php example.php по умолчанию будет слушать 127.0.0.1:8080. Параметры класса TCPServer TCPServer->wrapper = null - сюда создаётся класс для с соединением соеденения. TCPServer->timeout = 3600 - максимальное время ожидания в секундах входящего соединения TCPServer->interrupt = true - обрабатывать или нет прерывание с клавиатуры Ctr-C TCPServer->start(string $url = "tcp://127.0.0.1:8080") - что слушать Параметры класса HTTPServer HTTPServer->connection = null - сюда TCPServer передаст соединение после accept HTTPServer->timeout = 60 - максимальное время простоя соединения в секундах HTTPServer->max_length['header'] = 0x100000 - максимальный размер http header HTTPServer->max_length['body'] = 0x1000000 - максимальный размер http body HTTPServer->__construct(string $callable) - фукнция которую вызовет класс после обработки http request Параметры функции HTTPRouter string $method - Метод из request line string $url - URL из request line array $headers - Массив строк параметров из http header string $content - Cодержимое http body array $response - Параметры по умолчанию для формирования ответа Содержимое array $response $response["code"] = 0 - Код http ответа Значения параметров заголовка по умолчанию $response["headers"]["Content-Type"] = "Content-Type: text/html; charset=UTF-8" - содержимое текст в кодировке утф $response["headers"]["Cache-Control"] = "Cache-Control: no-cache,no-store" - не сохранять не кэшировать $response["headers"]["Content-Encoding"] = "Content-Encoding: identity" - использовать содержимое http response body таким какое оно есть $response["content" = "" - содержимое http response body TCPServer.php Code: <?php class TCPServer { var $socket = null; var $connection = null; var $wrapper = null; var $timeout = 3600; var $interrupt = true; var $childs = []; function start(string $url = "tcp://127.0.0.1:8080") { if ($this->interrupt) { declare(ticks = 1); pcntl_signal(SIGINT, function($signal) { exit(0); } ); } $controller = posix_getpid(); $pid = pcntl_fork(); if ($pid == -1) { trigger_error("can't fork", E_USER_ERROR); exit(255); } if ($pid != 0) { while(true) { $siginfo = []; pcntl_sigwaitinfo([SIGUSR1, SIGUSR2], $siginfo); switch ($siginfo["signo"]) { case SIGUSR1: array_push($this->childs, $siginfo["pid"]); break; case SIGUSR2: $key = array_search($siginfo["pid"], $this->childs); unset($this->childs[$key]); break; } } } $this->socket = stream_socket_server($url); if (!is_resource($this->socket)) { throw new Exception("can't create stream socket server"); } while (true) { $this->connection = stream_socket_accept($this->socket, $this->timeout); $pid = pcntl_fork(); if ($pid == -1) { trigger_error("can't fork", E_USER_ERROR); exit(255); } if ($pid != 0) { fclose($this->connection); continue; } if (!is_resource($this->connection)) { trigger_error("can't accept stream socket", E_USER_WARNING); return false; } stream_set_blocking($this->connection, false); $wrapper = clone $this->wrapper; $wrapper->connection = &$this->connection; posix_kill($controller, SIGUSR1); if (!$wrapper->start()) { trigger_error("session protocol wrapper error", E_USER_WARNING); } unset($wrapper); fclose($this->connection); fclose($this->socket); posix_kill($controller, SIGUSR2); exit(0); } } function __destruct() { if (is_resource($this->connection)) { fclose($this->connection); trigger_error("TCP connection closed", E_USER_NOTICE); } if (is_resource($this->socket)) { fclose($this->socket); trigger_error("TCP socket closed", E_USER_NOTICE); } foreach($this->childs as $pid) { posix_kill($pid, SIGTERM); trigger_error("PID {$pid} terminated", E_USER_NOTICE); } } } HTPServer.php Code: <?php class HTTPServer { var $connection = null; var $callable = ""; var $timeout = 60; var $max_length = [ 'header' => 0x100000, 'body' => 0x1000000 ]; var $request = [ "method" => "", "url" => "", "headers" => [], "content" => "" ]; var $response = [ "code" => 0, "headers" => [ "Content-Type" => "Content-Type: text/html; charset=UTF-8", "Cache-Control" => "Cache-Control: no-cache,no-store", "Content-Encoding" => "Content-Encoding: identity" ], "content" => "" ]; function __construct(string $callable) { $this->callable = $callable; } function start() { if (!$this->receive_header()) { trigger_error("can't receive header", E_USER_WARNING); return false; } if ($this->request['method'] == 'POST') { if (!$this->receive_body()) { trigger_error("can't receive body", E_USER_WARNING); return false; } } $response = call_user_func( $this->callable, $this->request['method'], $this->request['url'], $this->request['headers'], $this->request['content'], $this->response ); if (!is_array($response)) { $this->response['code'] = 500; } else { $this->response = $response; } if (!$this->send_response()) { trigger_error("can't send response", E_USER_WARNING); return false; } return true; } function receive_header() { $header = ""; stream_set_timeout($this->connection, $this->timeout); while (true) { $string = stream_get_contents($this->connection); if (!is_string($string)) { trigger_error("can't get contents for header from stream", E_USER_WARNING); return false; } $header .= $string; if (strlen($header) > $this->max_length['header']) { trigger_error("header length is greater than maximum", E_USER_WARNING); return false; } $position = strpos($header, "\r\n\r\n"); if (is_int($position)) { break; } } $this->request['content'] = substr($header, ($position+4)); $header = substr($header, 0, $position); $headers = explode("\r\n", $header); if (preg_match("/^([A-Z]+)\s+(.+)\s+HTTP\/1\.1$/", $headers[0], $matches) != 1) { trigger_error("can't parse request line", E_USER_WARNING); return false; } $this->request['method'] = $matches[1]; $this->request['url'] = $matches[2]; unset($headers[0]); $this->request['headers'] = array_values($headers); return true; } function receive_body() { $content_length = null; foreach ($this->request['headers'] as $string) { if (preg_match("/^Content\-Length\:\s+([0-9]+)$/", $string, $matches) == 1) { $content_length = intval($matches[1]); break; } } if (!is_int($content_length)) { trigger_error("can't find Content-Length from headers", E_USER_WARNING); return false; } stream_set_timeout($this->connection, $this->timeout); $body = &$this->request['content']; while (strlen($body) < $content_length) { $string = stream_get_contents($this->connection); if (!is_string($string)) { trigger_error("can't get body contents from stream", E_USER_WARNING); return false; } $body .= $string; if (strlen($body) > $this->max_length['body']) { trigger_error("body length is greater than maximum", E_USER_WARNING); return false; } } return true; } function send_response() { switch ($this->response['code']) { case 200: $response = "HTTP/1.1 200 OK\r\n"; $response .= implode("\r\n", $this->response['headers'])."\r\n"; break; case 404: $response = "HTTP/1.1 404 Not Found\r\n"; break; case 401: $response = "HTTP/1.1 401 Unauthorized\r\n"; $response .= implode("\r\n", $this->response['headers'])."\r\n"; break; case 403: $response = "HTTP/1.1 403 Forbidden\r\n"; break; case 500: $response = "HTTP/1.1 500 Internal Server Error\r\n"; break; default: trigger_error("unsupported response code", E_USER_WARNING); return false; } $content = &$this->response['content']; if (!empty($content)) { $content_length = strlen($content); $response .= "Content-Length: ".strval($content_length)."\r\n"; } $response .= "\r\n"; $response .= $content; stream_set_timeout($this->connection, $this->timeout); if (!is_int(fwrite($this->connection, $response))) { trigger_error("can't write response", E_USER_WARNING); return false; } return true; } } example1.php - Демонстрирует приём GET и POST запросов Code: <?php require __DIR__."/TCPServer.php"; require __DIR__."/HTTPServer.php"; function HTTPRouter( string $method, string $url, array $headers, string $content, array $response ) { if ($url == '/' && $method == 'GET') { $response['code'] = 200; array_push( $response['headers'], "Set-Cookie: CookieName=CookieValue" ); $response['content'] = <<<HEREDOC <html> <body> <form action="/" target="transceiver" method="post"> <input type="text" name="name1" value="value1" /> <input type="submit" value="submit" /> </form> <iframe name="transceiver" src=""></iframe> </body> HEREDOC; return $response; } if ($url == '/' && $method == 'POST') { $response['code'] = 200; $response['content'] = <<<HEREDOC <html> <body> {$content} </body> HEREDOC; return $response; } $response['code'] = 404; return $response; } $TCPServer = new TCPServer; $TCPServer->wrapper = new HTTPServer('HTTPRouter'); $TCPServer->start(); example2.php - Демонстрирует как прикрутить HTTP авторизацию Code: <?php require __DIR__."/TCPServer.php"; require __DIR__."/HTTPServer.php"; function HTTPRouter( string $method, string $url, array $headers, string $content, array $response ) { $is_authorized = is_authorized($headers); if ($is_authorized === null) { $response ['code'] = 401; $response ['headers'] = ['WWW-Authenticate: Basic']; return $response; } if ($is_authorized === false) { $response ['code'] = 403; return $response; } if ($url == '/' && $is_authorized === true) { $response['code'] = 200; $response['content'] = <<<HEREDOC <html> <body> Authorization was successful </body> HEREDOC; return $response; } $response['code'] = 404; return $response; } function is_authorized($headers) { $base64_string = null; foreach ($headers as $string) { if (preg_match("/^Authorization\:\sBasic\s(.+)$/", $string, $matches) == 1) { $base64_string = $matches[1]; break; } } if (!is_string($base64_string)) { return null; } $key = md5($base64_string); if ($key != "970cf1450cab27c98bade1876e6ed21a") { return false; } return true; } $TCPServer = new TCPServer; $TCPServer->wrapper = new HTTPServer('HTTPRouter'); $TCPServer->start(); Не жую. Читайте исходники и примеры.
php не язык для демонов. очень сильные утечки памяти. гиг утекает за пол часа и приходится перезапускать процесс. такие вещи лучше на питоне писать. ну или на чем угодно но не на php ps. попробуйте запустить процесс и посмотреть через сколько он умрет с сообщением out of memory
Мозг череп не жмёт? Хотел бы демона писать, использовал бы си. Ведь написал что это прокладка которую удобно сращивать с другими php скриптами. Будет сильно утекать вставлю авторестарт.
зачем хамить сразу ? авторестарт конечно хорошо. но зачем если можно просто подходящий инструмент взять. а утекать оно обязательно будет ))). это же пэхэпэ
Цель проги удерживать соедининие между прогой и например отладчиком. При этом к проге обращается браузер, для отправки команд в соеденение. Прога висит в памяти, держит соединение и слушает порт, добавляем fork после accept и все данные которые не очистились будут удаляться вместе с завершением дочернего процесса. не аргумент.
Нельзя создать и удерживать соединение с каким либо сервисом. Нельзя обмениваться сигналами, и работать с консолью. Читайте внимательней для чего был создан. Данный сервер, в первую очередь, предназначен для сохрания дескрипторов в открытом состоянии.
Добавил в TCPServer многопоток на fork. Обработку соединения процессами потомками. Контроллер завершения потомков. После этого перестало глючить с хромом который открывал соединение, но не передавал данные.