PHP: Программирование сокетов - Одновременная работа с несколькими сокетами

PHP: Программирование сокетов - Одновременная работа с несколькими сокетами

Индекс материала
PHP: Программирование сокетов
Создание клиентских сокетов.
Создание серверных сокетов
Одновременная работа с несколькими сокетами
Все страницы

Одновременная работа с несколькими сокетами

В листинге, на предыдущей странице был представлен сервер на базе сокетов. Однако он не слишком удобен для реальных, целей, поскольку к нему может выполняться только одно подключение одновременно. Чтобы создать более удобный сервер сокетов, необходимо научиться работать одновременно с множеством сокетов. Чтобы сделать это, понадобится функция socket_select (), синтаксис которой выглядит следующим образом:

socket_select(&$read, &$write, &$error, $sec [, $usec]);

Здесь $read, $write и $error — переданные по ссылке переменные (точнее, массивы). Эти массивы должны содержать список всех сокетов, за которыми нужно наблюдать на предмет чтения, записи и перехвата ошибок соответственно. Например, помещение активного сокета в массив, передаваемый в параметре $read, заставляет РНР проверять, есть ли в этом сокете данные для чтения. Последние два параметра - $sec и необязательный $usec - это значения тайм-аута, управляющие тем, как долго будет ожидать функция socket_select(), прежде чем вернуть управление РНР.

В результате выполнения функция socket_select() возвращает целое число, указывающее общее количество измененных сокетов ( из переданного списка), и модифицирует массивы $read, $write и $error, удаляя из них те элементы, которые не были изменены. В результате каждый из этих массивов будет содержать только список сокетов, отвечающих следующим требованиям:

  • Сокеты, перечисленные в массиве $read, содержат данные, подлежащие чтению из них, либо входящие подключения к ним.

  • Сокеты, перечисленные в массиве $write, содержат данные, подлежащие записи в них.

  • Сокеты, перечисленные в массиве $error, содержат ошибки, которые нужно обработать.

В случае ошибочного завершения socket_select() возвращает булевское значение false.

Для использования этой функции в реальном приложении первым делом должен быть создан сокет, представляющий сервер в целом. Этот главный сокет будет привязан к определенному адресу и порту и начнет прослушивание подключений.

Этот сокет будет также добавлен в массив $read и будет запущен управляемый бесконечный цикл. Затем с помощью функции socket_select() будет организован мониторинг главного сокета на предмет новых подключений. Когда появляется новое подключение, автоматически вызывается функция socket_accept(), что приводит к созданию нового серверного сокета, используемого для взаимодействия с подключенным клиентом.

Этот новый подключенный сокет затем подвергается мониторингу через тот же вызов socket_select() (за счет добавления его к тому же массиву, куда уже добавлен наш главный сокет) и реализуется логика приложения, обеспечивающая функциональность нашего сервера. В листинге ниже представлен работающий пример простого сервера, принимающего настраиваемое число подключений.

Создание многосортного сервера на РНР


<?php

set_time_limit(0);

$NULL           = NULL;
$address        = "127.0.0.1";
$port           = 4545;
$max_clients    = 10;
$client_sockets = array();
$master         = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
$res            = true;

$res &= @socket_bind($master, $address, $port);
$res &= @socket_listen($master);

if(!$res)
{
	die ("Невозможно привязать и прослушивать $address: $port\n");
}

$abort = false;
$read = array($master);

while(!$abort)
{
	$num_changed = socket_select($read, $NULL, $NULL, 0, 10);
	/* Изменилось что-нибудь? */
	if ($num_changed) 
	{
		/* Изменился ли главный сокет (новое подключение) */
		if(in_array($master, $read))
		{ 
				if(count($client_sockets) < $max_clients)
				{
						$client_sockets[]= socket_accept($master);
						echo "Принято подключение (" . count($client_sockets)  . " of $max clients)\n";
				}
		}		
		/* Цикл по всем клиентам с проверкой изменений в каждом из них */
		foreach($client_sockets as $key => $client)
		{ 
			/* Новые данные в клиентском сокете? Прочитать и ответить */ 
			if(in_array($client, $read))
			{
				$input = socket_read($client, 1024);
        
				if($input === false)
				{
					socket_shutdown($client);
					unset($client_sockets[$key]); 
				}
				else
				{
					$input = trim($input);
          
					if (!@socket_write($client, "Вы сказали: $input\n") )
					{
						socket_close($client);
						unset ( $client_sockets[$key] ) ;
					}
				}
				
				if($input == 'exit')
				{ 
					socket_shutdown($master);
					$abort = true;
				}
        
			}// END IF in_array
      
		} // END FOREACH
    
	} // END IF ($num_changed)
	
	$read = $client_sockets;
	$read[] = $master;
} // END WHILE
?>

Кстати!

В листинге выше вскрыты некоторые ограничения сценарного механизма РНР, которые требуют несколько более сложного обходного пути в форме вызова socket_select():

$num_changed = socket_select($read, $NULL, $NULL, 0, 10);

Обратите внимание на применение переменной с именем $NULL. В РНР для функций, принимающих параметры по ссылке (как это и делает socket_select() в первом приближении), NULL является недопустимым значением. Однако передача NULL в качестве одного или более параметров-списков вполне корректна. Поэтому обходной маневр заключается в присвоении переменной $NULL значения NULL:

$NULL = NULL;

И последующей ее передачи функции socket_select().



Добавить комментарий


Защитный код
Обновить






Кто на сайте
Сейчас 75 гостей онлайн