По работе мне потребовалось использовать удалённое API для получения информации об IP клиентов. В PHP я нашёл два варианта как можно выполнить запрос на удалённый сервер: Sockets и cURL. Так как с cURL я был уже знаком, решил узнать что такое Sockets.
Насколько я понял - Сокет это просто точка соединения. Есть разные типы Сокетов, наиболее распространённые (и реализованные в PHP) это AF_INET для интернет-соединений и AF_UNIX для коммуникаций между процессами ОС.
Это мой первый опыть работы с Sockets, поэтому наверняка есть какие-то недочёты в моём решении. Плюс к этому - моя задача, на данный момент, ограничена работой с HTTP-запросами: отправить Get или Post, и считать ответ.
Первым делом, я взял один из примеров с документации по PHP и начал работу с него. Принцип работы с этим модулем следующий:
// создание сокета $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); // подключение созданного сокета socket_connect($socket, $address, $service_port); // запись в сокет запроса socket_write($socket, $in, strlen($in)); // считывание ответа фиксированными порциями while ($out = socket_read($socket, 2048)) { } // закрытие socket_close($socket);
Основная сложность в том, что запрос нужно выполнять с помощью HTTP-заголовка и ответ также приходит с HTTP заголовком, который нужно сначала выделить а потом разобрать нужные поля.
Необходимую информацию о заголовках я нашёл тут:
Необходимую информацию о заголовках я нашёл тут:
, и официальный ресурс (которым я почти не воспользовался):
У меня получился следующий класс:
if (!defined('NL')) { define('NL', "\r\n"); define('DNL', NL . NL); } class HttpSocket { private $socket; private $host_str; private $query_str; private $ip_addr; private $resp_code; public function __construct() { $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); if ($this->socket === false) trigger_error('socket_create() failed: reason: ' . socket_strerror(socket_last_error()), E_USER_ERROR); } public function reqGet($addr) { $this->parseAddr($addr); $header = 'GET ' . $this->query_str . ' HTTP/1.1' . NL; $header .= 'Host: ' . $this->host_str . NL; $header .= 'Connection: Close' . DNL; return $this->doQuery($header); } public function reqPost($addr, $var_arr = array()) { $this->parseAddr($addr); $req_str = http_build_query($var_arr); $header = 'POST ' . $this->query_str . ' HTTP/1.1' . NL; $header .= 'Host: ' . $this->host_str . NL; $header .= 'Content-Type: application/x-www-form-urlencoded' . NL; $header .= 'Content-Length: ' . strlen($req_str) . NL; $header .= 'Connection: Close' . DNL; $header .= $req_str; return $this->doQuery($header); } public function getRespCode() { return $this->resp_code; } public function __destruct() { socket_close($this->socket); } private function parseAddr($addr) { // http://hostname.com/query/string?v=1 // http://192.168.241.125/query/string?v=1 preg_match('/^(?:https?:\/\/)?([^\/]+)(.*)/', $addr, $matches); if (filter_var($matches[1], FILTER_VALIDATE_IP)) { $this->ip_addr = $matches[1]; } else { $this->host_str = $matches[1]; $this->ip_addr = gethostbyname($this->host_str); } $this->query_str = strlen($matches[2]) ? $matches[2] : '/'; } private function doQuery($header) { $service_port = getservbyname('www', 'tcp'); $result = socket_connect($this->socket, $this->ip_addr, $service_port); if ($result === false) trigger_error('socket_connect() failed. Reason: (' . $result . ') ' . socket_strerror(socket_last_error($this->socket)), E_USER_ERROR); socket_write($this->socket, $header, strlen($header)); $resp_str = $this->doFetch(); $resp_bdy = $this->parseResponse($resp_str); return $resp_bdy; } private function doFetch() { $resp = ''; while ($out = socket_read($this->socket, 2048)) $resp .= $out; return $resp; } private function parseResponse($resp_str) { $pos_cr = strpos($resp_str, "\n\n"); $pos_crlf = strpos($resp_str, "\r\n\r\n"); if ($pos_cr !== false AND $pos_cr < $pos_crlf) { $sep_pos = $pos_cr; $sep_len = 2; } else { $sep_pos = $pos_crlf; $sep_len = 4; } $head = substr($resp_str, 0, $sep_pos); preg_match('/HTTP[^\s]+\s(\d+)/i', $head, $matches); $this->resp_code = $matches[1]; preg_match('/Content-Length:\s(\d+)/i', $head, $matches); $length = intval($matches[1]); $body = substr($resp_str, $sep_pos + $sep_len, $length); return $body; } }
Функции __construct() и __destruct() соответственно создают и закрывают сокет. Функции reqGet() и reqPost() получают URL в качестве аргумента и выполняют соответствующие запросы.
Пару слов о HTTP заголовках:
- Строка "Host: ..." сообщает на какой именно домен идёт запрос (на случай если на одном IP несколько доменов).
- Строка "Connection: Close" сообщает что соединение не должно считаться постоянным.
Ещё немного напишу о процессе разбора ответа в функции parseResponse():
- Сначала нужно отделить заголовок от основного сообщения, для этого ищу двойную новую строку. Это может быть как "\r\n\r\n", так и просто "\n\n".
- Далее сохраняю код ответа и выбираю информацию о длинне сообщения (Content-Length).
После этого уже выделяю само сообщение.
Комментариев нет:
Отправить комментарий