Сокеты в PHP
Тему сетевого программирования я еще не затрагивал, поэтому эта статья будет первым шагом в этом направлении. Сокеты, я бы сказал, - основа сетевого взаимодействия на прикладном уровне. С помощью этой технологии две программы, написанные даже на разных языках, могут обмениваться информацией, будучи достаточно удалены друг от друга. Нет, это не то, что изображено на картинке Это интерфейс обмена информацией сетевыми приложениями. |
Итак, рассмотрим, как реализованы сокеты в PHP, хотя принцип работы с ними одинаковый во всех языках.
Сначала напишем простые клиент и сервер: сервер будет запускаться и ждать соединения, а клиент соединяться к нему и посылать какую-то строку (информацию). А затем я покажу на примере, как отправлять письмо, используя почтовый SMTP-сервер.
Не буду приводить здесь список сокетных функций в PHP. По мере чтения примеров далее Вы сами увидете их.
Пишем простой echo-сервер
Что значит echo-сервер? Это значит, что строка, посланная серверу, возвращается в ответ тому, кто ее послал - клиенту. Т.е. эхо получается по сути.
Сначала нам нужно создать дескриптор сокета:
if (($sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) < 0) {
throw new Exception('socket_create() failed: '.socket_strerror(socket_last_error())."\n");
} else {
echo "OK\n";
}
Заметим, что я здесь использовал вызов socket_strerror(socket_last_error()), чтобы определить, в чем была причина ошибки, если вдруг возникла ошибка. Т.е. сначала мы определили номер ошибки с помощью socket_last_error(), а затем этот номер передали в socket_strerror, чтобы превратить его в текстовое описание ошибки.
Теперь нам нужно привязать созданный дескриптор к определенным адресу и порту машины, на которой он будет запущен. Обычно локальный адрес - 127.0.0.1, или localhost. Возьмем порт с номером 10001:
$port = 10001;
echo 'Bind socket ... ';
if (($ret = socket_bind($sock, $address, $port)) < 0) {
throw new Exception('socket_bind() failed: '.socket_strerror(socket_last_error())."\n");
} else {
echo "OK\n";
}
Затем нам нужно включить прослушивание этого сокета:
if (($ret = socket_listen($sock, 5)) < 0) {
throw new Exception('socket_listen() failed: '.socket_strerror(socket_last_error())."\n");
} else {
echo "OK\n";
}
Цифра 5 здесь означает, что мы разрешим встать в очередь на подключение к этому адресу максимум пяти клиентам.
Когда клиент попытается установить с нами соединение, нам нужно его принять:
if (($msgsock = socket_accept($sock)) < 0) {
throw new Exception('socket_accept() failed: '.socket_strerror(socket_last_error())."\n");
} else {
echo "OK\n";
}
После этого дескриптор принятого соединения сохраняется в переменной $msgsock, с которой мы далее будем работать.
Ну а дальше мы просто общаемся с клиентом, отправляя ему данные …
echo "Say to client ($msg) ... ";
socket_write($msgsock, $msg, strlen($msg));
echo "OK\n";
… или принимая их от него:
if (false === ($buf = socket_read($msgsock, 1024))) {
throw new Exception('socket_read() failed: '.socket_strerror(socket_last_error())."\n");
} else {
echo $buf."\n";
}
После всего этого «разговора» двух программ, нужно освободить ресурсы, вызвав socket_close:
socket_close($sock);
echo "OK\n";
Приведу полный исходный текст нашего простенького сервера:
server.php
header('Content-Type: text/plain;');
error_reporting(E_ALL ^ E_WARNING);
set_time_limit(0);
ob_implicit_flush();
echo "-= Server =-\n\n";
$address = 'localhost';
$port = 10001;
try {
echo 'Create socket ... ';
if (($sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) < 0) {
throw new Exception('socket_create() failed: '.socket_strerror(socket_last_error())."\n");
} else {
echo "OK\n";
}
echo 'Bind socket ... ';
if (($ret = socket_bind($sock, $address, $port)) < 0) {
throw new Exception('socket_bind() failed: '.socket_strerror(socket_last_error())."\n");
} else {
echo "OK\n";
}
echo 'Listen socket ... ';
if (($ret = socket_listen($sock, 5)) < 0) {
throw new Exception('socket_listen() failed: '.socket_strerror(socket_last_error())."\n");
} else {
echo "OK\n";
}
do {
echo 'Accept socket ... ';
if (($msgsock = socket_accept($sock)) < 0) {
throw new Exception('socket_accept() failed: '.socket_strerror(socket_last_error())."\n");
} else {
echo "OK\n";
}
$msg = "Hello, Client!";
echo "Say to client ($msg) ... ";
socket_write($msgsock, $msg, strlen($msg));
echo "OK\n";
do {
echo 'Client said: ';
if (false === ($buf = socket_read($msgsock, 1024))) {
throw new Exception('socket_read() failed: '.socket_strerror(socket_last_error())."\n");
} else {
echo $buf."\n";
}
if (!$buf = trim($buf)) {
continue;
}
if ($buf == 'shutdown') {
socket_close($msgsock);
break 2;
}
echo "Say to client ($buf) ... ";
socket_write($msgsock, $buf, strlen($buf));
echo "OK\n";
} while (true);
} while (true);
} catch (Exception $e) {
echo "\nError: ".$e->getMessage();
}
if (isset($sock)) {
echo 'Close socket ... ';
socket_close($sock);
echo "OK\n";
}
?>
Вызовом функции set_time_limit(0), мы говорим интерпретатору, что скрипт может выполняться бесконечно, а не 30 секунд максимум, как по-умолчанию прописано в php.ini.
А вызовом функции ob_implicit_flush мы говорим ему, что выводить строки с помощью echo нужно сразу при их выводе, а не после полной загрузки страницы, как это делается по-умолчанию.
Как видите, в этом простом скрипте мы сохраняем строку, которую нам пришлет клиент, в переменной $buf, затем ее же отправляем обратно клиенту. При этом мы сравниваем эту строку с ‘shutdown’. Если это ‘shutdown’, то корректно завершаем работу сервера.
Теперь запустите этот скрипт в браузере и Вы должны увидеть следующее:
-= Server =-
Create socket … OK
Bind socket … OK
Listen socket … OK
Accept socket …
Т.е. сервер ждет подключения к нему клиента. Ну что ж, не будем заставлять его долго ждать и напишем для него клиента.
Пишем клиента
client.php
header('Content-Type: text/plain;');
error_reporting(E_ALL ^ E_WARNING);
set_time_limit(0);
ob_implicit_flush();
echo "-= Client =-\n\n";
$address = 'localhost';
$port = 10001;
try {
echo 'Create socket ... ';
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if ($socket < 0) {
throw new Exception('socket_create() failed: '.socket_strerror(socket_last_error())."\n");
} else {
echo "OK\n";
}
echo 'Connect socket ... ';
$result = socket_connect($socket, $address, $port);
if ($result === false) {
throw new Exception('socket_connect() failed: '.socket_strerror(socket_last_error())."\n");
} else {
echo "OK\n";
}
echo 'Server said: ';
$out = socket_read($socket, 1024);
echo $out."\n";
$msg = "Hello, Server!";
echo "Say to server ($msg) ...";
socket_write($socket, $msg, strlen($msg));
echo "OK\n";
echo 'Server said: ';
$out = socket_read($socket, 1024);
echo $out."\n";
$msg = 'shutdown';
echo "Say to server ($msg) ... ";
socket_write($socket, $msg, strlen($msg));
echo "OK\n";
} catch (Exception $e) {
echo "\nError: ".$e->getMessage();
}
if (isset($socket)) {
echo 'Close socket ... ';
socket_close($socket);
echo "OK\n";
}
?>
Т.е. наш клиент соединяется к серверу по адресу localhost:10001. Далее после вывода того, что ему сказал сервер («Hello, Client!»), он приветствует сервера: «Hello, Server!». Потом сервер отвечает ему тем же (он же echo-сервер :)), а клиент отсоединяется от него, посылая команду shutdown.
После запуска этого скрипта, браузер у меня вывел следующие строчки:
-= Client =-
Create socket … OK
Connect socket … OK
Server said: Hello, Client!
Say to server (Hello, Server!) …OK
Server said: Hello, Server!
Say to server (shutdown) … OK
Close socket … OK
При этом вывод сервера выглядел следующим образом:
-= Server =-
Create socket … OK
Bind socket … OK
Listen socket … OK
Accept socket … OK
Say to client (Hello, Client!) … OK
Client said: Hello, Server!
Say to client (Hello, Server!) … OK
Client said: shutdown
Close socket … OK
Итоги
Фух, что-то статья получилась больше, чем я думал. Знаете, давайте я расскажу об отправке email с использованием сокетов в другой статье. Я знаю, что многие интересуются отправкой по SMTP с авторизацией, вот о ней и расскажу. А пока все. Удачных экспериментов с сокетами!
Даже не думал, что про сокеты можно вот так вот легко и в то же время подробно расписать) Отличная статья.
P.S. Держи еще одну идею :). С постов в этом блоге получился бы очень неплохой мануал, если собрать все это дело, например в pdf )
спасибо, познавательно
Я с вами полностью согласен.
Автор молодец! 5 баллов, нигде еще не встречал такой подробный и рабочий manual про сокетов в php -
2 porfeus: Спасибо! Рад такое слышать, т.е. читать
Спасибо, очень полезная статья.
Статья действительно очень доступно написана. Жаль только, что у меня не получается на практике поработать с сокетами. Даже не знаю почему. У меня стоит Денвер-3. Может кто-то знает как включить поддержку сокетов?
Prost: “Может кто-то знает как включить поддержку сокетов” -Заходишь в php.ini, ищешь через поиск строчку ;extension=php_sockets.dll и разкоментируешь ее те убираешь впереди знак-;
А цп неукого не нагружается??или так и должно быть?
Спасибо!
Супер. Большое спасибо автору
Большое пожалуйста читателю
странно не могу на 3м денвере работать с сокетами. разкоментировал ->скачал php_sockets.dll выдало что не хватает некого файла скачал -> php4ts.dll теперь возмущается что не может найти вход в ф-ю. не подскажете в чем могут быть проблемы?
Необходимо скачать и установить расширения для Денвера PHP 5: дополнительные модули.
[ссылка]
Очень информативно и полезною. Спасибо!
Как раз то, что искал.
Автору спасибо, коротко, понятно и работает =))
До этой статьи три мануала не осилил =)
Ошибку в socket_create() надо сравнивать с false.
manual
socket_create() returns a socket resource on success, or FALSE on error.
Уважаемый автор.
Подскажите пожалуйста как запустить описанный вами сокет-сервер, в ситуации, когда это не единственный php-скрипт в системе?
Т.е. предположим я взаимодействую с сервером написанном на php посредством обычных POST/GET запросов и хочу в каких-то особых случаях запускать сокет-сервер. Например так: получил команду от клиента на создание постоянного соединение, создал сокет, отдал клиенту порт и завершил текущий скрипт, а скрипт с сокет-сервером продолжает работать и закрывает сам себя когда он больше не нужен клиенту.
Я сам предполагаю следующее решение:
- Скрипт обработки команды, определяет порт сокета, который будет использоваться для взаимодействия в дальнешем, возвращает номер порта клиенту (и возможно сохраняет номер порта в бд для других обращений к этому же постоянному соединению), и завершается.
- Получив номер порта, клиент проверяет возможность соединения и если оно не открыто обращается к скрипту сокет-сервера передавая ему номер порта и таким образом запускает его. После чего сокет-сервер работает непоределенное время пока не закроется (по своему усмотрению либо по команде клиента).
Два вопроса в этой связи:
1. Правильно ли я рассуждаю. Представляется ли вам такая инфраструктура использования сокета разумной и корректной.
2. Не правильнее ли будет сразу получив команду запустить сокет-сервер в “автономное плавание” из скрипта обработки команды, с тем, чтобы клиент мог просто соединиться с сокетом, не запуская предварительно соотвествующий скрипт. И если это правильнее, то как это сделать?
С уважением.
Дмитрий, я думаю Вам стоит попробовать оба варианта - экспериментируйте и обязательно поймете, что лучше, а что нет. Если я правильно понял вопрос по второму варианту, то Вам нужно запустить скрипт сокетами в автономное плавание примерно следующим образом (тут описан запуск параллельного php-скрипта без передачи ему параметров через POST - передачу сами без труда сделаете):
$fp = fsockopen($server_name, 80);
fwrite($fp,
“POST /script.php HTTP/1.1\r\nHost: “.$server_name.”\r\n”.
“Content-Type: application/x-www-form-urlencoded\r\n”.
“Content-Length: 0\r\n”.
“Connection: close\r\n\r\n”
);
fclose($fp);
И не забывайте про функцию ignore_user_abort.
Novice, спасибо большое за советы. Вы позволите, я уточню детали?
Если я правильно Вас понял, Вы предлагаете разместить код сокет-сервера, о котором я писал выше, в файле, который в примере называется “script.php”, верно? И приводите способ его “отправки в автономное плавание”.
Способ неожиданно замысловатый. Учитывая совершенно очевидный недостаток информации по работе с сокетами в PHP, особенно. Получается, что не умея работать с сокетами из php-программы нельзя запустить параллельный (не дочерний) процесс, который не закроется с завершением вызывающего скрипта?
Насчет script.php - верно. Насчет запуска параллельного скрипта не с помощью сокетов - я думаю, что сокетами запускать проще всего. Возможно, есть и другие варианты, но, к сожалению, я не обладаю информацией о них. По поводу недостатка информации по работе с сокетами в PHP - если Вы программировали сокеты, например, на C++, то Вам не составит труда понять их и в PHP, т.к. принципы абсолютно те же.
Все понял. Еще раз большое спасибо. Сокеты программировал и на плюсах и на Java так что с ними самими никаких трудностей Некоторые трудности с .. как бы это сказать красиво… с виртуальной машиной PHP Отсюда и вопросы.
Нет, это не то, что изображено на картинке Это интерфейс обмена информацией сетевыми приложениями.
/* Там сокет под проц же нарисован какойто…. */
Подскажите, как заточить приведенный пример с сокетами под многопользовательскую схему. Я пробую и у меня не получается. Пытаюсь после принятия соединения на accept запихивать $msgsock в архив и работать на прием собщений опрашивая архив в цикле. В том же цикле опрашиваю и слушающий сокет. Но такая схема не работает. Не могу понять. Для каждого подключения новый слушающий нужен чтоли? Объясните пож. кто владеет информацией. Весь инет перерыл. На ПХП очень мало толкового. Спасибо.
Сокеты на PHP должны быть аналогичны сокетам на C++: создается “слушатель” с помощью socket_listen, затем socket_accept сидит в цикле и ждет. Как только к слушающему сокету подключается удаленный клиент, socket_accept создает “экземпляр” соединения и возвращает его, чтобы читать/записывать. Далее, если к этому же слушающему сокету соединяется другой клиент, для него так же socket_accept создает отдельный экземпляр и т.д. То есть получается, что слушающий сокет не нужно трогать - он нужен только для прослушки. А работать надо с ресурсом, который возвращается функцией socket_accept.
Вот вот. Я так и пытаюсь делать. Но это не работает. Комп начинает тормозить через минуту работы проги. Память чем то заполняется, хотя у меня стоит проверка переполнения массива с экземплярами сокетов сообщений. Вот код поправьте кто может. Буду рад помощи.
<?php
error_reporting (E_ALL);
/* Разрешить скрипту зависнуть в ожидании соединений. */
set_time_limit (0);
/* Включить неявную очистку вывода, и мы увидим всё получаемое
* по мере поступления. */
ob_implicit_flush ();
$address = ‘192.168.1.4’;
$port = 11000;
if (($sock = socket_create (AF_INET, SOCK_STREAM, 0)) < 0) {
echo “socket_create() failed: reason: ” . socket_strerror ($sock) . “\n”;
}
if (($ret = socket_bind ($sock, $address, $port)) < 0) {
echo “socket_bind() failed: reason: ” . socket_strerror ($ret) . “\n”;
}
if (($ret = socket_listen ($sock, 5)) < 0) {
echo “socket_listen() failed: reason: ” . socket_strerror ($ret) . “\n”;
}
$current_conn = 0;
$connections = array();
socket_set_nonblock ($sock);
$listen = true;
while ($listen)
{
if (($new_connection = socket_accept($sock)) $msgsock)
{
if (FALSE === ($buf = socket_read ($msgsock)))
{
socket_close ($connections[$key]);
unset($connections[$key]);
}
$buf = trim ($buf);
if (empty($buf))
{
continue;
}
if ($buf == ‘q’)
{
socket_write ($msgsock, ‘You exit now!’, strlen ($talkback));
socket_close ($connections[$key]);
unset($connections[$key]);
continue;
}
if ($buf == ‘s’ || count($connections) > 10)
{
$listen = false;
break;
}
$talkback = “PHP: You said ‘$buf’.\n”;
socket_write ($msgsock, $talkback, strlen ($talkback));
echo “$buf\n”;
}
}
reset($connections);
foreach ($connections as $key=>$msgsock)
{
$msg = “PHP: Server shuting down!\n”;
socket_write ($msgsock, $msg, strlen($talkback));
socket_close ($connections[$key]);
unset($connections[$key]);
}
socket_close ($sock);
exit();
?>
Подскажите, плиз: запускаю вышеприведенный скрипт сервера ([ссылка]) и у меня выходит следующее:
-= Server =-
Create socket … OK
Bind socket … OK
Listen socket … OK
Accept socket … OK
Say to client (Hello, Client!) … OK
Client said:
Client said:
Client said:
Client said:
Client said:
Client said:
Client said:
Client said:
………… и т.д.
Т.е. строка “Client said: ” выводится безконечное множество раз. В чем проблема?
elvis, это значит, что сервер почему-то принял от клиента пустое сообщение (в коде server.php есть условие).
Разобрался наконец. Вот рабочий пример многопользовательской схемы. Работает в одном потоке т.к. в WЦindows pcnctl не поддерживается. Прога криво останавливает сервер. Просто не успел подправить разрыв соединений. Нужно создать флаг завершения и отключать пользователей в цикле while($listen). В остальном работает и наглядно показывает как и что. Вот исходник:
———————————————————-
<?php
error_reporting (E_ALL);
/* Разрешить скрипту зависнуть в ожидании соединений. */
set_time_limit (0);
/* Включить неявную очистку вывода, и мы увидим всё получаемое
* по мере поступления. */
ob_implicit_flush ();
$address = ‘192.168.1.4’;
$port = 11000;
///*
if (($sock = socket_create (AF_INET, SOCK_STREAM, 0)) < 0) {
echo “socket_create() failed: reason: ” . socket_strerror ($sock) . “\n”;
}
if (($ret = socket_bind ($sock, $address, $port)) < 0) {
echo “socket_bind() failed: reason: ” . socket_strerror ($ret) . “\n”;
}
if (($ret = socket_listen ($sock, 5)) < 0) {
echo “socket_listen() failed: reason: ” . socket_strerror ($ret) . “\n”;
}
$current_conn = 0;
$connections = array();
socket_set_nonblock ($sock);
$listen = true;
while ($listen)
{
if (($new_connection = @socket_accept($sock)) 0)
{
$num = socket_select($read, $write, $exceptions, $timeout);
echo(“changed sockets number = {$num}\r\n”);
}
// reset($write);
if ($num > 0)
{
echo(“Changed sockets for reading “.count($read).”\r\n”);
foreach ($connections as $key=>$s)
{
//$key = array_search($s, $connections);
$can_write = array_search($s, $write);
$can_read = array_search($s, $read);
if ($can_read !== FALSE) $can_read = true;
if ($can_write !== FALSE) $can_write = true;
if ($can_write && $s === $new_connection)
{
$msg = “\nWelcome to the PHP Test Server. \n” .
“To quit, type ‘quit’. To shut down the server type ‘shutdown’.\n”;
socket_write($s, $msg, strlen($msg));
}
echo(“Try read from socket {$key}\r\n”);
if ($can_read && FALSE === ($buf = socket_read ($s)))
{
socket_close ($connections[$key]);
unset($connections[$key]);
echo(“Unable read from socket {$key} close conection! \r\n”);
continue;
}
if ($can_read)
{
$buf = trim($buf);
echo(“read ‘{$buf}’ from socket {$key}\r\n”);
}
else $buf = ”;
if (empty($buf))
{
continue;
}
if ($buf == ‘q’)
{
if ($can_write)
socket_write ($s, ‘You exit now!’, strlen ($talkback));
socket_close ($connections[$key]);
unset($connections[$key]);
continue;
}
if ($buf == ‘s’ || count($connections) > 10)
{
$listen = false;
break;
}
$talkback = “PHP: You said ‘$buf’.\n”;
if ($can_write)
socket_write ($s, $talkback, strlen ($talkback));
echo “$buf\n”;
}
}
}
reset($connections);
foreach ($connections as $key=>$msgsock)
{
$msg = “PHP: Server shuting down!\n”;
socket_write ($msgsock, $msg, strlen($talkback));
socket_close ($connections[$key]);
//unset($connections[$key]);
}
socket_close ($sock);
//*/
exit();
?>
Статья хорошая получилась.
Только вот возникают вопросы по многопользовательской системе работы через сокеты.
У меня стоит сервак в сети я запускаю на нем серверную часть и подключаюсь к нему по телнету, все ок.
Но как сделать, чтобы с других компов я мог подключиться к этому сокету?
Смотри мой пост выше. У меня все работает. С трех компов коннектился. Удачи.
Твой пример не работает..
огромное тебе спс автор!
действительно тикаво читать!
А что не работает в моем примере? Возможно какие то детали в коде вылетели при модерировании.. Так было с первым постом. Но там простые вещи, которые можно понять и поправить. Вроде отсутствия скобок или т.п.
ilyichzc, исправь плиз, ошибки в твоем коде, если не сложно. Я попробовал сам исправлять, но мой Апач постоянно ругается - ошибка на ошибке. Кстати, твой код в формате Юникод.
Если уважаемая администрация позволит, то дам ссылку на скрипт в zip архиве. А то так и будет дырявый код.
[ссылка]
Добрый день!
Очень понравилась статья. Большее спасибо автору!
У меня возник следующие вопросы. Получается, что для того, чтобы все работало, нужно, чтобы был запущен server.php. Тогда:
как сделать так, чтобы он был постоянно запущен? Или какие механизмы есть, чтобы сервер запускался перед тем, как работать с сокетом?
как запустить server.php? То есть я, например, открывал в браузере. А если мне не нужно выводить никакой информации, можно ли сделать как-то, чтобы “серверный” сокет как-то всегда находился в памяти?
Спасибо!
Чтобы socket.php был постоянно запущен, Вы можете запустить его с помощью тех же сокетов:
$fp = fsockopen($server_name, 80);
fwrite($fp, “POST /server.php HTTP/1.1\r\nHost: “.$server_name.”\r\n”.”Content-Type: application/x-www-form-urlencoded\r\n”.”Content-Length: 0\r\n”.”Connection: close\r\n\r\n”);
fclose($fp);
При этом необходимо, чтобы на сервере директива PHP max_execution_time была установлена в 0, иначе выполнение server.php будет прерываться.
Не нужно открывать скрипт в браузере. Сделайте batch файл в котором пропишите:
c:/PHP/php-cgi.exe -f d:/work/server/server.php
Спасибо за помощь!
Решил написать службу, а в ней открывать сокет
Еще раз благодарю за статью!
Было бы ваще супер, если бы еще была ссылочка на примерчик в зип(рар)-архиве… Скиньте плыз на мыло(artikz@ya.ru) т.к. временно пропадаю и не смогу ближайших пару месяцев войти в инет.
Не подскажите почему выводится ошибка:
Fatal error: Call to undefined function socket_create() in /home/host800681/flexsdk.ru/htdocs/www/flexapps/fex/server.php on line 15
для этой функции нужно что-то дополнительно ставить на сервер?
Вобщем начинал я с этого поста и в итоге написал игровой сервер. Под windows работает без нареканий, а под CentOS не могу запустить. Беда такая:
Не хочет работать с mySql сервером. Коннект проходит без ошибок и первый запрос тоже. В первом запросе система проверяет созданы ли нужные таблицы и если нет, то создает их самостоятельно. Дак вот все последующие запросы получают “MySQL server has gone away”, хотя wait_timeout стоит приличный, да и запросы происходят сразу после запуска скрипта. Не подскажет ли кто как победить данную проблему т.к. я в никсах не специалист
поведуйте, как в серверном скрипте узнать IP подключившегося клиента?
Возник такой вопрос. Как организовать множественное подключение? Т.е. чтобы 2 и больше клиента могли быть, и не могли отключать сервер. Что бы он всегда крутился в системе.
Автор, огромное тебе спасибо:) Довольно подробно и на отлично объясненных примерах все написано! Я - новичек в php и эта статья для меня была очень полезна
Здравствуйте, у меня при прочтении статьи возник вопрос.
Когда клиент отрубается от сервера, работа сервера прекращается. ка кже сделать. чтобы он дальше продолжал ловить подключения?
В большинстве случаев сокеты используют для обращения к уже
существующим серверам. Если будет желание, попробуйте раскрыть
тему по обращению через сокеты к различным действующим серверам по
различным протоколам. У Вас получается о сложном доступно.
Успехов
Vsevolod: Скорее всего у вас в php.ini не подключен модуль
sockets.
Отвечаю на вопросы: “Как сделать, чтобы сервер не отключался после отсоединения клиента?” и “Как обеспечить работу с множественными подключениями?” -
Приведенный мной выше код обеспечивает обработку множества соединений без остановки в реальном времени. Единственное ограничение это цикл обработки одного клиента т.к. остальные все равно стоят в очереди. По этому цикл обработки одного соединения не должен быть очень долгим (например соединение с другим сервером для получения даных по http может занять несколько секунд, что скажется на других пользователях).
Для того, чтобы сервер не останавливался его нужно запускать не из под апача как скрипт, инициируемый браузером, а как самостоятельную резидентную программу -демон, из командной строки. В UNIX обычно php myServer.php. В WINDWS путь к php\php-cgi.exe -f myServer.php. Для нормальной работы программа должна правильно демонизироваться. На эту тему есть достаточно информации в сети. Рекомендую поискать класс PhpDaemon. Он позволяет быстро путем наслодования создать собственный демон.
Забыл добавить, что демонизация не обязательна. Программа, запущенная из командной строки и так будет работать пока ее не вырубят. Демонизация же позволяет отвязать процесс от конкретного окна и заставить работать на фоне. В PHP демонизация возможна только в POSIX системах, к которым windows не относится.
Спасибо большое. Занимаемся написанием игрового сервера Flash + PHP с сокетным соединением (не loadVars() - тоесть вечное нужно соединение). Пост неплохо помог разобраться в самых азах - долго не мог понять как можно вызывать из клиента сервер по IP если у нас server.php имеет вполне определенное имя скрипта. Сразу все встало на места в голове, когда прочел строчку о запуске сервера из браузера.
Если не трудно ilyichzc - пожалуйста, выложите где-нибудь на народе/рапидшаре и т.д. архивчик со скриптом на предмет покопаться. По приведенной ссылке не грузится к сожалению.
Автору поста - радует, когда такие подробные комментарии в тему уточняют сам пост. Это редчайший вариант блогосферы, обычно такое на форумах происходит. Респект!
Исходника к сожалению не осталось. Есть законченый батлнет для мобильных телефонов. Но то штука комерческая и по этому не могу ее выдать, а выдерать что то оттуда нет времени. Причем начинал делать с этого поста. Многие хулят PHP, что мол медленный очень для таких задач. Я испытывал свой сервер 500 ми ботами, которые одновременно рвали его на части (запрашивали инфу друг про друга, про комнаты, создавали комнаты и т.п.), при этом сервер работал нормально и показал в худшем случае 0.9 миллисекунд задержки на цикл. Так что для первого проекта вполне не плохо, учитывая что разработка занимает не много времени.
Ок, да в принципе и не нужно. Во время освоения мануала и поста показало, что ваш пример почему-то сразу рвет коннект с сервером, telnet при подключении выдает что-то типа “подключение утеряно”. Видимо либо где-то побился кусок кода, либо элементарный баг в счетчике.
А так улыбнуло, что хостер сам не знает, открыты ли у него порты наружу или нет.
2 ilyichzc:
А немогли бы вы выложить “многпоточный скрипт” тот который приводили выше, а то при копирование он не работает, даже с корректировками. И ещё, клиента можно оставить того же что приводит novice или тоже нужен модифицированный?
Видимо, комментарий не прошел антиспам. Хороший, детально документированный (на английском) пример многопоточного (правильнее говоря обрабатывающего более 1 клиента) скрипта находится в комментариях на сайте к функции socket_select() - точную ссылку не привожу чтоб антиспам пропустил. Там же есть мой комментарий с особенностями использования клиента на флеше. Также, есть куски кода на flasher.ru по ссылке forum/showpost.php?p=901346&postcount=7 (сами подставьте)
А вот я кой-чего не могу догнать. Что такое socket_set_option($sock, SOL_SOCKET, SO_REUSEADDR, 1); и зачем оно нужно? Такая строчка присутствует в примере кода. Я построил уже на этом примере сокет для игры в кости, все прекрасно работает. Но вот начал детально разбирать код - и так и не смог найти адекватного объяснения зачем это и что это.
Спасибо за пример, очень помог. А вот как сделать когда 3 и д.т. клиента запущенно сразу, что-бы 1 клиент отправлял другим клиентам сообщения, я через массив замутил, но когда 2 человек просто рубит соеденение, массив не чистится
Статья супер! Так все просто и понятно про написано.
ilyichzc
не могли бы вы выложить еще раз файлы?
Очень нужен много поточный сервер.
Такой вопрос. При запуске на локальной машине серверная часть работает нормально. Подключаюсь к сокетам из софтины на дельфе. При установке скрипта на хостинге из вне подключиться не удаётся. Если же запустить простенький скрипт проверки открытых портов на том же хосте показывает что заданный порт открыт. В чём может быть причина что из вне достучаться до порта не получается?
Сейчас попробую по мануалу наваять клиента для чтения данных с Шоукаст сервера.
Однако, получилось. Спасибо за пример кода.
Спасибо, отличная статья!
Столкнулся с такой проблемой стоит Denwer3_PHP5,
-= Client =-
Create socket …
Fatal error: Call to undefined function socket_create() in Z:\home\localhost\www\testworkflash\client.php on line 15
может кто знает или сталкивался с этой проблемой, помогите.
Столкнулся с такой проблемой стоит Denwer3_PHP5,
-= Server =-
Create socket …
Fatal error: Call to undefined function socket_create() in Z:\home\localhost\www\testworkflash\server.php on line 15
script language=JavaScript src=’/denwer/errors/phperror_js.php’></script
может кто знает или сталкивался с этой проблемой, помогите.
Извеняюсь за второе сообщение, он в первом не показал целиком ошибку, пришлось делать второе.
Денвер по умолчанию собран без поддержки сокетов. Вообще говоря, вам не помешало бы подучить английский, или научиться гуглить сообщения об ошибках.
Call to undefined function socket_create() - переводится как “вызов неопределенной функции socket_create()” - иными словами, нет модуля socket в PHP
Подскажу вам еще. на 99% виртуальных хостингах (то что по 3-5-7 долларов) сокеты закрыты. Даже если собран PHP с поддержкой оных - вы всеравно не сможете достучаться до сокета извне, то есть соединитmcя с ним. Поэтому либо ставьте себе не денвер, а PHP с собственной компиляцией, либо покупайте VDS - их настраивают с открытыми сокетами по вашей просьбе. Цены от 20 долларов, даже я бы сказал от 25. Для теста хватает аж бегом.
Я с сокетами сталкиваюсь в первые и вообще столкнулся потому что делаю небольшой проект, может быть покажется глупым мой вопрос, но всё же спрошу, С помощью сокетов(тоесть соединений с сервером) возможно ли соединить несколько флешей ,допустим небольшой чат, чтобы вся информация переносилась в реальном времени(от одного флеша в другой) по нажатию на кнопку, и ещё, неохота платить пока проект не готов, как можно сделать всё что нужно для теста соединений(сокетов) на локальном компе и вообще реально ли как нибудь это сделать?
Связать флешки - реально, более того - это единственно адекватный способ организации реал-тайм приложений (игр, чатов и т.п). Мы в данный момент разрабатываем портал флеш-игр, и все отлично работает.
Для теста - поднимайте у себя PHP сервер (не денвер как пакет, а отдельно БД. отдельно ПХП процессор и т.д.) и в сборку при компиляции включите сокет модуль.
Но это геморрой, проще купить или “пропетлять” у знакомых себе сервер уже настроенный. Я как веб-разработчик бы и в страшном сне не представил себе поднятие сервера под виндой у себя на машине.
Попробуйте поискать денвер, но с сокетами - может у них есть спецсборка. Или может денвер-пакет там самому можно сделать, указав какую сборку PHP использовать. Не знаю, я денвером не пользуюсь уже три года.
Пусть не подумают о рекламе… А ты не мог сказать более или менее хостинг нормальный который можно купить, что бы там помогли всё настроить… или в асю стукни 240016019
У меня выделенный сервер. Пробуй - мне они нравятся. На аську нет времени, к сожалению.
Фу, наконец дошел до конца и нифига не понял. Непонятна сама технология, то есть как это работает.
Не понимаю как server.php связан с client.php, как они вместе то работают. Кошмар вот каша в голове. Ща соберусь и вопрос задам.
1) Получается браузер соединяется с сервером?
2) Если много браузеров соединилось, то можно ли что бы сервер от каждого принимал сообщения потом выдавал их и на во всех браузерах выводились все сообщения всех браузеров.
Я думаю судя по моим вопросам вы поняли на какой стадии я нахожусь в плане сокетов.
Посоветуйте пож-та ченить почитать, а то время 7 утра и эта тема мне уснуть не дает уже который день.
А началось все с замашек написать видео конференцию. Сейчас ломаю голову над простым чатом на сокетах.
Да, стадия ясна.
Для начала, давайте определимся. Браузер ни с кем никогда не соединяется. Когда вы заходите на сайт, но PHP скрипт выполняется на сервере, что-то там делает, выдает результат в виде текста - и этот-то текст и отображается в браузере. Точка.
Чуть хитрее устроены парсеры. Вы заходите на страничку index.php, запускается скрипт-парсер. Он допустим с помощью Curl чего-то тамм запрашивает на другом сайте, ждет пока отработает другой скрипт и получает его результат. Затем он чего-то там делает с результатом - и обработанный результат того, второго скрипта, выдает вам на страницу.
Ключевой момент - все, что я написал раньше, имеет цикл жизни. То есть у нас есть задача, решаемая за конечный промежуток времени и не ожидающая ввода данных (ну входные данные есть, но ждать их от юзера не нужно, они получаются все и сразу в момент запуска скрипта).
А тот же чат должен быть построен на схеме, независимой от временного промежутка. Мы ж не знаем, в какой момент кто что напишет в чат. Конечно, можно решать обновлением данных по таймауту, как это было раньше в ифреймовых чатах, но при большом потоке данных и критичности скорости передачи - этот меток перестает подходить.
Теперь смотрите на ключевые моменты скрипта, работающего на сокетах:
1) он не зависит от времени. Сразу первой строчкой ставится выключение лимита на выполнение скрипта
2) он реагирует на два раздражителя. Первый - ввод данных из клиента, второй (не описанный выше) - на срабатывание таймера
Причем же тут браузер и бесконечно крутящийся скрипт? Так вот, основная цель “сервера” - обрабатывать запросы клиента. При этом все реакции он выдает клиенту, через запись в сокет. А браузер здесь нужен только для того чтобы
а) запустить скрипт (его же нужно как-то инциализировать, “дважды кликнуть мышкой по экзешнику”)
б) читать в режиме реального времени отладочные логи
Получается браузер ничего никому не передает (в случае сервера), он просто запускает скрипт на выполнение и тупо слушает отладочные логи. Лично в моих скриптах стоит второй строчкой параметр ignore_user_abort() - то есть я запускаю сервер на выполнение и сразу закрываю браузер - все, он мне нафиг не нужен. Я работаю с сервером через команды из клиента (в моем случае это флешка).
Как только вы уясните себе этот скорее психологический момент работы сокет сервера - сразу все встанет на свои места. Останется только понять психологию работы таймера (там все просто) - и можно ваять.
Обращаю ваше внимание, что если вы будете писать клиента на флеше (а скорее всего так и будет, у вас же видео-стриминг) - то просто так флеш не соединяется с сервером, там есть пара сложных моментов. Во всяком случае с использованием socketXML.
Запускаю server.php как демона.
После того как клиент подключился, выполнил необходимые операции и отключился, демон продолжает есть процессор. Причем все время на 100%. Как быть?
Закачайте пожалуйста снова рабочий скрипт, последнего варианта!!!!или мне на почту , очень нужно!!!Ошибки сидел исправлял и в итоге
Warning: socket_select() [function.socket-select]: no resource arrays were passed to select in …..
socket_select() failed, reason: Операция на незаблокированном сокете не может быть завершена немедленно.
Здравствуйте! Очень помогла ваша статья.
У меня возник глупый вопрос: всё отлично работает, когда файл находится в localhost и принимающий сокет “связан” с 127.0.0.1 и портом 10001.. клиент (пока также находится в localhost) и к нему цепляется.
Как правильно установить это дело на сервере, чтобы можно было связаться с ним “удалённо” (положили файл server.php на сервер у друга, пробовали “связать” принимающий сокет с ip сервера:
$adres = “(ip сервера)”;
$port = “(порт, “разрешённый на сервере” )”;
socket_bind($s,$adres,$port);
Однако клиент с указанным адресом и портом не соединяется).
Подскажите пожалуйста, что мы сделали не правильно, и что могли не учесть?
Прошу прощение за карявый вопрос.
>После того как клиент подключился, выполнил необходимые операции и отключился, демон продолжает есть процессор. Причем все время на 100%. Как быть?
Вставьте usleep(250); в любое место цикла с ожиданием
Гениально!
На Вашем сайте единственный работающий сокет-клиент скрипт во всем интернете.
Спасибо Вам!
Как правильно запустить скирпт на сервере ?
А у меня хоть убейте не получается даже запустить. Пробовал на Денвере, на хостинге в инете тоже пробовал - сервер в итоге у меня выдает следующее
-= Server =-
Create socket … OK
Bind socket … OK
Listen socket … OK
Accept socket … OK
Say to client (Hello, Client!) … OK
Client said:
Client said:
Client said:
Client said:
Client said:
и так до бесконечности…
Код скопирован до буковки. В чем может быть проблема, помогите пожалуйста разобраться)
Прошу прощения, на Денвере реально не работает даже при подключении сокетов как библиотеки. Получается описанный выше пример с бесконечным срабатыванием Client said. А на хостинге нормальном - работает. Спасибо за пищу для мозга)
Здравствуйте. Я читал ваш сокеты в PHP и сделал пример. Пример тестировал на xampp. Все работает отлично. Есть вопрос:
Как сделать настройки, ваш два файла server.php и client.php должно быть разном компьютере?
$address = “192.168.0.8”; //server.php
$address = “192.168.0.9”;//client.php
Я сделал один компьютер etc/host/: 192.168.0.8 localhost1 а второй etc/host/: 192.168.0.9 localhost2 и server.php файл на первом(192.168.0.8) компьютеры сохраняется, client.php файл на втором(192.168.0.9). У меню настройка правильно или…. Как можно сделать настройки двум компьютера.
(Пипец великому и могучему. Столько ошибок в одном сообщении…)
По поводу PHP: В клиенте надо прописывать адрес и порт сервера, к которому клиент будет подключаться.
Gairon: По поводу PHP: В клиенте надо прописывать адрес и порт сервера, к которому клиент будет подключаться.
У меню server.php файл один компьютере а client.php на другом.
server.php
$address = “192.168.0.8”;
$port = “8080”;
Результат: Create socket…OK,Bind socket…OK,Listen socket…OK,Accept scoket…OK,Say to client(Hello client!)…OK,Client said:Client said:Client said:Client said:
client.php
$address = “192.168.0.8”;
$port = “8080”;
Результат: Create socket…OK,Connect socket…OK,Server said:Say to server(Hello client!)…OK,Server said:Say to server(shutdown)…OK,Close socket…OK
Здесь гду можно быть ошибка? Помогите решать этого задачу?Спасибо!
shushrat,
судя по логам конект проходит нормально, проблема либо в отправке сообщений, либо в отображении - трассируй код.
Gairon спасибо вам за ответов. Я проверил отправка сооб. и честно говоря не понял трассировка кода. Вот у меня общий код.
server.php
getMessage();
} if (isset($sock)) {
echo “Close socket … “;
socket_close($sock);
echo “OK\n”; } ?>
client.php
$address = “192.168.0.8”;
$port = 80;
try {
echo “Server said: “;
$out = socket_read($socket, 1024);
echo $out.”\n”;
$msg = “2”;
echo “Say to server ($msg) …”;
socket_write($socket, $msg, strlen($msg));
echo “OK\n”;
echo “Server said: “;
$out = socket_read($socket, 1024);
echo $out.”\n”;
$msg = “shutdown”;
echo “Say to server ($msg) … “;
socket_write($socket, $msg, strlen($msg));
echo “OK\n”;
} catch (Exception $e) {
echo “\nError: “.$e->getMessage();
}
if (isset($socket)) {
echo “Close socket … “;
socket_close($socket);
echo “OK\n”;
}
?>
1. Спасибо за статью. Оказалась полезной многим.
2. Вопрос: если я подсоединяюсь клиентом к серверу, посылаю серверу сообщение, но ответ должен приходить каждые 1-2 минуты. Как тут быть? Можно ли не использовать socket_close($socket);? Или чтоб socket_close($socket) исполнялось по нажатию кнопки… Может существуют другие способы поддержки соединения с сервером?
1. Спасибо за статью. Оказалась полезной многим.
2. Вопрос: если я подсоединяюсь клиентом к серверу, посылаю серверу сообщение, но ответ должен приходить каждые 1-2 минуты. Как тут быть? Можно ли не использовать socket_close($socket);? Или чтоб socket_close($socket) исполнялось по нажатию кнопки… Может существуют другие способы поддержки соединения с сервером?
И как быть, если сервер хочет только ssl соединения? Если дописать так: $address = ‘ssl://localhost'; прокатит или обругает?
Кого работает этот server.php и client.php. Вы можете показать работающий программы. У меня все работает но ничего отправляется. Пожалуйста покажите как это будет? Спасибо!
Уважаемый автор, правильно ли я понимаю, что в серверной части, часть кода
———————————————————-
$msg = “Hello, Client!”;
echo “Say to client ($msg) … “;
socket_write($msgsock, $msg, strlen($msg));
echo “OK\n”;
———————————————————-
живёт в бесконечном цикле и выполняется безотносительно того, подключился клиент или нет ???
Вот то что искал, начинаю работу над php торрент клиентом, спс автор
Sergey, эта часть кода живет бесконечно, но не выполняется постоянно. Там выше функция socket_accept не даст дальше выполняться скрипту, пока не подключится клиент. А после подключения клиента ему выдается сообщение “Hello, Client!” и начинает работать вложенный цикл do, читающий сообщения от клиента и отвечающий ему эхом, вот как раз этот цикл работает постоянно и при отсутствии сообщения продолжает цикл сначала (continue). При отключении клиента вложенный цикл завершается (break 2) и снова скрипт возвращается к внешнему циклу и останавливается на socket_accept до нового подключения
Нда, статья видимо не для новичков. У меня на денвере ничего не работает. Смущает вот это throw new Exception() - это что за класс? где взять его? И где можно почитать про try/catch/throw в php?
Спасибо!
Огромное спасибо автору, тем более что я только позавчера взялся за ПШП и вбил в поисковнике порт 10001. Хочу написать сайт ЖПС трэкера.
Автору огромное СПАСИБО!!! Очень много читал статей, но эта просто супер расписана. Также хочу отметить что много нюансов решено благодаря комментариям активных участников дискуссии.
ПыСы думал до конца не дочитал все комментарии!!! Всем спасибо!