Давненько ничего полезного не писал, посему спешу исправиться.
Собственно, эта тема обсасывалась уже уже не раз и не два (призываю Google в свидетели), поэтому не буду разводить тряхомудиюдемагогию, а сразу приведу исходник класса, который позволяет довольно просто выполнять несколько HTTP-запросов параллельно:
class HttpQueue
{
/**
* An array of URLs
*
* @access private
* @var array
*/
private $_urls = array();
/**
* An array of server sockets
*
* @access private
* @var array
*/
private $_sockets = array();
/**
* An array of server responses
*
* @access private
* @var array
*/
private $_response = array();
/**
* Socket timeout
*
* @access private
* @var integer
*/
private $_timeout = 30;
/**
* An array of sockets which can be received
*
* @access private
* @var array
*/
private $_read = array();
/**
* An array of sockets which can be sended
*
* @access private
* @var array
*/
private $_write = array();
/**
* Adds an URL into a tasklist
*
* @access public
* @param string $method
* @param string $url
* @return void
*/
public function add($method, $url)
{
$this->_urls[] = array(strtoupper($method), $this->_parseUrl($url));
}
/**
* Parses requested URL and checks for all URL parts
*
* @access private
* @param string $url
* @return array
*/
private function _parseUrl($url)
{
$parts = parse_url($url);
$parts['port'] = array_key_exists('port', $parts) ? $parts['port'] : 80;
$parts['sock'] = sprintf('%s:%s', $parts['host'], $parts['port']);
$parts['request'] = sprintf('%s?%s', $parts['path'], $parts['query']);
return $parts;
}
/**
* Starts fetch process
*
* @access public
* @param void
* @return array
*/
public function fetch()
{
$this->_create();
$this->_process();
return $this->toArray();
}
/**
* Sets socket timeout (in seconds)
*
* @access public
* @param integer $timeout
* @return void
*/
public function setTimeout($timeout)
{
$this->_timeout = $timeout;
}
/**
* Returns array of server responses
*
* @access public
* @param void
* @return array
*/
public function toArray()
{
return $this->_response;
}
/**
* Creates socket conenctions with hosts given in URL
*
* @access private
* @param void
* @return void
*/
private function _create()
{
foreach ($this->_urls as $id => $connect) {
if ($socket = @stream_socket_client($connect[1]['sock'], $errno,
$errstr, $this->_timeout,
STREAM_CLIENT_ASYNC_CONNECT|STREAM_CLIENT_CONNECT)) {
$this->_sockets[$id] = $socket;
$this->_response[$id] = 'processing';
continue;
}
$this->_response[$id] = "failed, $errno $errstr";
}
}
/**
* Process opened sockets
*
* @access private
* @param void
* @return void
*/
private function _process()
{
while (count($this->_sockets)) {
$this->_read = $this->_sockets;
$this->_write = $this->_sockets;
$select = stream_select($this->_read, $this->_write, $e = null,
$this->_timeout);
if ($select > 0) {
$this->_read();
$this->_write();
}
else {
foreach ($this->_sockets as $id => $s) {
$this->_response[$id] = 'Timed out';
}
break;
}
}
}
/**
* Receives data from readable socket
*
* @access private
* @param void
* @return void
*/
private function _read()
{
foreach ($this->_read as $fp) {
$id = array_search($fp, $this->_sockets);
$data = fread($fp, 8192);
if (strlen($data) == 0) {
if ($this->_response[$id] == 'processing') {
$this->_response[$id] = 'failed to connect';
}
fclose($fp);
unset($this->_sockets[$id]);
}
else {
$this->_response[$id].= $data;
}
}
}
/**
* Sends HTTP request into writeable socket
*
* @access private
* @param void
* @return void
*/
private function _write()
{
foreach ($this->_write as $fp) {
$id = array_search($fp, $this->_sockets);
$method = $this->_urls[$id][0];
$request = $this->_urls[$id][1]['request'];
$host = $this->_urls[$id][1]['host'];
fputs($fp, "$method $request HTTP/1.0rn");
fputs($fp, "Host: $hostrnrn");
$this->_response[$id] = '';
}
}
}
Ну и, разумеется, пример использования:
$http = new HttpQueue();
$http->setTimeout(20);
$http->add('get', 'http://ya.ru');
$http->add('get', 'http://google.com');
$http->add('get', 'http://wordpress.com');
$result = $http->fetch();
Чтобы добавить URL в очередь, используется метод HttpQueue::add($method, $url). Первый параметр указывает тип метода передачи данных (в данном классе заработают GET и HEAD; чтобы заработал POST, нужно внести несколько изменений, но это никому не интенесно
), второй — собственно URL.
В результирующем массиве $result будут храниться ответы серверов в том порядке, в котором они были заданы.
P.S. Если я буду продолжать публиковаться, то со временем перестану напоминать, что мои классы работают на PHP4.x и младше
P.P.S. Прошу сильно не пинать ногами за комментарии в DocBlock: по-английски пишу как умею.
Комментарии (10)
19 Сентябрь 2007 в 22:02
1. Класс и объектно-ориентированность не одно и тоже и писать класс вряд ли надо было.
2. Пулл неблокирующих сокетов и ряд других идей действительно описаны не раз, но это не МНОГОПОТОЧНОСТЬ, увы. В Perl она есть: threads&fork – вставляй куда хочешь, за исключением работы с файлами, а это лишь сетевые запросы один за другим.
19 Сентябрь 2007 в 22:32
Спасибо за комментарий прежде всего
. А теперь по пунктам:
Насчет того, что класс и объектная ориентированность не одно и то же, согласен на сто процентов. А вот насчет “вряд ли надо было”… Разрешите узнать, а почему, собсна?..
И как бы Вы реализовали эту задачу?
Тут дело вот в чем… Думаю, что не стоит погрязать в определениях; достаточно знать, что при использовании этого класса запрос #2 начнет выполняться до того, как обработка запроса #1 полностью завершится. На мой взгляд, это многопоточность
. А вот fork, кстати, нет: это распараллеливание процессов, а не многопоточность. Не годится.
Между прочим, в теле статьи я ни разу не употребил слово «многопоточность». Там написано примерно так:
Что касается заголовка… Каюсь, слово многопоточность в заголовке поста я употребил специально, чтобы собирать трафик по этому запросу, который имеет некоторую популярность. Прошу меня простить за эту маленькую ложь
. Что ж поделать, законы рынка: не обманешь — не продашь
20 Сентябрь 2007 в 20:34
fork – не многопоточность? оригинально. Наверное, я что-то забыл о кодинге под unix. =( Если говорить о fork’е, то есть важная вещь – многопоточность непродуктивна при скажем переборе словаря, потоков эдак >50M. Треды дохнут, а если нет, то виснут при моменте соединения. Тот же fork на микрозадачах – открыть файл#1, потом файл#2.. файл#т<100. – слишком грузен и выигрыш от многопоточности стремится к нулю. Треды – это не совсем потоки смысла “потоков”. Это потоки, но сильно урезанные, они для микрозадач. А fork создаёт, конечно, полноправные процессы с дочерним PID и создан был для задач макроуровня.
Но многопоточности на php пока нет. Многие пытаются доказать и в других средствах СМИ, что вот мы можем отправить несколько запросов и получить ответы, ряд приложений построен на этом принципе, но многопоточность есть в php лишь для сети. Для локального воздействия с файлам/процессами/обработки текстовой информации – у него нет. Он может лишь послать несколько запросов и получить ответы без создания очереди.
А так многопоточности нет.
20 Сентябрь 2007 в 20:51
К вопросу о объектно-ориентированности: как бы я реализовал. Максимум – функция. Мне, честно, не совсем интересен Java или, скажем, C#, ибо – классизированны и ОО до конца. Мне мало представляет интерес код:
class Hello_world{
/**
* Text variable for print
*
* @access private
* @var string
*/
private $text = "Hello, world!";
/**
* Function for print text
*
* @access public
* @param string $var
* @return void
*/
public function print_text($var)
{
print $var;
}
}
А к классу ещё пара интерфейсов и абстрактный класс.
Для меня важна рациональность использования памяти. Класс заполняет несколько связок p-блоков в ОО движке php.
- чтобы собирать трафик по этому запросу
Чего там, не оправдывайся. Рано или поздно теги в блоге приобретут свою законченность:
php(2),
web2.0(238),
firefox(41),
opera(39),
ie7(12),
многопоточность(713),
как заработать денех в сети(1940)
21 Сентябрь 2007 в 09:31
Да, PHP не поддерживает работу в несколько потоков в прямомсмысле этого слова и вряд ли будет поддерживать. Но выполнять параллельно сетевые операции с помощью неблокируемых сокетов может свободно. Больше ничего и не надо.
А fork всё ранво не многопоточность, а распараллеливание процессов.
Про стиль программирования: разумеется, писать класс для вывода ‘Hello World’ не всегда нужно (Ваш класс, кстати, вообще непонятно что делает). Но это уже больше вопрос проектирования. Если вывод текста является бизнес-частью приложения, а не простым дизайном, то почему бы и нет…
Про теги: я буду писать про то, что мне интересно
. Про PHP интересно, про Web2.0 — не особо. Про Оперу, FF и многопоточность всё написали до меня, IE7 терпеть ненавижу, а о том, как заработать денех в сети, буду молчать как рыба об лёд: кому надо, тот в теме
6 Октябрь 2007 в 01:20
[...] About Многопоточность в PHP: HTTP-клиент [...]
12 Декабрь 2007 в 01:57
Не подскажите, куда можно дописать user-agent и header?
13 Декабрь 2007 в 06:38
User-Agent можно дописать туда же, где расположены другие заголовки (
Host:, к примеру): смотрите тело метода_write(), на строки сfputs()Кстати, Вордпресс изгадил код, бекслеши повырезал. Разумеется, там не “rn” должно быть, а “\r\n”…
13 Декабрь 2007 в 12:53
Ага, заметил
22 Сентябрь 2008 в 16:06
[...] http://php.webconsulting.by/2008/05/…funkciya-fork/ http://nopox.wordpress.com/2007/09/1…d-http-client/ http://dyuss.od.ua/2007/07/06/mnogopotochnost-v-php/ http://www.php.net/manual/en/ref.pcntl.php [...]