Модераторы: Aliance, skyboy, MoLeX, ksnk
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> создание онлайн консультанта (сокеты), использование сокетов и т.д. 
V
    Опции темы
numerovan
Дата 25.11.2014, 01:06 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 549
Регистрация: 1.12.2007

Репутация: 2
Всего: 2



Здравствуйте, что-то ни как не могу понять до конца - как правильно реализовать онлайн консультанта.
В осн. проблемы в сокетах.

Задача следующая: заходит админ на сайт под своим аккаунтом, программа видит что он зашел и автоматом горит индикатор что админ на сайте и что можно ему писать сообщение, чат начинает работать. Допустим зашли на сайт сразу 5 клиентов, они пишут админу через чат сообщения. Админ может сразу просматривать все приходящие сообщения и выбирать в какой последовательности отвечать пользователям. У пользователей только одна прямая связь с админом. Надеюсь доходчево объяснил. Далее стандартный функционал - отправить, отобразить сообщение у админа и у того кто написал, закрыть чат, открыть снова чат и т.д.

Тестовый html
Код

<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>web-socket</title>
</head>
<script src="jquery-2.0.3.min.js" type="text/javascript"></script>
<script>
$(document).ready(function(){
    mySocket.init();
});
</script>
<script src="ws.js" type="text/javascript"></script>
<body>
    <div>
     Message:
        <br>
     <input id="sock-msg" type="text" style="width:300px">
        <input id="sock-send-butt" type="button" value="send" onClick="mySocket.sendMes()">
    </div>
    <br>
    <div>
        <input id="sock-init-butt" type="button" value="connect" onClick="mySocket.init()">
        <input id="sock-recon-butt" type="button" value="reconnect" onClick="mySocket.recon()">
        <input id="sock-disc-butt" type="button" value="disconnect" onClick="mySocket.closeCon()">
    </div>
    <br>
    <div>
        Полученные сообщения от веб-сокета:<br> 
        <div id="sock-info" style="border: 1px solid; min-height:50px"></div>
    </div>
</body>
</html>


Яваскрипт
Код

mySocket = {
    address:    "ws://localhost:9001/ws.php",
    websocket:    new Object,
    textField:    new Object,
    mesField:   new Object,
    isOpen:    false,
    showLog:    true,
    
    init: function(){
        if(this.isOpen)
            return;
        
        this.textField = $("#sock-info");
        this.mesField  = $("#sock-msg");    
        this.websocket = new WebSocket(this.address);
        
        this.websocket.onopen = function() { 
            if(mySocket.showLog) 
                console.log("Соединение установлено.");
            
            mySocket.isOpen = true;
        }
        
        this.websocket.onmessage = function(event){
            var msg = JSON.parse(event.data);
            var type = msg.type; //message type
            var umsg = msg.message; //message text
            
            if(type == 'user')
                mySocket.textField.append("<div>user: " + umsg + "</div>");
            else
                mySocket.textField.append("<div>sys: " + umsg + "</div>");
            
            if(mySocket.showLog) 
                console.log("Получены данные - " + event.data);
        };
        
        this.websocket.onerror    = function(error){
            if(mySocket.showLog)    
                console.log("Ошибка: " + error.message);
        };
        
        this.websocket.onclose = function(event){
            if(event.wasClean){
                if(mySocket.showLog)
                    console.log('Соединение закрыто чисто');
            }
            else{
                if(mySocket.showLog)
                    console.log('Обрыв соединения');
            }
            
            if(mySocket.showLog)
                console.log('Код: ' + event.code + ' причина: ' + event.reason);
        };
    },
    sendMes: function(){
        if(!this.isOpen)
            return;
        
        var text = this.mesField.val();
        text = $.trim(text);
        
        if(text == "" || text == null)
            return;
        
        this.websocket.send(text);
        this.mesField.val("");
    },
    closeCon: function(){
        if(!this.isOpen)
            return;
        
        this.websocket.close();
        this.isOpen = false;
        
        if(this.showLog)
            console.log('Соединение закрыто');
    },
    recon: function(){
        if(!this.isOpen)
            return;
        
        this.closeCon();
        this.init();
        
        if(this.showLog)
            console.log('Перезагрузка');
    }
}


