Php проверка работы сокетов
Сегодня будет много работы
Начнем с простого.
ws сервер + панель управления v.0.3.
Прежде чем начать работу с ws, рекомендую скачать вам архив Downloads, ws server admin panel v.0.3., и установить его хотя бы в Денвере, поскольку все дальнейшие приёмы и способы будут разобраны на базе данной системы.
Данная версия выглядит гораздо более понятной и структурированной. Взяв её за основу, мне удалось перенести относительно сложное приложение выстроенное на AJAX на технологию ws менее чем за пол рабочего дня.
Немного о разработке большого приложения
Постараюсь в несколько слов рассказать о своём опыте. Ранее у меня уже было приложение которое работало на AJAX. Переход оказалось осуществить достаточно просто. В классе websocketserver_class я завёл экземпляр главного класса игрового проекта game_class. В game_class я заменил получение данных из переменных $_GET на получение данных из переменных передаваемых в него из websocketserver_class в качестве аргументов. Также websocketserver_class передавал еще и id игрока с которым произошло событие, поскольку процесс всегда загружен в памяти и для него не могло существовать сессий игроков. С помощью полученного id game_class опознавал игрока, восстанавливал данные игрока в класс player_class, производил над ним определенные манипуляции в итоге сохраняя данные из player_class в БД или файл или память. Таким образом переход с AJAX на ws оказался действительно очень прост. Вот, кстати, как стала выглядеть диаграмма классов после перехода на ws.
Комментарии, как всегда, приветствуются.
upd 2014.11.27: следующая статья, релиз анонимного чата.
Отладка приложений на веб-сокетах на PHP
Первое на что нужно обратить внимание, это на то, что ошибки разрабатываемого кода могут быть нескольких видов, но самые распространённые с которыми может столкнуться разработчик:
Всё просто когда вы работаете на прямую с веб-страницей на PHP, ведь для этого вам достаточно воспользоваться директивами error_reporting и display_errors, но как понять какая ошибка произошла когда вы работаете с демоном использующим веб-сокет и который не запускается при нажатии на кнопку start из панели управления ws server admin panel v.0.3.?
Далее, если в результате работы скрипт упал, вы увидите красную надпись в панели управления. Причину ошибки можно будет посмотреть там же в окне лога ошибок сервера echo ws server errorfile. Вы увидите в конце строку PHP Fatal error с описанием того, что случилось. Чаще всего это может быть обращение к несуществующему элементу массива, обращение к заприваченному свойству объекта или вызов неизвестной функции. Таким образом дописывая исходный код вашей программы, вы сможете проверять его не только на этапе интерпретации но и в процессе выполнения. Если не удаётся разобраться с проблемой во время выполнения программы (E_ERROR). Например, в логиге ошибок (echo ws server errorfile) указано что идёт обращение не по адресу или происходит попытка вызвать неизвестную функцию, в таком случае я предлагаю использовать функции логирования consolemsg и функцию представления содержимого переменной test_var_value в теле программы, чтобы посмотреть как ведёт себя программа во время выполнения и почему она приходит к таким результатам.
Проверка поддержки сокетов на хостинге и в Денвере
– Что нужно сделать в первую очередь для начала работы с WebSocket?
– Проверить поддержку сокетов на хостинге и в Денвере.
Для этого создаём простенький файлик sockettest.php
Загружаем на хостинг и обращаемся к нему через браузер, и, здесь может быть самое неприятное, хостинг выдаст WebSockets UNAVAILABLE. В таком случае нужно искать другой хостинг, либо, если есть возможность поменять в настройках хостинга версию и комплектацию PHP на PHP с sockets, добиваться того, чтобы хотсер волшебным образом это сделал. Если хостер такой возможности не предоставляет, то тогда при выборе нового хостинга, нужно смотреть на php_info(); и по нему определить поддержку сокетов по наличию следующей строки. А для того, чтобы сокеты заработали в Денвере, пришлось немного покопаться.
В моём случае хостинг заработал с первой попытки, что в 2014 году вероятно на 80%.
Небольшой обзор проделанной работы
Я веду небольшой (около 3х тысяч строк) игровой проект Growing Crystals(последняя AJAX-версия до перехода на ws) о нём есть много материалов в моём блоге. Изначально весь обмен данными клиент-сервер был выстроен на технологии AJAX. Однако, по советам друзей, я обратил внимание на технологию веб-сокет что и привело к написанию первой статьи о работе с веб-сокетами на PHP. Затем пришлось решать задачи запуска веб-сокет демона, и закончилось эта история разработкой простых приложений: панели управления веб-сокет сервера и чата на веб-сокете. После получения панели управления и реального работающего чата, я вернулся к разработке проекта Growing Crystals, ради которого изначально и затевалось знакомство с веб-сокетами. Это было огромным удовольствием осознавать на сколько правильно поступил выбрав в качестве транспорта для своего игрового приложения веб-сокет, технология оказалась очень удобна и органична с точки зрения встраивания в проект. В то же время, с момента выхода статьи с чатом, прошло уже более месяца и благодаря вашим комментариям я почувствовал необходимость поделиться опытом разработки гораздо более сложного приложения чем чат, и заодно предоставить вашему вниманию обновленный и более удобный инструмент Downloads, ws server admin panel v.0.3., а также инструкцию по его разворачиванию.
Прежде чем двинуться дальше напомню, что ws это веб-сокет, а pid-файл, это файл содержащий id процесса ws в системе, он создаётся и хранится только для того, чтобы можно было проверить запущен ли ws и если запущен то работает ли он в настоящее время.
ws сервер управление процессом PHP из браузера
Какой должен быть интерфейс для ws клиента:
- Проверка состояния ws сервера и запуск, если сервер недоступен
Какой должен быть интерфейс для ws администратора:
- Информация о статусе ws сервера, желательно с количеством подключений и объёмом занимаемой памяти
- Просмотр журналов
- Остановка работы ws сервера
- Перезагрузка ws сервера
Итак, для реализации нам нужен ws сервер, который необходимо повесить в памяти одним из описанных ранее способов, т.е. нужно сделать из него качественного демона. Отличная статья относительно процессов и демонов есть на хабре. Однако, к сожалению, найти хостинг с поддержкой команды создания дочерних процессов на PHP pcntl_fork еще труднее чем с поддержкой сокетов, поэтому придётся отказаться от классического способа демонизации. Также такие программы невозможно отладить на windows т.к. forkи существуют только в *nix операционных системах. Но всё же кое что полезное из статьи почерпнуть удалось, а именно создание PID-файла хранящего process id, который не позволит запускаться двум процессам одновременно — подробнее об этом чуть ниже.
В итоге я немного модифицировал код PHP скрипта ws echo сервера вместив в него код переключения потоков ввода/вывода STDIN, STDOUT, STDERR и, тем самым, упростил запуск ws сервера из консоли:
И опять проблема в том, что функция posix_kill($pid,0) оказалась не работоспособной по той же причине что и pcntl_fork. Я не смог с этим мириться и опять разработал “хитрое” решение. Т.к. я всё равно задумал реализовывать функционал показывающий состояние ws сервера, то мне так или иначе потребуется функция которая показывает статус процесса в ОС. Для реализации этой функции воспользуемся командой exec() которая позволяет выполнять любые команды консоли. И если мы выполним
то в результате в массив $output, в случае если демон запущен и имеет $pid будет выдана информация о демоне (процессе).
Таким образом получили реально работающие функции без posix_kill, которые проверяют запущен ли демон и выдают данные о нём соответственно.
Upd 2017.08.14: В примере выше используется BSD синтаксис команды ps. Для большинства *nix систем он будет отличным, а эта команда будет выводить все процессы вместо одного с идентификатором pid. Начиная с версии ws server admin panel v.0.4. и выше используется классический синтаксис, корректно работающий в большинстве *nix систем.
Метод бесконечного выполнения PHP процесса запуск из консоли
PHP может быть использован для запуска PHP-скриптов в абсолютной независимости от Apache, но у меня нет уверенности что без Apache будет работать механизм сокетов, я не пробовал запускать без Apache — мне это показалось не к чему. Запуск через консоль считается более правильным нежели через веб-браузер, но он также как и запуск через браузер не способен решить ряд проблем. Возможно такой запуск и избавит нас от прекращения работы скрипта при перезапуске Apache и то это маловероятно, но что делать, если весь веб-сервер или виртуальная машина будет перезагружена. Вам придётся в ручную лезть на сервер и запускать скрипт, конечно если у вас большой игровой проект и есть выделенные системные администраторы которые мониторят состояние процессов на сервере и есть скрипты инициализации и загрузки ws сервера вместе с Apache и всем остальным, в таком случае это единственно правильный вариант, но мы говорим о бытовом удобном способе реализации ws сервера на PHP для небольших проектов. Также иногда встречается проблема при запуске PHP скрипта из консоли, которая прекращает выполнение PHP скрипта одновременно с тем когда вы выходите из консили, это связано с тем что выполнение PHP скрипта было привязано к вашей сессии как к клиенту. По идее это должно залечиваться использованием в PHP скрипте строки ignore_user_abort(true);, но это помогает не всегда в связи с разными настройками PHP. В таком случае используют трюк, давая PHP скрипту поток /dev/null, который он будет считать клиентом и не прекратит работу при вашем выходе из консоли.
Амперсанд в конце обязателен, чтобы вы могли нажать Ctrl+C и вернуться в консоль а процесс остался в памяти. Или можно воспользоваться утилитой nohup.
В добавок, будет полезно знать, что и на windows-платформе можно запускать выполнение скрипта из консоли
Если всё делать правильно, то лучше воспользоваться утилитой Supervisor: A Process Control System она следит за работой процесса, в случае необходимости запускает его, осуществляет регистрацию падений. Отличная правильная вещь, когда вы делаете серьезный проект и в вашем распоряжении выделенный сервер или хотя бы VDS.
Размещение и запуск веб-сокет сервера на хостинге
Чат на веб-сокетах
Взглянув один раз на код опубликованного в прошлой статье веб-чата я решил улучшить его снабдив новым интересным функционалом, заодно разобрав подробности реализации.
Подсчёт количества пользователей он-лайн
Тоже очень простая задача, для этого в классе websocketserver_class заводим приватную переменную $online, задаём значение 0 в конструкторе.
При подключении пользователя
при отключении соответственно
На стороне клиента я делаю проверку на наличие маркера и записываю значение после двоеточия в соответствующее поле на html странице клиента.
Автоматический запуск
Для чата автоматический запуск может быть необходим просто для того, чтобы администратору не нужно было постоянно следить за тем, поднялся ws после перезагрузки Apache или нет. Т.к. скрипт ws-сервер чата простой, то достаточно реализовать при запуске чат клиента обращение по AJAX к скрипту сервера который всегда инициирует проверку состояния и осуществляет запуск если ws-сервер чата не запущен. Но это применимо только для простых приложений типа чата. В случае с моим игровым проектом, я не использую автозапуск, т.к. там процесс запуска сложен сам по себе и может занимать до 40 секунд времени, поскольку игровые карты загружаются в память. Также к игровому проекту совершенно другой подход, при котором состояние игры должно мониториться более тщательно администратором и в случае проблем отправляться e-mail уведомление.
Как реализовать автозапуск ws-сервера? Реализуется простая функция на javascript на стороне клиента wsserverrun(), которая каждый раз при загрузке клиента делает AJAX-запрос к серверному скрипту wsstart.php отвечающему за запуск ws, который запускает ws в случае, если он отключен.
Скрипт wsstart.php очень простой, рекомендую ознакомиться с ним самостоятельно.
Клиент на html+JavaScript для веб-сокет соединения
Для тестирования скрипта сокет-сервера нам понадобится несколько клиентов, один из них telnet (я использовал Putty), второй, веб-сокет клиент, написанный на html+JavaScript. Вы можете не устанавливать себе Telnet, его я использовал исключительно, чтобы разобраться в том, что отправляет сервер и почему я не могу это увидеть в браузере. Приведу код веб-сокет клиента, написанного на html+JavaScript.
Код сокет-сервера на PHP я приводить не буду, т.к. он есть в архиве и снабжен комментариями. Я искусственно ограничил время работы приёмки соединений 100 секундами, чтобы скрипт он не оставался в памяти, закрывал все соединения и не приходилось при внесении в него изменений постоянно перезагружать Денвер. При чем, по прошествии 100 секунд, скрипт не прекратит свою работу, а будет ждать подключения, после которого его работа будет завершена, а все сокеты благополучно закрыты. Также, для тестирования, я использую localhost и порт 889.
Файлы из архива можно поместить в корневую папку localhost Денвера. Алгоритм тестирования:
По telnet должен придти ответ “Hello, Client!”, что свидетельствует о том, что всё работает в штатном режиме и связь с сервером двухсторонняя.
Ответ сокет сервера при попытке подключения по Telnet
При тестировании на локальном компьютере используя Денвер, всегда убеждайтесь что выполнения скрипта PHP завершено, в противном случае перезагружайте Денвер, чтобы избежать коллизий и занятых портов.
Или getting started with WebSocket PHP без phpDaemon
Здравствуйте! Простите за столь длинный заголовок, но, надеюсь, что новичкам вроде меня будет легче найти эту статью, ведь мне ничего подобного найти не удалось. Несколько недель назад я принял решение переработать игровой клиент и сервер своей игры Growing Crystals с AJAX, на WebSocket, но всё оказалось не просто непросто, а очень сложно. Поэтому я и решил написать статью, которая бы помогла самым что ни на есть начинающим разработчикам на WebSocket + PHP сэкономить несколько дней времени, максимально подробно объясняя каждый свой шаг по настройке и запуску первого WebSocket скрипта на PHP.
Что у меня есть: Денвер на локальной машине, на нём я веду разработку проекта и дешевый PHP хостинг, на котором я публикую свой проект для того, чтобы получить обратную связь от Интернет-пользователей.
Что я хочу: Без установки phpDaemon (phpd), NodeJS и прочих вещей на локальную машину и хостинг, продолжить разработку своего проекта, но теперь с WebSocket, в этой статье разберем простой WebSocket эхо сервер.
Чего я не хочу: Говоря о NodeJS, не хочется переписывать серерную логику с PHP на другой язык, тем более устанавливать NodeJS, хотя и люблю JavaScript больше чем PHP.
Важно: не путайте демона написанного на php, с фреймворком асинхронных приложений phpDaemon. Который, конечно же, обязательно потребуется в случае развития проекта и многократного роста нагрузки на хостинг. Но для начала работы с WebSocket на дешевом хостинге можно обойтись и без него.
Метод бесконечного выполнения PHP скрипта запуск из браузера
Инструкция по разворачиванию системы ws server admin panel v.0.3.
Система пригодна для разворачивая как в корневом каталоге домена так и в каталоге любой степени вложенности, единственное что изменяется, так это пути в конфигах.
Использование в Девере на localhost
Рассмотрю пример конфигурационных файлов для использования на localhost (они уже прописаны в архиве и пригодны для запуска на localhost).
- Распаковываем содержимое архива в каталог Денвера w:\home\localhost\www\
- Открываем в редакторе файл w:\home\localhost\www\chat\ws\wsadmin.js. Единственное, что нужно в него прописать это адреса скрипта панели управления, лог-файла и файла ошибок. Для localhost они выглядят следующим образом.
Добавление веб-сокетов в Денвер
Теперь задача настроить Денвер. Мне довелось работать с разными сборками Денвера. Последняя из них Denwer3_Base_2013-06-02_a2.2.22_p5.3.13_m5.5.25_pma3.5.1_xdebug.exe, к сожалению, она и другие имеющиеся в настоящий момент сборки Денвера(дело в PHP) не поддерживают сокеты по умолчанию.
Но эта проблема решается путём поиска и установки подходящей php_sockets.dll. Для того, чтобы всё заработало, достаточно разместить dll файл в каталоге Денвера \usr\local\php5\ext\php_sockets.dll и отредактировать файл \usr\local\php5\php.ini убрав точку с запятой перед строкой
перезагрузить Денвер и всё, или почти всё: в некоторых случаях при перезагрузке Денвера может возникнуть ошибка
Теперь можете запустить файлик sockettest.php через браузер и увидеть заветную строку “WebSockets OK”, означающую что всё необходимое для работы с Web-Socket установлено и работает хорошо.
Хранение логов
В комментариях под прошлой статьёй был вопрос о том, как организовать логирование чата в БД. Реализация этого очень проста, единственное, что вместо запроса INSERT в БД я сохраняю данные в лог-файл. Для этого я написал простую функцию chatlogmsg($msg), которая сохраняет всё в отдельный файл chatlog.html.
и добавил её во все необходимые места кода. На всякий случай в лог-файл я решил записывать еще и ip-адреса и порты клиентов.
В итоге лог файл выглядит следующим образом
[2014.08.04-00:29:46](127.0.0.1:50183)Мистер Слива вошел в чат
[2014.08.04-00:30:01](127.0.0.1:50142)[00:30] Мистер Огурец: Всем привет и пока
[2014.08.04-00:30:20](127.0.0.1:50183)Мистер Слива покинул чат
Или как работать с WebSocket на простом PHP хостинге
Браузерная панель управления ws сервером
Разработаю простую систему управления и мониторинга демона. Она очень проста и состоит из нескольких файлов echowsadmin.html (панель администратора), echowsadmin.js (логика панели администратора), echowsadmin.php (логика управления ws echo сервером). Разработать эту систему оказалось на удивление просто, я потратил не более 1го часа своего времени.
Панель управления демоном
Очевидно, что можно много чего улучшить:
- Улучшить представление и сделать более детальной информацию о статусе ws сервера
- Добавить вывод в лог более подробной информации о занимаемой памяти и количества текущих соединений
- Сделать проверку на операционную систему и разработать версию для Денвера и Windows
- Сделать авторизацию
Но, в моём случае стояла задача сделать себе простенький инструмент мониторинга состояния демона через веб. Кстати, эту штуку слегка модифицировав можно использовать для мониторинга любого демона, а не только ws.
А что касается реализации для ОС Windows, то все места где pid можно обходить проверкой и таким образом обеспечиь запускаемость.
А запущен демон или нет достаточно проверять просто наличием pid файла.
Пока я был на выходных, забыл выключить ws echo сервер, в итоге время его жизни составило 233774 секунд, т.е. где-то около 3х суток, занимаемая память так и осталась около 0.1%, что говорит о том, что решение имеет право на жизнь.
Отзывы, комментарии всегда приветствуются. Продолжу развивать тему и в следующей статье реализую простенький чат как и обещал.
After many non-sleep nights I got the most simple multi-client server written in PHP that really works. Ctrl+C and Ctrl+V. use as command line to test it. Enjoy it.
ini_set ( 'error_reporting' , E_ALL ^ E_NOTICE );
ini_set ( 'display_errors' , 1 );
// Set time limit to indefinite execution
set_time_limit ( 0 );
// Set the ip and port we will listen on
$address = '10.203.9.67' ;
$port = 6901 ;
// Create a TCP Stream socket
$sock = socket_create ( AF_INET , SOCK_STREAM , 0 );
// Bind the socket to an address/port
socket_bind ( $sock , $address , $port ) or die( 'Could not bind to address' );
// Start listening for connections
socket_listen ( $sock );
// Non block socket type
socket_set_nonblock ( $sock );
// Loop continuously
while ( true )
<
unset( $read );
if ( count ( $client ))
<
foreach ( $client AS $k => $v )
<
$read [ $j ] = $v ;
if ( $newsock = @ socket_accept ( $sock ))
<
if ( is_resource ( $newsock ))
<
socket_write ( $newsock , " $j >" , 2 ). chr ( 0 );
echo "New client connected $j " ;
$client [ $j ] = $newsock ;
if ( count ( $client ))
<
foreach ( $client AS $k => $v )
<
if (@ socket_recv ( $v , $string , 1024 , MSG_DONTWAIT ) === 0 )
<
unset( $client [ $k ]);
// Close the master sockets
socket_close ( $sock );
?>
I've been using the ICMP Checksum calculation function written by Khaless [at] bigpond [dot] com. But when having an odd length of data, it failed, so I made my own instead, which adds a 0 if the data length is odd:
function icmpChecksum ( $data )
// Add a 0 to the end of the data, if it's an "odd length"
if ( strlen ( $data )% 2 )
$data .= "\x00" ;
// Let PHP do all the dirty work
$bit = unpack ( 'n*' , $data );
$sum = array_sum ( $bit );
// Stolen from: Khaless [at] bigpond [dot] com
// The code from the original ping program:
// sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
// sum += (sum >> 16); /* add carry */
// which also works fine, but it seems to me that
// Khaless will work on large data.
while ( $sum >> 16 )
$sum = ( $sum >> 16 ) + ( $sum & 0xffff );
Wake on Lan , working ok without configurations, and some features
function wake_on_lan ( $mac , $addr = false , $port = 7 ) //Usage
// $addr:
// You will send and broadcast tho this addres.
// Normaly you need to use the 255.255.255.255 adres, so i made it as default. So you don't need
// to do anything with this.
// Since 255.255.255.255 have permission denied problems you can use addr=false to get all broadcast address from ifconfig command
// addr can be array with broadcast IP values
// $mac:
// You will WAKE-UP this WOL-enabled computer, you need to add the MAC-addres here.
// Mac can be array too
//
//Return
// TRUE: When socked was created succesvolly and the message has been send.
// FALSE: Something went wrong
//
//Example 1
// When the message has been send you will see the message "Done. "
// if ( wake_on_lan('00:00:00:00:00:00'))
// echo 'Done. ';
// else
// echo 'Error while sending';
//
if ( $addr === false ) exec ( "ifconfig | grep Bcast | cut -d \":\" -f 3 | cut -d \" \" -f 1" , $addr );
$addr = array_flip ( array_flip ( $addr ));
>
if( is_array ( $addr )) $last_ret = false ;
for ( $i = 0 ; $i < count ( $ret ); $i ++)
if ( $ret [ $i ]!== false )
$last_ret = wake_on_lan ( $mac , $ret [ $i ], $port );
return( $last_ret );
>
if ( is_array ( $mac )) $ret =array();
foreach( $mac as $k => v )
$ret [ $k ]= wake_on_lan ( $v , $addr , $port );
return( $ret );
>
//Check if it's an real MAC-addres and split it into an array
$mac = strtoupper ( $mac );
if (! preg_match ( "/([A-F0-9][-:])[A-F0-9]/" , $mac , $maccheck ))
return false ;
$addr_byte = preg_split ( "/[-:]/" , $maccheck [ 0 ]);
//Creating hardware adress
$hw_addr = '' ;
for ( $a = 0 ; $a < 6 ; $a ++) //Changing mac adres from HEXEDECIMAL to DECIMAL
$hw_addr .= chr ( hexdec ( $addr_byte [ $a ]));
//Create package data
$msg = str_repeat ( chr ( 255 ), 6 );
for ( $a = 1 ; $a $msg .= $hw_addr ;
//Sending data
if ( function_exists ( 'socket_create' )) //socket_create exists
$sock = socket_create ( AF_INET , SOCK_DGRAM , SOL_UDP ); //Can create the socket
if ( $sock ) $sock_data = socket_set_option ( $sock , SOL_SOCKET , SO_BROADCAST , 1 ); //Set
if ( $sock_data ) $sock_data = socket_sendto ( $sock , $msg , strlen ( $msg ), 0 , $addr , $port ); //Send data
if ( $sock_data ) socket_close ( $sock ); //Close socket
unset( $sock );
return( true );
>
>
>
@ socket_close ( $sock );
unset( $sock );
>
$sock = fsockopen ( "udp://" . $addr , $port );
if( $sock ) $ret = fwrite ( $sock , $msg );
fclose ( $sock );
>
if( $ret )
return( true );
return( false );
>
?>
Planning on sending integer values through as socket, I was surprised to find PHP only supports sending strings.
I came to the conclusion the only way to do it would be to create a string that would evaluate to the same byte values as the integer I wanted to send. So (after much messing about) I created a couple of functions: one to create this 'string' and one to convert a received value back to an integer.
//Converts an integer to 'byte array' (string), default to 4 'bytes' (chars)
function int2string ( $int , $numbytes = 4 )
$str = "" ;
for ( $i = 0 ; $i < $numbytes ; $i ++) $str .= chr ( $int % 256 );
$int = $int / 256 ;
>
return $str ;
>
//Converts a 'byte array' (string) to integer
function string2int ( $str )
$numbytes = strlen ( $str );
$int = 0 ;
for ( $i = 0 ; $i < $numbytes ; $i ++) $int += ord ( $str [ $i ]) * pow ( 2 , $i * 8 );
>
return $int ;
>
//Example
echo int2string ( 16705 , 2 ); // 16-bit integer converts to two bytes: 65, 65; which in turn is 'AA'
echo string2int ( 'AA' ); //back the other way
?>
A multicast server can be written badly as follows:
$bc_string = "Hello World!";
$sock = socket_create(AF_INET, SOCK_DGRAM, 0);
$opt_ret = socket_set_option($sock, 1, 6, TRUE);
$send_ret = socket_sendto($sock, $bc_string, strlen($bc_string), 0, '230.0.0.1', 4446);
Checking the return types is needed, but this does allow for you to multicast from php code.
NOTE! If you are trying to send a broadcast-message using this code you _may_ get a "Permission denied"-Error at socket_connect, even if you are running this as root on a linux box.
$sock = socket_create ( AF_INET , SOCK_DGRAM , SOL_UDP );
socket_connect ( $sock , "255.255.255.255" , 10000 );
socket_set_option ( $sock , SOL_SOCKET , SO_BROADCAST , 1 );
$buf = "Hello World!" ;
socket_write ( $sock , $buf , strlen ( $buf ));
socket_close ( $sock );
?>
The only workaround for this is to get the broadcast address of the interface and walk through all IPs with a for-loop.
I have searched long and hard for a ping script that does NOT use EXEC() or SYSTEM(). So far, I have found nothing, so I decided to write my own, which was a task to say the least.
First off, I would like to thank Khaless for their checksum function, converting it from C looked like a task in itself.
Here is the class I wrote
class Net_Ping
var $icmp_socket ;
var $request ;
var $request_len ;
var $reply ;
var $errstr ;
var $time ;
var $timer_start_time ;
function Net_Ping ()
$this -> icmp_socket = socket_create ( AF_INET , SOCK_RAW , 1 );
socket_set_block ( $this -> icmp_socket );
>
function ip_checksum ( $data )
for( $i = 0 ; $i < strlen ( $data ); $i += 2 )
if( $data [ $i + 1 ]) $bits = unpack ( 'n*' , $data [ $i ]. $data [ $i + 1 ]);
else $bits = unpack ( 'C*' , $data [ $i ]);
$sum += $bits [ 1 ];
>
while ( $sum >> 16 ) $sum = ( $sum & 0xffff ) + ( $sum >> 16 );
$checksum = pack ( 'n1' ,~ $sum );
return $checksum ;
>
function start_time ()
$this -> timer_start_time = microtime ();
>
function get_time ( $acc = 2 )
// format start time
$start_time = explode ( " " , $this -> timer_start_time );
$start_time = $start_time [ 1 ] + $start_time [ 0 ];
// get and format end time
$end_time = explode ( " " , microtime ());
$end_time = $end_time [ 1 ] + $end_time [ 0 ];
return number_format ( $end_time - $start_time , $acc );
>
function Build_Packet ()
$data = "abcdefghijklmnopqrstuvwabcdefghi" ; // the actual test data
$type = "\x08" ; // 8 echo message; 0 echo reply message
$code = "\x00" ; // always 0 for this program
$chksm = "\x00\x00" ; // generate checksum for icmp request
$id = "\x00\x00" ; // we will have to work with this later
$sqn = "\x00\x00" ; // we will have to work with this later
// now we need to change the checksum to the real checksum
$chksm = $this -> ip_checksum ( $type . $code . $chksm . $id . $sqn . $data );
// now lets build the actual icmp packet
$this -> request = $type . $code . $chksm . $id . $sqn . $data ;
$this -> request_len = strlen ( $this -> request );
>
function Ping ( $dst_addr , $timeout = 5 , $percision = 3 )
// lets catch dumb people
if ((int) $timeout if ((int) $percision
// set the timeout
socket_set_option ( $this -> icmp_socket ,
SOL_SOCKET , // socket level
SO_RCVTIMEO , // timeout option
array(
"sec" => $timeout , // Timeout in seconds
"usec" => 0 // I assume timeout in microseconds
)
);
if ( $dst_addr )
if (@ socket_connect ( $this -> icmp_socket , $dst_addr , NULL ))
> else $this -> errstr = "Cannot connect to $dst_addr " ;
return FALSE ;
>
$this -> Build_Packet ();
$this -> start_time ();
socket_write ( $this -> icmp_socket , $this -> request , $this -> request_len );
if (@ socket_recv ( $this -> icmp_socket , & $this -> reply , 256 , 0 ))
$this -> time = $this -> get_time ( $percision );
return $this -> time ;
> else $this -> errstr = "Timed out" ;
return FALSE ;
>
> else $this -> errstr = "Destination address not specified" ;
return FALSE ;
>
>
>
$ping = new Net_Ping ;
$ping -> ping ( "www.google.ca" );
if ( $ping -> time )
echo "Time: " . $ping -> time ;
else
echo $ping -> errstr ;
After many non-sleep nights I got the most simple multi-client server written in PHP that really works. Ctrl+C and Ctrl+V. use as command line to test it. Enjoy it.
ini_set ( 'error_reporting' , E_ALL ^ E_NOTICE );
ini_set ( 'display_errors' , 1 );
// Set time limit to indefinite execution
set_time_limit ( 0 );
// Set the ip and port we will listen on
$address = '10.203.9.67' ;
$port = 6901 ;
// Create a TCP Stream socket
$sock = socket_create ( AF_INET , SOCK_STREAM , 0 );
// Bind the socket to an address/port
socket_bind ( $sock , $address , $port ) or die( 'Could not bind to address' );
// Start listening for connections
socket_listen ( $sock );
// Non block socket type
socket_set_nonblock ( $sock );
// Loop continuously
while ( true )
<
unset( $read );
if ( count ( $client ))
<
foreach ( $client AS $k => $v )
<
$read [ $j ] = $v ;
if ( $newsock = @ socket_accept ( $sock ))
<
if ( is_resource ( $newsock ))
<
socket_write ( $newsock , " $j >" , 2 ). chr ( 0 );
echo "New client connected $j " ;
$client [ $j ] = $newsock ;
if ( count ( $client ))
<
foreach ( $client AS $k => $v )
<
if (@ socket_recv ( $v , $string , 1024 , MSG_DONTWAIT ) === 0 )
<
unset( $client [ $k ]);
// Close the master sockets
socket_close ( $sock );
?>
I've been using the ICMP Checksum calculation function written by Khaless [at] bigpond [dot] com. But when having an odd length of data, it failed, so I made my own instead, which adds a 0 if the data length is odd:
function icmpChecksum ( $data )
// Add a 0 to the end of the data, if it's an "odd length"
if ( strlen ( $data )% 2 )
$data .= "\x00" ;
// Let PHP do all the dirty work
$bit = unpack ( 'n*' , $data );
$sum = array_sum ( $bit );
// Stolen from: Khaless [at] bigpond [dot] com
// The code from the original ping program:
// sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
// sum += (sum >> 16); /* add carry */
// which also works fine, but it seems to me that
// Khaless will work on large data.
while ( $sum >> 16 )
$sum = ( $sum >> 16 ) + ( $sum & 0xffff );
Wake on Lan , working ok without configurations, and some features
function wake_on_lan ( $mac , $addr = false , $port = 7 ) //Usage
// $addr:
// You will send and broadcast tho this addres.
// Normaly you need to use the 255.255.255.255 adres, so i made it as default. So you don't need
// to do anything with this.
// Since 255.255.255.255 have permission denied problems you can use addr=false to get all broadcast address from ifconfig command
// addr can be array with broadcast IP values
// $mac:
// You will WAKE-UP this WOL-enabled computer, you need to add the MAC-addres here.
// Mac can be array too
//
//Return
// TRUE: When socked was created succesvolly and the message has been send.
// FALSE: Something went wrong
//
//Example 1
// When the message has been send you will see the message "Done. "
// if ( wake_on_lan('00:00:00:00:00:00'))
// echo 'Done. ';
// else
// echo 'Error while sending';
//
if ( $addr === false ) exec ( "ifconfig | grep Bcast | cut -d \":\" -f 3 | cut -d \" \" -f 1" , $addr );
$addr = array_flip ( array_flip ( $addr ));
>
if( is_array ( $addr )) $last_ret = false ;
for ( $i = 0 ; $i < count ( $ret ); $i ++)
if ( $ret [ $i ]!== false )
$last_ret = wake_on_lan ( $mac , $ret [ $i ], $port );
return( $last_ret );
>
if ( is_array ( $mac )) $ret =array();
foreach( $mac as $k => v )
$ret [ $k ]= wake_on_lan ( $v , $addr , $port );
return( $ret );
>
//Check if it's an real MAC-addres and split it into an array
$mac = strtoupper ( $mac );
if (! preg_match ( "/([A-F0-9][-:])[A-F0-9]/" , $mac , $maccheck ))
return false ;
$addr_byte = preg_split ( "/[-:]/" , $maccheck [ 0 ]);
//Creating hardware adress
$hw_addr = '' ;
for ( $a = 0 ; $a < 6 ; $a ++) //Changing mac adres from HEXEDECIMAL to DECIMAL
$hw_addr .= chr ( hexdec ( $addr_byte [ $a ]));
//Create package data
$msg = str_repeat ( chr ( 255 ), 6 );
for ( $a = 1 ; $a $msg .= $hw_addr ;
//Sending data
if ( function_exists ( 'socket_create' )) //socket_create exists
$sock = socket_create ( AF_INET , SOCK_DGRAM , SOL_UDP ); //Can create the socket
if ( $sock ) $sock_data = socket_set_option ( $sock , SOL_SOCKET , SO_BROADCAST , 1 ); //Set
if ( $sock_data ) $sock_data = socket_sendto ( $sock , $msg , strlen ( $msg ), 0 , $addr , $port ); //Send data
if ( $sock_data ) socket_close ( $sock ); //Close socket
unset( $sock );
return( true );
>
>
>
@ socket_close ( $sock );
unset( $sock );
>
$sock = fsockopen ( "udp://" . $addr , $port );
if( $sock ) $ret = fwrite ( $sock , $msg );
fclose ( $sock );
>
if( $ret )
return( true );
return( false );
>
?>
Planning on sending integer values through as socket, I was surprised to find PHP only supports sending strings.
I came to the conclusion the only way to do it would be to create a string that would evaluate to the same byte values as the integer I wanted to send. So (after much messing about) I created a couple of functions: one to create this 'string' and one to convert a received value back to an integer.
//Converts an integer to 'byte array' (string), default to 4 'bytes' (chars)
function int2string ( $int , $numbytes = 4 )
$str = "" ;
for ( $i = 0 ; $i < $numbytes ; $i ++) $str .= chr ( $int % 256 );
$int = $int / 256 ;
>
return $str ;
>
//Converts a 'byte array' (string) to integer
function string2int ( $str )
$numbytes = strlen ( $str );
$int = 0 ;
for ( $i = 0 ; $i < $numbytes ; $i ++) $int += ord ( $str [ $i ]) * pow ( 2 , $i * 8 );
>
return $int ;
>
//Example
echo int2string ( 16705 , 2 ); // 16-bit integer converts to two bytes: 65, 65; which in turn is 'AA'
echo string2int ( 'AA' ); //back the other way
?>
A multicast server can be written badly as follows:
$bc_string = "Hello World!";
$sock = socket_create(AF_INET, SOCK_DGRAM, 0);
$opt_ret = socket_set_option($sock, 1, 6, TRUE);
$send_ret = socket_sendto($sock, $bc_string, strlen($bc_string), 0, '230.0.0.1', 4446);
Checking the return types is needed, but this does allow for you to multicast from php code.
NOTE! If you are trying to send a broadcast-message using this code you _may_ get a "Permission denied"-Error at socket_connect, even if you are running this as root on a linux box.
$sock = socket_create ( AF_INET , SOCK_DGRAM , SOL_UDP );
socket_connect ( $sock , "255.255.255.255" , 10000 );
socket_set_option ( $sock , SOL_SOCKET , SO_BROADCAST , 1 );
$buf = "Hello World!" ;
socket_write ( $sock , $buf , strlen ( $buf ));
socket_close ( $sock );
?>
The only workaround for this is to get the broadcast address of the interface and walk through all IPs with a for-loop.
I have searched long and hard for a ping script that does NOT use EXEC() or SYSTEM(). So far, I have found nothing, so I decided to write my own, which was a task to say the least.
First off, I would like to thank Khaless for their checksum function, converting it from C looked like a task in itself.
Here is the class I wrote
class Net_Ping
var $icmp_socket ;
var $request ;
var $request_len ;
var $reply ;
var $errstr ;
var $time ;
var $timer_start_time ;
function Net_Ping ()
$this -> icmp_socket = socket_create ( AF_INET , SOCK_RAW , 1 );
socket_set_block ( $this -> icmp_socket );
>
function ip_checksum ( $data )
for( $i = 0 ; $i < strlen ( $data ); $i += 2 )
if( $data [ $i + 1 ]) $bits = unpack ( 'n*' , $data [ $i ]. $data [ $i + 1 ]);
else $bits = unpack ( 'C*' , $data [ $i ]);
$sum += $bits [ 1 ];
>
while ( $sum >> 16 ) $sum = ( $sum & 0xffff ) + ( $sum >> 16 );
$checksum = pack ( 'n1' ,~ $sum );
return $checksum ;
>
function start_time ()
$this -> timer_start_time = microtime ();
>
function get_time ( $acc = 2 )
// format start time
$start_time = explode ( " " , $this -> timer_start_time );
$start_time = $start_time [ 1 ] + $start_time [ 0 ];
// get and format end time
$end_time = explode ( " " , microtime ());
$end_time = $end_time [ 1 ] + $end_time [ 0 ];
return number_format ( $end_time - $start_time , $acc );
>
function Build_Packet ()
$data = "abcdefghijklmnopqrstuvwabcdefghi" ; // the actual test data
$type = "\x08" ; // 8 echo message; 0 echo reply message
$code = "\x00" ; // always 0 for this program
$chksm = "\x00\x00" ; // generate checksum for icmp request
$id = "\x00\x00" ; // we will have to work with this later
$sqn = "\x00\x00" ; // we will have to work with this later
// now we need to change the checksum to the real checksum
$chksm = $this -> ip_checksum ( $type . $code . $chksm . $id . $sqn . $data );
// now lets build the actual icmp packet
$this -> request = $type . $code . $chksm . $id . $sqn . $data ;
$this -> request_len = strlen ( $this -> request );
>
function Ping ( $dst_addr , $timeout = 5 , $percision = 3 )
// lets catch dumb people
if ((int) $timeout if ((int) $percision
// set the timeout
socket_set_option ( $this -> icmp_socket ,
SOL_SOCKET , // socket level
SO_RCVTIMEO , // timeout option
array(
"sec" => $timeout , // Timeout in seconds
"usec" => 0 // I assume timeout in microseconds
)
);
if ( $dst_addr )
if (@ socket_connect ( $this -> icmp_socket , $dst_addr , NULL ))
> else $this -> errstr = "Cannot connect to $dst_addr " ;
return FALSE ;
>
$this -> Build_Packet ();
$this -> start_time ();
socket_write ( $this -> icmp_socket , $this -> request , $this -> request_len );
if (@ socket_recv ( $this -> icmp_socket , & $this -> reply , 256 , 0 ))
$this -> time = $this -> get_time ( $percision );
return $this -> time ;
> else $this -> errstr = "Timed out" ;
return FALSE ;
>
> else $this -> errstr = "Destination address not specified" ;
return FALSE ;
>
>
>
$ping = new Net_Ping ;
$ping -> ping ( "www.google.ca" );
if ( $ping -> time )
echo "Time: " . $ping -> time ;
else
echo $ping -> errstr ;
От сокета к веб-сокету
Для начала, чтобы убедиться что мне не мешают файрволлы, всё настроено правильно и связь между клиентом и сервером может быть установлена, я решил написать и протестировать небольшой PHP скрипт выполняющий роль сокет-сервера (именно сокет, а не веб-сокет!), устанавливающий соединение и отвечающий всем фразой “Hello, Client!”. Но для его тестирования нужно несколько клиентов (веб-сокет клиент, чтобы понять базовое отличие от простого сокета и обычный telnet).
Протокол веб-сокет
можно увидеть, что попытка установить соединение по протоколу веб-сокет всегда сопровождается отправлением клиентом заголовка с обязательным соблюдением формата протокола WebScoket. Тег pre для вывода заголовков выбран не случайно, т.к. в заголовках большое значение играют переносы строк. Такие заголовки я получаю от браузеров, когда пытаюсь подключиться к нашему ws://127.0.0.1:889.
Специально для тех, кто ищет хостинг для запуска своего проекта на веб-сокетах обсуждение по ссылке.
Не совсем ожидаемо для меня, хотя и совершенно закономерно, в моём блоге самой популярной стала тема, которой я посвятил больше всего времени — работа с веб-сокетами (предыдущая статья про чат). Сегодня я опубликую новый усовершенствованный инструмент управления веб-сокетами Downloads, ws server admin panel v.0.3., который будет полностью управляем через веб панель как под Windows, так и под *nix. Расскажу о том, с какими я столкнулся проблемами, как их лечил и предоставлю несколько полезных рецептов посвященных работе с веб-сокетами на PHP.
Что нового в ws server admin panel v.0.3.
Также реализовано получение данных о процессе ws под ОС Windows, пока удалось протестировать только под Windows 7, но, если окажется что не работает под Windows 8, дайте об этом знать и я выпущу обновление. Получение данных о процессе под ОС Windows было необходимо чтобы экономить время разработчика на перезапуск процесса в время отладки.
Исправлена проблема запуска php скриптов из Денвера. Долго не мог разобраться в чём проблема при работе под ОС Windows, оказалось что правильный путь php в Денвере выглядит следующим образом w:\usr\bin\php5.exe. Если использовать другой путь w:\usr\local\php5\php.exe то запуск будет происходить далеко не каждый раз. Однако теперь в процессах появляется не php.exe а сразу несколько процессов php5.exe и php-cgi.exe.
Единственное что я не реализовывал, это проверку занятости портов, и проверку на наличие запущенных процессов при отсутствии pid-файла, поскольку для разработки подразумевается что вы знаете какой порт использовать и не будете удалять pid-файл уже запущенного процесса. Но если вы случайно удалили pid-файл запущенного процесса, то просто перезагрузите веб-сервер целиком. Реализацию принудительно выключения процесса у которого нет pid-файла я пока не рассматриваю.
Читайте также: