Наконец доделал свою задачу. Радости полные штаны ). Задача: 1. онлайн-консультант на сайт для улучшения сервиса сайта 2. через веб-сокеты, чтоб добится получения вопросов/ответов в реальном времени, а так же без большой нагрузки на сервер. 3. консультант только один, которые помогает другим пользователям
Поочереди расскажу про файлы, так как тема PHP-сети, то начну со скрипта для работы сокетов
Код | error_reporting(E_ALL); //Выводим все ошибки и предупреждения set_time_limit(0); //Позволить сценарию зависнуть вокруг ожидания подключений ob_implicit_flush();
$host = 'domen'; // домен для подключения $port = '9001'; // порт (боковая дверь для входа) $null = NULL; // пригодится далее в коде $clients = array(); // подключенные пользователи $root_resource = 0; // ссылка на ресурс рута, как упоминал есть только один консультант, чтоб не выискаивать его среди всех подключенных клиентов заносим ресурс сокета сюда
// создаем один раз сокет пришедшего пользователя $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); // установим опцию что на один порт могут подключатся несколько сокетов socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1); // привяжем данный сокет к определенному хосту и порту socket_bind($socket, $host, $port); // начнем слушать этот сокет socket_listen($socket);
while(true){ // возьмем клиентов кого нужно прослушать $aSocket = array($socket); foreach($clients as $val) $aSocket[] = $val["resource"]; // тут нужно взять ресурсы сокетов, куда я сохранил ранее, далее по коду будет понятней // создадим массив read, далее read может изменится, а изменится только тогда когда какой-то из пользователей что-то сделает $read = $aSocket; // послушаем какие изменения были у наших сокетов и если что-то изменилось, то массив read не будет пустым socket_select($read, $null, $null, 0); // тут создадим новое подключение текущего пользователя ко всем, когда чел. впервые соединяется, то попадает в этот блок (соединится с браузером, допустим при перезагрузки страницы) if(in_array($socket, $read)){ // сделаем новый ресурс по каторому будет общение к текущему пользователю $socket_new = socket_accept($socket); // получим заголовки с http $headerStr = socket_read($socket_new, 1024); require_once("class.SupportChat.php"); // тут подсоединяю класс, где описаны доп. ф-ии для работы $SC = new SupportChat(); // получим заголовки в нормальном виде (массив) $headers = $SC->getAHeaders($headerStr); // проверим есть ли кука и в ней запись "PHPSESSID=", чтоб из нее сделать название чата. Другого идентификатора пользователя не вижу. Если есть идеи, пишите! Этим идентификатором называю чат с данным клиентом в базе данных (далее по коду). $chat_name = !empty($headers['Cookie'])? $SC->getNameForChat($headers['Cookie']): ""; // произведем рукопожадие с текущим пользователем, чтоб браузер соединился. Если кука есть и рукопожатие произошло, то все хорошо, добавляем чела в список клиентов if( !empty($chat_name) && $SC->handshake($headers['Sec-WebSocket-Key'], $socket_new, $host, $port) ){ // проверим: был ли ранее уже подсоединен пользователь с такой же кукой. Если пользователь перезагрузил страницу, то было бы не плохо предоставить ему предыдущий сохраненый его диалог, чтоб мог к примеру перечитать. Поэтому если этот чел, со своим идентификатором уже есть, то предоставим ему данные (далее по коду) $reload_root_key = false; foreach($clients as $key=>$val){ if($val["id"] == $chat_name){ // если это рут перезагрузился (админ конечно же тоже может перезагружать страницу и ни чего не терять), то заменим ресурс if($val["resource"] == $root_resource){ $root_resource = $socket_new; $reload_root_key = true; } // удалим остаток. сокет-ресурс лучше создавать новый, а не заменять на старый. без раздницы какой сокет будет по номеру (Recourse #150 к примеру), определяем челов и их кол-во по идентификатору куки, каторая ранее приходила ... поэтому удаляем остаток unset($clients[$key]); break; } } // добавим пользователя. Имя куки, ресурс сокета и время последнего действия текущего пользователя (чтоб потом их удалять, якобы под видом не активных и быть может то что ушли уже из чата). Как по другому определить то что пользователя нет, ни как не получилось. Пробывал посылать байты на ресурс, но появлялись всякие ошибки в программе, так что решил вычислять относительно времяни, макс. 5 мин. $clients[] = array("id"=>$chat_name, "resource"=>$socket_new, "last_act"=>time()); // если это не рут, а пользователь if( $root_resource !== 0 && $root_resource !== $socket_new ){ $a_all_messages = array(); // если чата еще нет, то создадим новый (это значит что новый клиент), иначе подгрузим старые сообщения if(!$SC->isChat($chat_name)){ $SC->insertNewChat($chat_name); // если клиент новый, то уведомим рута о его приходе $response = $SC->mask(json_encode(array("type" => "came_a_new_customer", "message" => "", "chat_name" => $chat_name))); socket_write($root_resource, $response, strlen($response)); } else $a_all_messages = $SC->getArrayAllMessages($chat_name); // если есть что показать, покажем клиенту $response = $SC->mask(json_encode(array("type" =>"first_open_user", "message" =>$a_all_messages))); socket_write($socket_new, $response, strlen($response)); // почистим unset($SC, $a_all_messages, $response); } else{ // если рут перезагрузился (перезагрузка страницы) if($reload_root_key){ // тут нужно восстановить данные рута, т.к. была сделана перезагрузка страницы $response = $SC->mask(json_encode(array("type" => "first_open_root", "message" => "", "chat_name" => $SC->aGetOnlineCustomer($clients, $root_resource)))); socket_write($root_resource, $response, strlen($response)); } else // создадим рута для простоты обращения к его ресурсу сокета $root_resource = $socket_new; } // почистим unset($reload_root_key); } else echo "рукопожатие прошло не удачно\n"; // почистим unset($headers, $chat_name, $headerStr, $socket_new); } // посмотрим кто из подключенных что либо сделал, если сделал то идем дальше (если есть общие значения в массивах) if( !count(array_intersect($aSocket, $read)) ) continue; // обойдем список всех наших подключенных клиентов foreach($clients as $key=>$client){ // если это тот клиент, каторый сделал что-то в данный момент, то идем дальше if(!in_array($client["resource"], $read)) continue; // определимся кто это пишет - рут или пользователь $is_root = ($client["resource"] === $root_resource)? 1: 0; // возьмем то что написал пользователь, если какая-то херь, продолжаем цикл $temp = socket_read($client["resource"], 1024); if($temp === false) continue; require_once("class.SupportChat.php"); // подсоединим доп. ф-ии $SC = new SupportChat(); $temp = trim($SC->unmask($temp)); // приведем сообщения в нормальный вид $temp = json_decode($temp); if(empty($temp->cmd)) // наши команды, с помощью которых может реагировать continue; switch($temp->cmd){ case("send_message"): // если это обычный посыл сообщения if(!isset($temp->var1) || !isset($temp->var2)) continue; $opened_chat_name = $temp->var1; // открытый в данную секунду чат у рута (всмысле конкретно кому-то нужно писать, не в пустоту ведь писать) $message = trim($temp->var2); unset($temp); if(empty($message)) // если пустое сообщение, то продолжаем цикл continue; // если это пишет рут if($is_root){ // посмотрим есть ли вообще такой чат куда необходимо писать и то что он принадлежит не рут $send_to = 0; foreach($clients as $val){ if($val["id"] == $opened_chat_name && $client["id"] != $val["id"]){ $send_to = array("id"=>$val["id"], "resource"=>$val["resource"]); break; } } unset($opened_chat_name); // если писать не для кого, то сообщаем об этом руту (к примеру у пользователя может пройти эти 5 минут, которые говорят нам о том что он еще онлайн) if($send_to === 0){ $response = $SC->mask(json_encode(array("type" => "system", "message" => "ошибка: неизвестен получатель сообщения!"))); socket_write($root_resource, $response, strlen($response)); // есть 3 вида сообщений от программы - system, для рута - root и для пользователя - user, выше как заметили было системное оповещение } else{ $message = $SC->clearMsg($message); // почистим сообщение о мусора и если есть сслыки в сообщениях, то придадим им нормальный вид (это уже в классе смотрите) // отправим сообщение руту и собеседнику для которого было это предназнечено $response = $SC->mask(json_encode(array("type" => "root", "message" => $message, "chat_name" => $send_to["id"]))); socket_write($root_resource, $response, strlen($response)); socket_write($send_to["resource"], $response, strlen($response)); socket_getpeername($root_resource, $ip); // сохраним запись, ip-шник взяли на всякий случай, вдруг какую-то херь будет писать, чтоб могли по ip выследить данного пользователя, если вдруг менты нагрянут $SC->writeMessage($send_to["id"], $message, 1, $ip); } } else{ $message = $SC->clearMsg($message); // иначе это пишет пользователь, отправляем данному пользователю и руту $response = $SC->mask(json_encode(array("type" => "user", "message" => $message, "chat_name" => $client["id"]))); socket_write($root_resource, $response, strlen($response)); socket_write($client["resource"], $response, strlen($response)); socket_getpeername($client["resource"], $ip); // сохраним запись $SC->writeMessage($client["id"], $message, 0, $ip); // обновим время действия пользователя $clients[$key]["last_act"] = time(); } break; // получение всех сообщений от конктерного чата для рута case("get_all_messages_from_chat_name"): if(empty($temp->var1)) continue; $a_all_messages = $SC->getArrayAllMessages($temp->var1); $response = $SC->mask(json_encode(array("type" => "get_all_messages_from_chat_name", "message" => $a_all_messages, "chat_name" => ""))); socket_write($root_resource, $response, strlen($response)); break; // возьмем активные соединения и при необходимости спрячем не активные у рута на панели case("getValidConnection"): $response = $SC->mask(json_encode(array("type" => "getValidConnection", "message" => "", "chat_name" => $SC->aGetOnlineCustomer($clients, $root_resource)))); socket_write($root_resource, $response, strlen($response)); break; // если совсем вырубить чат case("power_off"): if($is_root){ foreach($clients as $key2=>$val) socket_close($val["resource"]);
unset($clients); break 3; // почему число 3? во первых выйти со switch и следующие 2 из циклов } break; } } } socket_close($socket);
|
|