и сам серверный php
Код

error_reporting(E_ALL); //Выводим все ошибки и предупреждения
set_time_limit(0);        //Позволить сценарию зависнуть вокруг ожидания подключений
ob_implicit_flush();

$host = 'localhost'; //host
$port = '9001'; //port
$null = NULL; //null var

$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);

//create & add listning socket to the list
$clients = array($socket);

//start endless loop, so that our script doesn't stop
while(true){
    
    //manage multipal connections
    $changed = $clients;
    
    //returns the socket resources in $changed array
    socket_select($changed, $null, $null, 0, 10);
    
    //check for new socket
    if(in_array($socket, $changed)){
        $socket_new = socket_accept($socket); //accpet new socket
        $clients[] = $socket_new; //add socket to client array
        
        $header = socket_read($socket_new, 1024); //получим заголовки
        perform_handshaking($header, $socket_new, $host, $port); //запишем свои заголовки в сокет и сделаем рукопожатие
        
        //socket_getpeername($socket_new, $ip); // узнаем удаленный ip-адрес и записываем его в переменную $ip
        
        //$response = mask(json_encode(array('type'=>'system', 'message'=>$ip . ' connected (' . $socket_new . ')'))); //prepare json data
        //send_message($response); //notify all users about new connection
        
        //make room for new socket
        $found_socket = array_search($socket, $changed);
        unset($changed[$found_socket]);
    }    
    
    //loop through all connected sockets
    foreach($changed as $changed_socket){    
        //check for any incomming data
        while(socket_recv($changed_socket, $buf, 1024, 0) >= 1){
            $text = unmask($buf); //unmask data
            
            if($user_message == 'выкл'){
                echo "сработал выход\n";
                break;
            }
            /*if($user_message == 'выключение'){
                echo "сработало выключение\n";
                socket_close($socket_new);
                break 2;
            }*/
            
            //prepare data to be sent to client
            $response_text = mask(json_encode(array('type'=>'user', 'message'=>$text)));
            send_message($response_text); //send data
            break 2; //exist this loop
        }
        
        /*$buf = @socket_read($changed_socket, 1024, PHP_NORMAL_READ);
        
        if($buf === false){ // check disconnected client
            // remove client for $clients array
            $found_socket = array_search($changed_socket, $clients);
            socket_getpeername($changed_socket, $ip);
            unset($clients[$found_socket]);
            
            //notify all users about disconnected connection
            $response = mask(json_encode(array('type'=>'system', 'message'=>$ip . ' disconnected (' . $socket_new . ')')));
            send_message($response);
        }*/
    }
}
// close the listening socket
socket_close($sock);

function send_message($msg){
    global $clients;
    
    foreach($clients as $changed_socket)
        @socket_write($changed_socket, $msg, strlen($msg));
    
    return true;
}
//Unmask incoming framed message
function unmask($text) {
    $length = ord($text[1]) & 127;
    if($length == 126) {
        $masks = substr($text, 4, 4);
        $data = substr($text, 8);
    }
    elseif($length == 127) {
        $masks = substr($text, 10, 4);
        $data = substr($text, 14);
    }
    else {
        $masks = substr($text, 2, 4);
        $data = substr($text, 6);
    }
    $text = "";
    for ($i = 0; $i < strlen($data); ++$i) {
        $text .= $data[$i] ^ $masks[$i%4];
    }
    return $text;
}
//Encode message for transfer to client.
function mask($text){
    $b1 = 0x80 | (0x1 & 0x0f);
    $length = strlen($text);
    
    if($length <= 125)
        $header = pack('CC', $b1, $length);
    elseif($length > 125 && $length < 65536)
        $header = pack('CCn', $b1, 126, $length);
    elseif($length >= 65536)
        $header = pack('CCNN', $b1, 127, $length);
    
    return $header . $text;
}
//handshake new client.
function perform_handshaking($receved_header, $client_conn, $host, $port){
    $headers = array();
    $lines = preg_split("/\r\n/", $receved_header);
    foreach($lines as $line){
        $line = chop($line);
        
        if(preg_match('/\A(\S+): (.*)\z/', $line, $matches))
            $headers[$matches[1]] = $matches[2];
    }

    $secKey = $headers['Sec-WebSocket-Key'];
    $secAccept = base64_encode(pack('H*', sha1($secKey . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')));
    //hand shaking header
    $upgrade  = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" .
    "Upgrade: websocket\r\n" .
    "Connection: Upgrade\r\n" .
    "WebSocket-Origin: " . $host . "\r\n" .
    "WebSocket-Location: ws://" . $host . ":" . $port . "/ws.php\r\n".
    "Sec-WebSocket-Accept:" . $secAccept . "\r\n\r\n";
    socket_write($client_conn, $upgrade, strlen($upgrade));
}

При таких вот программах (файлах) происходит следующее: сколько открывается страниц с этим чатом сколько и соединений открывается, если один пишет, то у всех отображается, хотя это конечно можно спокойно попдправить в функции send_message($msg)

Помогите более правильно направить эти программы, а то чет голова уже не работает у меня.

Не понятные вопросы для меня следующие:
1. Необходимо вычислять открытые сокеты, и ориентируясь на эти каналы (сокеты) писать пользователям ... всмысле вычислять пользователей по сокетам ? 
2. Не нужно же для каждого пользователя открывать отдельный порт, да ?
3. Переписку может лучше в сессию записывать, а не в базу данных, как думаете ?
PM MAIL   Вверх
numerovan
Дата 29.11.2014, 23:59 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 549
Регистрация: 1.12.2007

Репутация: 2
Всего: 2



Наконец доделал свою задачу. Радости полные штаны ).
Задача:
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);

PM MAIL   Вверх
numerovan
Дата 30.11.2014, 00:14 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 549
Регистрация: 1.12.2007

Репутация: 2
Всего: 2



Сам класс выглядит следующим образом:
Код

class SupportChat{
    protected $mysqli; // ссылка на mysqli, с mysql (без i) сокеты не дружили, пришлось использовать mysqli
    protected $host    = "localhost"; // подсоединение для базы данных
    protected $user    = "root"; // тут тоже для базы, пользователь
    protected $pass        = "55555"; // пароль от базы
    protected $db        = "name_baza"; // и сама база
    protected $email    = "[email protected]"; // мэйл нужен для того что когда чат не работает, пользователь мог написать на почту
    public      $sc_ws    = "файл сокетов.php"; // файл где запускаются сокеты, нужен для включения через, к примеру, exec()
    
// соединяемся при создании объекта
    public function __construct(){
        $this->mysqli = new mysqli($this->host, $this->user, $this->pass, $this->db);
        $this->mysqli->query("set names 'utf8'");
    }
// уничтожаем соединение
    public function __destruct(){
        $this->mysqli->close();
    }
    // цепляем все сообщения с чата любого пользователя
    public function getArrayAllMessages($chat_name){
        $aMessages = array();
        
        $result = $this->mysqli->query('SELECT M.* 
                                            FROM support_chat_messages AS M, support_chats AS C 
                                                WHERE M.chat_id=C.id AND C.name="' . $chat_name . '" ORDER BY M.`time` ASC');

        while($row = $result->fetch_array(MYSQLI_ASSOC))
            $aMessages[] = $row;
        
        $result->close();
        
        return $aMessages;
    }
// добавляем новый чат
    public function insertNewChat($chat_name){
        $chat_name = $this->mysqli->real_escape_string($chat_name);
        $this->mysqli->query('INSERT INTO support_chats (`name`) VALUES ("' . $chat_name . '");');
    }
// проверяем - есть ли вообще такой чат
    public function isChat($chat_name){
        $result = $this->mysqli->query('SELECT COUNT(*) FROM support_chats WHERE `name`="' . $chat_name . '"');
        $row    = $result->fetch_array(MYSQLI_NUM);
        $result->close();
        return $row[0];
    }
// сохраняем запись с чата
    public function writeMessage($chat_name, $text, $is_root=1, $ip = ""){
        $chat_name    = $this->mysqli->real_escape_string($chat_name);
        $result    = $this->mysqli->query('SELECT id FROM support_chats WHERE `name`="' . $chat_name . '"');
        $row        = $result->fetch_array(MYSQLI_NUM);
        $result->close();
        
        $chat_id = $row[0];
        
        $is_root    = ($is_root === 1)? 1: 0;
        $text        = $this->mysqli->real_escape_string($text);
        $this->mysqli->query('INSERT INTO support_chat_messages (`chat_id`, `message`, `is_root`, `ip`)
                                VALUE ("' . $chat_id . '", "' . $text . '", ' . $is_root . ', "' . $ip . '");');    
    }
// отправка на почту, если консультанта нет
    public function sendEmail($emailFrom, $subject, $message){
        $mail_header = "MIME-Version: 1.0\n";
        $mail_header.= "Content-type: text/html; charset=UTF-8\n";
        $mail_header.= "From: " . $emailFrom . " <" . $emailFrom . ">\n";
        $mail_header.= "Reply-to: Reply to " . $emailFrom . " <" . $emailFrom . ">\n";
        
        $result = mail($this->email, $subject, $message, $mail_header)? 1: 0;
            
        return $result;    
    }
// чистим текст
    public function clearText($str){
        $trans = array("~" => "", "`" => "", "$" => "", "^" => "", "*" => "", "{" => "", "}" => "", "|" => "", "\\" => "", "/" => "", "\"" => "", "<" => "", ">" => "", "  " => " ");
        $str = strip_tags($str);
        $str = strtr($str, $trans);
        $str = trim($str);
        
        return $str;
    }
    
    //приводит приходящие сообщения в нормальный вид
    public function unmask($text){
        $length = ord($text[1]) & 127;
        if($length == 126) {
            $masks = substr($text, 4, 4);
            $data = substr($text, 8);
        }
        elseif($length == 127) {
            $masks = substr($text, 10, 4);
            $data = substr($text, 14);
        }
        else {
            $masks = substr($text, 2, 4);
            $data = substr($text, 6);
        }
        $text = "";
        for ($i = 0; $i < strlen($data); ++$i) {
            $text .= $data[$i] ^ $masks[$i%4];
        }
        return $text;
    }
    // кодируем для удобной передачи сообщения
    public function mask($text){
        $b1 = 0x80 | (0x1 & 0x0f);
        $length = strlen($text);
        
        if($length <= 125)
            $header = pack('CC', $b1, $length);
        elseif($length > 125 && $length < 65536)
            $header = pack('CCn', $b1, 126, $length);
        elseif($length >= 65536)
            $header = pack('CCNN', $b1, 127, $length);
        
        return $header . $text;
    }
    // возьмем имя для чата взятое из куки PHPSESSID
    public function getNameForChat($str){
        $res = "";
        $find = "PHPSESSID=";
        $ar = substr_count($str, "; ")? explode("; ", $str): array($str);
        
        foreach($ar as $val){
            if(substr($val, 0, strlen($find)) == $find){
                $res = substr($val, strlen($find));
                break;
            }    
        }
        
        return $res;
    }
    // делаем рукопожатие
    public function handshake($secKey, $socket, $host, $port){
        $secAccept = base64_encode(pack('H*', sha1($secKey . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')));
        $upgrade  = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" .
        "Upgrade: websocket\r\n" .
        "Connection: Upgrade\r\n" .
        "WebSocket-Origin: " . $host . "\r\n" .
        "WebSocket-Location: ws://" . $host . ":" . $port . "/ws_chat.php\r\n".
        "Sec-WebSocket-Accept:" . $secAccept . "\r\n\r\n";
        $total_write_byte = socket_write($socket, $upgrade, strlen($upgrade));
        return $total_write_byte;
    }
    // разберем заголовки http в ассоциативный массив
    public function getAHeaders($receved_header){
        $headers = array();
        $lines = preg_split("/\r\n/", $receved_header);
        foreach($lines as $line){
            $line = chop($line);
            
            if(preg_match('/\A(\S+): (.*)\z/', $line, $matches))
                $headers[$matches[1]] = $matches[2];
        }
        
        return $headers;
    }
    // фильтр для сообщений с учетом ссылок фигурирующих в сообщениях
    public function clearMsg($str){
        $trans = array("~" => "", "`" => "", "$" => "", "^" => "", "{" => "", "}" => "", "|" => "", "\\" => "", "\"" => "", "<" => "", ">" => "", "  " => " ");
        $str = strip_tags($str);
        $str = strtr($str, $trans);
        $str = trim($str);
        
        $aProtocol = array("http://", "https://", "www.");
        $aStr = explode(" ", $str);
        $aRes = array();
        foreach($aStr as $val){
            if(substr_count($val, ".")){
                foreach($aProtocol as $val2){
                    if(substr($val, 0, strlen($val2)) == $val2){
                        $temp = substr($val, strlen($val2));
                        if($val2 == "www.")
                            $val2 = "http://";
                        $val = '<a href="' . $val2 . $temp . '" target="_blank">' . $temp . "</a>"; 
                    }
                }
            }
            
            $aRes[] = $val;
        }
        
        return implode(" ", $aRes);
    }
    // возьмем активных пользователей
    public function aGetOnlineCustomer($clients, $root_resource, $max_val_last_act = 300){
        $aOutput = array();
        $nowTime = time();
        
        foreach($clients as $val){
            // если время не больше максимального лимита, то клиент теоретически есть, заносим в массив его
            if( ($nowTime - $val["last_act"]) <= $max_val_last_act && $root_resource !== $val["resource"])
                $aOutput[] = $val["id"];
        }
        
        return $aOutput;
    }
}



Добавлено через 5 минут и 7 секунд
Далее бекэнд, который включает чат или отправляет письмо
Код

require_once("class.SupportChat.php");

$SC = new SupportChat();
// включает чат, тут конечно нужно еще на права проверить, чтоб кто попало не включал его, а только рут
if(!empty($_GET["power"])){
    $result = false;
    // если выполнение ф-ии exec разрешено, то запускаем скрипт, который прослушивает сокеты
    if(function_exists('exec')){
        exec("php -q " . $SC->sc_ws . " > /dev/null &");
        $result = true;
    }
        
    $output = array("result"=>$result);
}
// если консультанта нет, то шлем ему письмо
elseif(!empty($_POST["sendMail"]) && !empty($_POST["email"]) && isset($_POST["name"]) && !empty($_POST["message"])){
    $result    = false;
    
    $email        = filter_var($_POST["email"], FILTER_VALIDATE_EMAIL);
    $message    = $SC->clearText($_POST["message"]);
    $name        = !empty($_POST["name"])? $SC->clearText($_POST["name"]): "";
    
    $err = array();
    if(!$email)
        $err[] = "Ошибка: впишите е-мэйл!";
    if(!$message)
        $err[] = "Ошибка: впишите сообщение!";
    
    if(!count($err)){
        $message = "Имя: " . $name . "\n" . $message;
        $res = $SC->sendEmail($email, "www." . $_SERVER['HTTP_HOST'] . " - cообщение с чата", $message);
        if(!$res)
            $err[] = "Ошибка: е-мэйл не отправлен!";
        else{
            $result = true;
            $err[] = "Е-мэйл оправлен, ожидайте ответа!";
        }
    }
    
    $output = array("result"=>$result, "message" => implode("<br>", $err));
}
else 
    $output = array("result"=>false);
    
die(json_encode($output));


Добавлено через 11 минут и 29 секунд
html выглядит так:
Код

<? $is_root = // за ранее определим это рут или нет, чтоб дальше использовать эту переменную ?>
<div id=support_chat>
    <div class="sc_head offline">
     <? if($is_root): ?> // если рут, то покажем ему список соединеных клиентов
            <div class=sc_power title="Включить" onclick="SupportChat.power()"></div> 
        <? endif; ?>
        <div class=sc_indikator></div>
        консультант
    </div>
    <div class=sc_body>
     <? if($is_root): ?> // блок где отображаются подсоедиденые через сокеты клиенты
            <div class=sc_block_people></div> 
        <? endif; ?>
     <div class=sc_block_dialog>
         <div class=sc_to_email>
                <? if(!$is_root): ?> // тут ниже теги для отправки сообщения, если консультанта нет
                <table border=0 cellpadding=0 cellspacing=0>
                    <tr>
                        <td align="center" colspan="2">
                            <font size="+1">Консультант отсутствует.</font>
                            <br>
                            Напишите нам сообщение, мы обязательно свяжемся с Вами.
                        </td>
                    </tr>
                    <tr>
                        <td valign="bottom" colspan="2" height="100">
                            <div class=err_red_line_20></div>
                            <div class=err_green_line_20></div>
                        </td>
                    </tr>
                    <tr>
                        <td align=left width="100">Ваш е-мэйл* :</td>
                        <td align=left><input type="text" name="email" /></td>
                    </tr>
                    <tr>
                        <td align=left>Ваше имя :</td>
                        <td align=left><input type="text" name="name" /></td>
                    </tr>
                    <tr>
                        <td align=left colspan="2"><font class=comment>* поля обязательны для заполнения</font></td>
                    </tr>
                </table>
                <? else: ?> // этот блок для рута, чтоб не показывать ему блок для отправки сообщения через емэйл самому себе
                <table border=0 cellpadding=0 cellspacing=0 width="100%" height="100%">
                    <tr>
                        <td align="center">
                            <font size="+1">Консультант отсутствует.</font>
                        </td>
                    </tr>
                </table>
                <? endif; ?>
            </div>
            <? if(!empty($_COOKIE["PHPSESSID"])): ?> // вот тут если куки нет, то тогда и не зачем включать вообще чат, а то потом не получится идентифицировать пользователя
             <div class=sc_messages></div>
            <? endif; ?>
            <div class=sc_foot>
                <textarea placeholder="сообщение"></textarea>
                <button disabled="disabled">Отправить</button>
                <font id=att_hotkey_enter class=comment>(Enter)</font>
                <div class=sc_attention_enter_text>введите текст!</div>
            </div>
        </div>
    </div>
</div>

PM MAIL   Вверх
numerovan
Дата 30.11.2014, 00:44 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 549
Регистрация: 1.12.2007

Репутация: 2
Всего: 2



delete

Добавлено через 14 минут и 34 секунды
яваскрипт ни как не могу сюда разметить, ну да ладно

Это сообщение отредактировал(а) numerovan - 30.11.2014, 00:56
PM MAIL   Вверх
numerovan
Дата 30.11.2014, 01:00 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 549
Регистрация: 1.12.2007

Репутация: 2
Всего: 2



Код

@charset "UTF-8";
/* CSS Document */

#support_chat{position:fixed; bottom:0px; right:20px; z-index:98; box-shadow:1px 1px 3px rgba(0,0,0,0.5); border-radius:4px 4px 0 0; opacity:0.5;}
#support_chat div.sc_head{line-height:20px; padding:5px 10px 5px 10px; border-radius:4px 4px 0 0; border:white solid 1px; font-size:16px; text-shadow:0px 1px 0px white, 0px 2px 1px rgba(0,0,0,0.1); font-weight:bold; height:18px; cursor:pointer; text-align:right}
#support_chat div.sc_head.offline{border-bottom:lightgray solid 1px;
background: #ffffff; /* Old browsers */
/* IE9 SVG, needs conditional override of 'filter' to 'none' */
background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2ZmZmZmZiIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNlNWU1ZTUiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+);
background: -moz-linear-gradient(top,  #ffffff 0%, #e5e5e5 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(100%,#e5e5e5)); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top,  #ffffff 0%,#e5e5e5 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top,  #ffffff 0%,#e5e5e5 100%); /* Opera 11.10+ */
background: -ms-linear-gradient(top,  #ffffff 0%,#e5e5e5 100%); /* IE10+ */
background: linear-gradient(to bottom,  #ffffff 0%,#e5e5e5 100%); /* W3C */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#e5e5e5',GradientType=0 ); /* IE6-8 */
}
#support_chat div.sc_head.online{border-bottom:#00cc33 solid 1px;
background: #edffed; /* Old browsers */
/* IE9 SVG, needs conditional override of 'filter' to 'none' */
background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2VkZmZlZCIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNjY2ZmY2MiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+);
background: -moz-linear-gradient(top,  #edffed 0%, #ccffcc 100%); /* FF3.6+ */
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#edffed), color-stop(100%,#ccffcc)); /* Chrome,Safari4+ */
background: -webkit-linear-gradient(top,  #edffed 0%,#ccffcc 100%); /* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(top,  #edffed 0%,#ccffcc 100%); /* Opera 11.10+ */
background: -ms-linear-gradient(top,  #edffed 0%,#ccffcc 100%); /* IE10+ */
background: linear-gradient(to bottom,  #edffed 0%,#ccffcc 100%); /* W3C */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#edffed', endColorstr='#ccffcc',GradientType=0 ); /* IE6-8 */
}
#support_chat div.sc_head div.sc_power{width:20px; height:20px; float:left; cursor:pointer; background-image:url(/img/on_off_chat.png); background-repeat:no-repeat; background-position:1px 0px; opacity:0.7; position:relative; top:-2px}
#support_chat div.sc_head div.sc_power:hover{opacity:1}
#support_chat div.sc_head.online div.sc_power{background-position:-20px 0px}
#support_chat div.sc_head div.sc_indikator{display:inline-block; width:10px; height:10px; margin-right:5px; border-radius:5px; background-color:lightgray;}
#support_chat div.sc_head.online div.sc_indikator{background-color:lightgreen;}
#support_chat div.sc_body{height:0px; background-color:ghostwhite;}
#support_chat div.sc_body div.sc_block_people{display:inline-block; vertical-align:top; width:150px; height:inherit; overflow-y:auto; overflow-x:hidden; background-color:#333333;}
#support_chat div.sc_body div.sc_block_dialog{display:inline-block; width:300px;}
#support_chat div.sc_body div.sc_block_dialog div.sc_to_email{position:relative; height:300px;}
#support_chat div.sc_body div.sc_block_dialog div.sc_to_email table{position:absolute; bottom:0px; margin:5px;}
#support_chat div.sc_body div.sc_block_dialog div.sc_to_email table tr td{padding-bottom:5px}
#support_chat div.sc_body div.sc_block_dialog div.sc_to_email table tr td input[type=text]{width:184px;}
#support_chat div.sc_body div.sc_block_dialog div.sc_messages{display:none; height:300px;}
#support_chat div.sc_body div.sc_block_dialog div.sc_foot{padding:5px; border-top:lightgray solid 1px; height:120px; background-color:floralwhite}
#support_chat div.sc_body div.sc_block_dialog div.sc_foot textarea{padding:4px; width:280px; height:60px; margin-bottom:10px; box-shadow:inset 0px 0px 1px rgba(0,0,0,0.5);}
#support_chat div.sc_body div.sc_block_dialog div.sc_foot button{margin-left:10px; margin-right:5px;}
#support_chat div.sc_message{margin-bottom:5px; padding:0px 5px;}
#support_chat div.sc_message div[mytag=content]{max-width:230px; background-color:white; padding:5px 10px; text-align:left; display:inline-block}
#support_chat div.sc_message.system{text-align:center; color:#999999; padding:5px}
#support_chat div.sc_message.root{text-align:left; border-radius:0px 5px 5px 5px;}
#support_chat div.sc_message.user{text-align:right; border-radius:5px 0px 5px 5px;}
#support_chat div.sc_message.root div[mytag=content]{border-radius:0px 5px 5px 5px;}
#support_chat div.sc_message.user div[mytag=content]{border-radius:5px 0px 5px 5px;}

#support_chat div.sc_people{background-color:#444444; padding:5px; color:whitesmoke; margin:5px; cursor:pointer; text-align:left;border-radius:2px}
#support_chat div.sc_people.checked{background-color:whitesmoke; color:#444444; cursor:default; text-align:right}
#support_chat div.sc_total_new_message{min-width:10px; font-size:10px; padding:1px 2px; text-align:center; float:right; background-color:yellow;  color:#333; border-radius:7px; display:none}
#support_chat div.sc_attention_enter_text{float:right; color:red; display:none}
#support_chat #att_hotkey_enter{display:none}

font.comment{color:#999; text-shadow:0px 1px 0px white}
div.err_red_line_20{display:none; line-height:20px; background-color:red; color:white; text-align:center; margin-bottom:10px; border-radius:4px;}
div.err_green_line_20{display:none; line-height:20px; background-color:#00ff33; text-align:center; margin-bottom:10px; border-radius:4px;}



Добавлено через 12 минут и 2 секунды
яваскрипт

Присоединённый файл ( Кол-во скачиваний: 2 )
Присоединённый файл  Untitled_1.js.zip 4,83 Kb
PM MAIL   Вверх
numerovan
Дата 30.11.2014, 01:17 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 549
Регистрация: 1.12.2007

Репутация: 2
Всего: 2



картинку чуть позже выставлю ...
Библиотеки яваскриптов использовались следующие: jquery-2.0.3.min.js, jquery.mousewheel.js, jquery.jscrollpane.min.js

Еще не много нужно подтачить, картику выложу чуть позже, кому интересно будет посмотреть ...

Если что пиши какие нибудь замечания и т.д.

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

Это сообщение отредактировал(а) numerovan - 30.11.2014, 01:17
PM MAIL   Вверх
numerovan
Дата 1.12.2014, 19:29 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 549
Регистрация: 1.12.2007

Репутация: 2
Всего: 2



Кстати, время просчитывания массивов в ф-ии socket_select() лучше ставить в 1 секунду. В моем случае если в 0, то загрузка процессора машины равняется 99%, если 0.7 то где-то 15% ... в общем 1 сек. это то что надо и при этом скорость передачи данных визуально не меняется.
PM MAIL   Вверх
numerovan
Дата 3.12.2014, 17:12 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 549
Регистрация: 1.12.2007

Репутация: 2
Всего: 2



Теперь проблема с настройкой на веб-сервере, в моем случае CentOS 6.
Ни как сокет на сервере не хочет соединятся с браузером, хотя на локалке все хорошо было ...
PM MAIL   Вверх
numerovan
Дата 18.12.2014, 15:25 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 549
Регистрация: 1.12.2007

Репутация: 2
Всего: 2



Решил задачу ... правда пришлось завалить, пока юзал, весь сервер. Если стоит nginx, то в нем нужно указывать поддержку сокета, если его нет, как в моем случае сейчас, то по сути ни чего, на веб-сервере (Апачи), не нужно делать. В php-скрипте, как выяснил сейчас, нужно правильно прописать host. Повторюсь портов открывать не нужно, который вы собираетесь использовать порт, в моем случае 9000, он должен быть свободен и другие программы не должны его использовать. Так в общем этот host не должен быть назван как "localhost" и не должен быть обозначен $_SERVER['SERVER_NAME'], нужно указать в нем либо IP машины, либо название домена.
PM MAIL   Вверх
numerovan
Дата 2.1.2015, 17:50 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 549
Регистрация: 1.12.2007

Репутация: 2
Всего: 2



защитный сервис cloudflare.com блокировал сокеты, пришлось пока от него отказаться, для того чтоб сокеты работали (онлайн-консультант).
PM MAIL   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
1 Пользователей читают эту тему (1 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | PHP: Сеть | Следующая тема »


 




[ Время генерации скрипта: 0.1853 ]   [ Использовано запросов: 21 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.