Diskuze: WebSocket klient pro python

Python Python WebSocket klient pro python American English version English version

Aktivity (1)
Avatar
Karel Panský:12. září 21:06

Ahoj, potýkám se s problémem při implementaci WebSocketího klienta vůči javovské aplikaci. Potřeboval bych nějaký tip na dobrého klienta pro python, který je schopen:

  1. autorizaci (např, basic, nebo nějaký token)
  2. podporu ticketů, tzn. potřebuji aby příjem i odesílání zpráv šlo přes vlastní url (např. /send, /receive)

Pokud by se tu v tom někdo vyznal, bylo by to skvělý. Jinak původně jsme měli javu tvořenou pomocí SockJS, ale to na pythonu je k dispozici jen jako server, nebo klient ve vývoji. Za jakýkoli tipy budu velice vděčný.

Zkusil jsem: Zkoušel jsem již bezpočet různých knihoven, ale žádná nesplňovala moje požadavky. Už se s tím babrám asi týden a nemůžu si dovolit na tom ztrácet tolik času. Bohužel většina těch klientů je řešená tak, že mají jak příjem tak odesílání přes jednu cestu. Na straně javy by to byl hodně velký zásah a rád bych se mu vyhnul.

Chci docílit: Výsledkem by mělo být autorizované připojení pomocí WebSocket s proměnnou url, která bude obsahovat i session id, např /device/{sessi­onId}/send-data - /device/{sessi­onId}/receive-data

 
Odpovědět 12. září 21:06
Avatar
Jindřich Máca
Tým ITnetwork
Avatar
Odpovídá na Karel Panský
Jindřich Máca:13. září 2:21

Ahoj, mám na úvod otázku trochu bokem, ale tou "Javou" myslíš JavaScript? :-?

 
Nahoru Odpovědět 13. září 2:21
Avatar
Peter Mlich
Člen
Avatar
Peter Mlich:13. září 8:14

Autorizaci si musis dopsat sam.
Je to uz nejaky cas, co jsem to googloval. Nevim, zda mam puvodni verzi programku, ale ta upravena vypada nejak takto, v php.
Funguje to tak, ze mas nekonecny cyklus, ktery reaguje na vsechno, co se mu posle. Proto je nutne nastavit pro beh programu neomezeny limit. A mel bys tam mit moznost program zastavit. Jinak ho musis pres procesy zhazovat rucne :)
Co tam uplne nefungovalo, bylo to maskovani pro delsi zpravy. A taky se mi tam na portu objevovali jakesi zpravicky, nejspis pingy. Tak jsem to dal neresil.

<?php
header("Content-Type: text/html; charset=UTF-8");


error_reporting(E_ALL);
//set_time_limit(0);    // how long run server, 0 = unlimited
//set_time_limit(20);
//set_time_limit(10);
set_time_limit(5);
ob_implicit_flush();

//$MSG = new class_message;

// ------------

class classChat
{
var $WS;
//var $users_db;

function __construct($ws)
        {
        $this->WS = $ws;
        }

private function status($func_name,$item='')
        {
        echo '<hr>';
        echo '<b>CHAT::'.$func_name.'</b> ';
        if ($item!='')
                {
                var_dump($item);
                }
        }

public function encode($msg_arr)
        {
        $msg_str = json_encode($msg_arr,JSON_FORCE_OBJECT);
        return $msg_str!==false ? $msg_str : '';
        }

public function decode($msg_str)
        {
        return json_decode($msg_str,true);
        }

public function createMessage($ws_user,$type,$text)     // $text = string/array
        {
        $msg_arr = array(
                'type'   => $type,
                'socket' => $ws_user->index,
                'text'   => $text
                );
        return $msg_arr;
        }

/*
public function createUserList()        // $text = string/array
        {
        $msg_arr = array(
                'type'   => $type,
                'socket' => $ws_user->socket,
                'text'   => $text
                );
        return $msg_arr;
        }
*/

public function sendOne($msg_arr,$socket=null)
        {
        $this->status('sendOne');
        if ($socket!=null)
                {
                $this->WS->msgSend($socket,$this->encode($msg_arr));
                }
        }

public function sendAll($msg_arr,$socket_user=null)
        {
        $this->status('sendAll');
//      $user_list = $this->socketsList($socket);
        $socket_all = $this->WS->sockets_db->table;
        foreach($socket_all as $socket)
                {
                if ($socket==$this->WS->master ||
                    ($socket_user!=null && $socket_user==$socket))      // skip user, skip master (mastera filtruje i socketSend)
                        {
                        continue;
                        }
//var_dump($socket);
                $this->sendOne($msg_arr,$socket);
                }
        }

// user messages: text, login, logout, userlist... || now only simple text for all sockets
public function receiveUserMessage($ws_user,$msg_str)
        {
        $this->status('receiveUserMessage',$msg_str);
        $msg_arr = $this->decode($msg_str);
        if (!(is_array($msg_arr) && $msg_arr['type'] && isset($msg_arr['text'])))
                {
                $this->status('receiveUserMessage - not my WS message structure',$msg_arr);
                return;
                }
        if ($msg_arr['text']=='serverstop')
                {
                $this->WS->serverStop();
                }
        $msg_arr['type']   = 'msg';
        $msg_arr['socket'] = $ws_user->index;
//      $this->sendAll($msg,$ws_user->socket);
        $this->sendAll($msg_arr);
        }

public function receiveControlPing($ws_user,$msg_str)
        {
        $this->status('receiveControlPing',$msg_str);
        $msg_arr = $this->createMessage($ws_user,'ping',$msg_str);
//      $this->sendAll($msg,$ws_user->socket);
        $this->sendAll($msg_arr);
        }

public function receiveControlPong($ws_user,$msg_str)
        {
        $this->status('receiveControlPong',$msg_str);
        $msg_arr = $this->createMessage($ws_user,'pong',$msg_str);
//      $this->sendAll($msg,$ws_user->socket);
        $this->sendAll($msg_arr);
        }

public function receiveControlClose($ws_user,$msg_str)
        {
        $this->status('receiveControlClose',$msg_str);
        $msg_arr = $this->createMessage($ws_user,'close',$msg_str);
//      $this->sendAll($msg,$ws_user->socket);
        $this->sendAll($msg_arr);
        }

public function receiveConnect($ws_user)
        {
        $this->status('receiveConnect',$ws_user);
        $msg_arr = $this->createMessage($ws_user,'connect'   ,array(
                'ip' => $this->WS->socketIp($ws_user->socket),
                ));
//      $this->sendAll($msg_arr,$ws_user->socket);
        $this->sendAll($msg_arr);
        }

public function receiveDisConnect($ws_user)
        {
        $this->status('receiveDisConnect',$ws_user);
        $msg_arr = $this->createMessage($ws_user,'disconnect',array(
                'ip' => $this->WS->socketIp($ws_user->socket),
                ));
//      $this->sendAll($msg_arr,$ws_user->socket);
        $this->sendAll($msg_arr);
        }
}

// ------------



// -------------
class classSocket {
  var
    $index,     // unique autoincrement
    $socket;
}

class classSocketsDb
{
var $table, $structure, $index_auto;

function __construct($structure)
        {
        $this->table = array();
        $this->structure  = $structure;
        $this->index_auto = 0;
        }

function insert($data)
        {
        $index = $this->index_auto;             // count($this->table);
        $this->table[$index] = $data;   // uloz jen socket
        $new_row = new $this->structure;
        $new_row->index  = $index;
        $new_row->socket = $data;
//      $this->table[$index] = $new_row;
        $this->index_auto++;
        return $new_row;
        }

function delete($data)
        {
        $row = $this->select($data);
        if ($row)
                {
                unset($this->table[$row->index]);
                return $row;
                }
        return false;
        }

function select($data)
        {
        foreach($this->table as $index=>$value)
                {
                if ($value==$data)      // $socket
                        {
                        $new_row = new $this->structure;
                        $new_row->index  = $index;
                        $new_row->socket = $value;
                        return $new_row;
                        }
                }
        return false;
        }
}


// -----------------

class classWebSocket
{
public $cfg, $cb, $run, $master, $sockets_db;

function __construct($cfg,$cb=array())
        {
        $url = "ws://".$cfg['server']['host'].":".$cfg['server']['port']."/demo/".$cfg['server']['page'];//RTC-chat/php-ws
        $cfg['server']['url'] = $url;
        $this->cfg = $cfg;
        $this->cb  = $cb;
        $this->sockets_db = new classSocketsDb('classSocket');
        $this->serverCreate();
        }

private function errorAdd($str,$socket)
        {
        if ($socket)
                {
                $err_code = socket_last_error($socket);
                socket_clear_error($socket);
                }
        else    {
                $err_code = socket_last_error();
                }
        $err_msg = socket_strerror($err_code);
        $str     = '<b>'.$str.'</b>' . ' Socket error ['.$err_code.'] '.$err_msg.'<br>';
echo $str;
        return false;
        }

private function status($func_name,$item='')
        {
        echo '<b>WS::'.$func_name.'</b> ';
        if ($item!='')
                {
                var_dump($item);
                }
        echo '<hr>';
        }

public function runCb($name, $socket, $msg='')  // call back function
        {
//call_user_func
        $this->status('runCb',$name);
        if (isset($this->cb[$name]))
                {
//echo 1;
                $cb = $this->cb[$name];
                if (isset($cb[0]) && isset($cb[1]) )
                        {
//echo 2;
                        list($obj, $func) = $cb;
//var_dump($obj, $func);
                        call_user_func_array(array($obj, $func), array($socket, $msg));
//&& is_object($func[0]))
// && is_callable($func[0]->$func[1])
//                      $cb = $func[0]->$func[1];
//                      $cb($socket, $msg);
//echo 333;
                        }
                }
        }

//Unmask incoming framed message
public function unmask($str)
        {
        $this->status('unmask');
        $byte1  = ord($str[1]);
        $masked = $byte1 >> 7;
        $length = $byte1 & 127;
        if ($masked==1)
                {
//var_dump('/masked');
                if($length == 126)
                        {
var_dump('unmask 0');
                        $mask = substr($str, 4, 4);
                        $str  = substr($str, 8);
                        }
                elseif($length == 127)
                        {
var_dump('unmask 1');
                        $mask = substr($str, 10, 4);
                        $str  = substr($str, 14);
                        }
                else    {
var_dump('unmask 2');
                        $mask = substr($str, 2, 4);
                        $str  = substr($str, 6);
                        }
                $out   = "";
                $i_end = strlen($str);
                for ($i = 0; $i < $i_end; ++$i)
                        {
                        $out .= $str[$i] ^ $mask[$i % 4];
                        }
                return $out;
                }
//var_dump('/not masked');
        if ($length == 126)
                {
                return substr($str, 4);
                }
        if ($length == 127)
                {
                return substr($str, 10);
                }
        return substr($str, 2);
        }


//Encode message for transfer to client.
public function mask($str,$masking=true)
        {
        $this->status('mask');
        // server must send non-masked data
        $byte0 = 0x80 | (0x1 & 0x0f);   // fin + text frame
        $length = strlen($str);
        if($length <= 125)
                $header = pack('CC', $byte0, $length);
        elseif($length > 125 && $length < 65536)
                $header = pack('CCn', $byte0, 126, $length);
        elseif($length >= 65536)
                $header = pack('CCNN', $byte0, 127, $length);
        return $header.$str;
/*
// masking
        $mask = 0;
        if ($masking)
        {
        $mask = array();
        $range = range(0x00,0xFF);
        $index = array_rand($range,4);
        $i_end = 4;
        for ($i = 0; $i < $i_end; ++$i)
                {
                $mask[$i] = $range[$index[$i]];
                }
        $i_end = strlen($str);
        for ($i = 0; $i < $i_end; ++$i)
                {
                $str[$i] = $str[$i] ^ $mask[$i % 4];
                }

        $byte1_masking = 0x80;  // masking bit
        $byte0 = 0x80 | (0x1 & 0x0f);   // fin bit + opcode text frame
//var_dump($byte0,$mask);
        $length = strlen($str);
        if($length <= 125)
                $header = pack('CCCCCC', $byte0, $length | $byte1_masking,$mask[0],$mask[1],$mask[2],$mask[3]);
        elseif($length > 125 && $length < 65536)
                $header = pack('CCnCCCC', $byte0, 126 | $byte1_masking, $length,$mask[0],$mask[1],$mask[2],$mask[3]);
        elseif($length >= 65536)
                $header = pack('CCNNCCCC', $byte0, 127 | $byte1_masking, $length,$mask[0],$mask[1],$mask[2],$mask[3]);
//      return $header.$text;

        return $header.$str;

        }
*/
        }


public function createHandshake($socked, $received_header, $cfg)
        {

        $this->status('createHandshake','<pre>'.$received_header.'</pre>');
        // read header
        $headers = array();
        $lines   = preg_split("/\r\n/", $received_header);      //preg_quote    //      $lines   = preg_split("~".$cfg['header_sep']."~", $received_header);    //preg_quote
        foreach($lines as $line)
                {
                $line = chop($line);
                if (preg_match('/\A(\S+): (.*)\z/', $line, $matches))
                        {
                        $headers[$matches[1]] = $matches[2];
                        }
                }
        // write header
        $sec_key    = isset($headers['Sec-WebSocket-Key']) ? $headers['Sec-WebSocket-Key'] : '';
        $guid       = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';                   // globally unique identifier / RFC 6455 WebSocket Protocol
        $accept_key = base64_encode(pack('H*', sha1($sec_key . $guid)));        // $accept_key = base64_encode(sha1($sec_key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',true));
        $upgrade    = array(
                'HTTP/1.1 101 Web Socket Protocol Handshake',
                'Upgrade: websocket',
                'Connection: Upgrade',
                'Sec-WebSocket-Accept: '.$accept_key,
                'WebSocket-Origin: '    .$cfg['server']['host'],
                'WebSocket-Location: '  .$cfg['server']['url'],
                '',
                ''              );
        $upgrade = implode($cfg['handshake']['sep'], $upgrade);
        return $upgrade;
        }


public function socketIp($socket)
        {
        if ($socket)
                {
                socket_getpeername($socket, $ip) or $this->errorAdd("socket_getpeername()",$socket);    //WS
                return $ip;
                }
        return false;
        }


public function socketReceive($socket,$buffer)
        {
        //https://tools.ietf.org/html/rfc6455#section-5.2
        $this->status('socketReceive',array($socket));  //,$buffer
        $byte0   = ord($buffer[0]);
        $op_code = $byte0 & 0x0F;
//      $ws_user = new classSocket;
        $ws_user = $this->sockets_db->select($socket);  // mozna by to chtelo pockat na dalsi cyklus s delete/close
        switch ($op_code)
                {
                case 0x01 :// frame-opcode-non-control: Text frame (10000001):
                        $this->runCb('receiveUserMessage' ,$ws_user,$this->unmask($buffer));
                        break;
                // other may be not masked?
                case 0x08 :// frame-opcode-control Close connection (10001000):
                        $this->runCb('receiveControlClose',$ws_user,$this->unmask($buffer));
                        break;
                case 0x09 :// frame-opcode-control Ping (10001001):
                        $this->runCb('receiveControlPing' ,$ws_user,$this->unmask($buffer));
                        break;
                case 0x0A :// frame-opcode-control Pong (10001010):
                        $this->runCb('receiveControlPong' ,$ws_user,$this->unmask($buffer));
                        break;
                }
        return true;
        }

public function socketConnect($cfg)
        {
        $this->status('socketConnect');
        $socket = socket_accept($this->master) or $this->errorAdd("socket_accept()"); //accept new socket
        if ($socket)
                {
                $header  = socket_read($socket, 1024) or $this->errorAdd("socket_read()",$socket); //read data sent by the socket
                $msg     = $this->createHandshake($socket, $header, $cfg); //perform websocket handshake
                $len     = strlen($msg);
                $this->socketSend($socket, $msg, $len);
                $ws_user = $this->sockets_db->insert($socket);
                $this->runCb('receiveConnect',$ws_user);
                }
        return false;
        }

public function socketDisconnect($socket)
        {
        $this->status('socketDisconnect',$socket);
        $ws_user = $this->sockets_db->delete($socket);  // mozna by to chtelo pockat na dalsi cyklus s delete/close
        $this->runCb('receiveDisConnect',$ws_user);
        @socket_close($socket);
        }

public function socketDisconnectAll()
        {
        $this->status('socketDisconnectAll',$socket);
        foreach ($this->sockets_db->table as $socket)
                {
                @socket_close($socket);
                }
        @socket_close($this->master);
        }

public function socketSend($socket=null,$msg_str='',$len=0)     // kvuli rychlosti pro cyklus davam $len mimo funkci
        {
        $this->status('socketSend',array($socket,$msg_str,$len));
        if ($socket && $socket!=$this->master)
                {
                socket_write($socket,$msg_str,$len);
                }
        }

public function msgSend($socket,$msg_str)
        {
        $this->status('msgSend',$msg_str);
        $msg_str = $this->mask($msg_str);
        $len     = strlen($msg_str);
        $this->socketSend($socket,$msg_str,$len);
        }

public function serverCreate()
        {
        $cfg    = $this->cfg['server'];
        $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)  or $this->errorAdd("socket_create()"    ,$socket);
        socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1) or $this->errorAdd("socket_set_option()",$socket);
        socket_bind($socket, $cfg['host'], $cfg['port'])        or $this->errorAdd("socket_bind()"      ,$socket);
        socket_listen($socket, $cfg['conn_max'])                or $this->errorAdd("socket_listen()"    ,$socket);
        $this->master = $socket;
        $this->sockets_db->insert($socket);
        //socket_set_nonblock($socket);
        $str  = "Server Started : ".date('Y-m-d H:i:s')."\n";
        $str .= "Master socket  : ".$socket."\n";
        $str .= "Listening on   : address = ".$cfg['host'].", port = ".$cfg['port']."\n";
        $str .= $this->cfg['server']['url']."\n\n";
        echo nl2br($str);
        }

public function serverStop()
        {
        $this->status('serverStop');
        $this->run = false;
        }

public function serverRestart()
        {
        $this->serverStop();
        $this->run = true;
        while ($this->run)
                {
                $this->serverRun();
                }
        }

public function serverRun()
        {
        $write_null  = null;
        $except_null = null;
        $socket_all  = $this->sockets_db->table;
//var_dump($changed);
        socket_select($socket_all,$write_null,$except_null,0,10);       // read changed sockets to $socket_all
        if ($socket_all === false)
                {
                errorAdd('socket_select()'); return; // error
                }
        foreach($socket_all as $socket)
                {
                if ($socket==$this->master)
                        {
                        $this->socketConnect($this->cfg);
                        continue;
                        }
                $bytes = @socket_recv($socket,$buffer,$this->cfg['server']['buffer_max'],0);
                if ($bytes === false)
                        {
                        $this->errorAdd('bytes==false',$socket);
                        $this->socketDisconnect($socket);       // odpojit socket, kdyz nastane chyba
                        continue;
                        }
                elseif ($bytes==0)
                        {
                        continue;
                        $user = $this->socketDisconnect($socket);// odpojit socket, kdyz na nej nic nechodi, uzivatel se odpojil
                        }
                $this->run = $this->socketReceive($socket, $buffer);
                }
//echo '.';
        }


}






$cfg = array(
'server' => array(
        'host' => 'localhost',  // host
        'port' => '9000',       // port
        'page' => 'ws_server_min.php',  // page / program filename


        'conn_max'   => 20,
        'buffer_max' => 2048,
        'url'  => ''
        ),
'handshake' => array(
        'sep'  => PHP_EOL ? PHP_EOL : "\r\n"
        )
);



echo '<title>WS server</title>';
$WS   = new classWebSocket($cfg);
$CHAT = new classChat($WS);
$WS->cb['receiveUserMessage'] = array($CHAT,'receiveUserMessage');
// other CB not need for chat
$WS->cb['receiveConnect']     = array($CHAT,'receiveConnect');
$WS->cb['receiveDisConnect']  = array($CHAT,'receiveDisConnect');
$WS->cb['receiveControlPing'] = array($CHAT,'receiveControlPing');
$WS->cb['receiveControlPong'] = array($CHAT,'receiveControlPong');
$WS->cb['receiveControlClose'] = array($CHAT,'receiveControlClose');

echo 'Starting...';
$WS->serverRestart();
echo '<br>Stoping...';
$WS->socketDisconnectAll();
$WS->serverStop();

// chyba Trusted = spatny handshake

Js cast vypada nejak takto pro Firefox

// https://github.com/stephenlb/webrtc-sdk/blob/gh-pages/js/webrtc.js
// error 'isTrusted' = not connect / bad handshake

//alert(JSON);
function $(id) {return document.getElementById(id);}
function writeStatus(text)   {var o = $("status");   o.innerHTML = (window.performance.now() / 1000).toFixed(3) + ' '  + text + "\n" + o.innerHTML;}
function isObject(data)
        {
        return typeof data==='object' || typeof(data)==='array';
        }
function isSet(obj,index)
        {
        return (index in obj) || typeof obj.index!=='undefined' ? true : false;
//return true;
        }
function render(tpl,data)
        {
        var i, str;
        str = tpl;
        for (i in data)
                {
                str = str.replace('{'+i+'}',data[i]);
                }
        return str;
        }

function fill(value)
{
var form;
form = $('form_login');
form.user_name.value = value;
form.user_psw.value  = value;
form = $('form_message');
form.user1.value  = value;
}

// ---

var CFG = {};
CFG.ws = {};    // web socket server
CFG.ws.server_url = 'ws://localhost:9000/demo/ws_server_min.php';
CFG.ws.server_opt = null;
CFG.chat = {};

// ---

function classChatMessage()
{
this.type  = '';
this.text  = '';
this.users = '';
}

function classChatUser()
{
this.socket = '';
this.ip   = '';
this.name = '';
this.time = '';
this.status = '';       // from message
}

var CHAT  = {};
// private
CHAT.cfg  = CFG.chat;
CHAT.obj  = {};
CHAT.data = {};
CHAT.func = {};
CHAT.func.status = function(text) {writeStatus('CHAT ' + text);};
CHAT.func.error  = function(text) {writeStatus('CHAT error ' + text);};
CHAT.func.encode = function(obj) {return JSON.stringify(obj);}
CHAT.func.decode = function(str) {return JSON.parse(str);}

CHAT.func.construct = function(ws,cfg)  // public
        {
        CHAT.WS = ws;
        CHAT.data.userlist = {};
        CHAT.obj.form     = $('form_message');
        CHAT.obj.screen   = $("messages");
        CHAT.obj.userlist = $("userlist");
        }

CHAT.func.findUserData = function(msg_arr)
        {
        var obj, chat_user;
        obj = msg_arr.text;
        chat_user = new classChatUser;
        chat_user.socket = isSet(msg_arr,'socket') ? msg_arr.socket : '';
        chat_user.ip     = isSet(obj,'ip') ? obj.ip : '';
        chat_user.name   = isSet(obj,'name') || (isSet(obj,'users') && isSet(obj.users,0)) ? msg_arr.text.users[0] : '';
        chat_user.status = isSet(msg_arr,'type') ? msg_arr.type : '';
        chat_user.time   = new Date();
        return chat_user;
        }

CHAT.func.userlist = {};

CHAT.func.userlist.updateUser = function(id,chat_user)
        {
        var i;
        if (!isSet(CHAT.data.userlist,id))
                {
                CHAT.data.userlist[id] = new classChatUser;
                }
        for (i in chat_user)
                {
                value = chat_user[i];
                if (value!=='')
                        {
                        CHAT.data.userlist[id][i] = value;
                        }
                }
        CHAT.func.userlist.show();
        }

CHAT.func.userlist.show  = function()
        {
        CHAT.func.status('userlist.show');
        var i,j, str, arr, list, list_row, tpl;
        tpl = '#{socket} {name} {ip}';
        arr = [];
        list = CHAT.data.userlist;
        for (i in list)
                {
                arr[arr.length] = render(tpl,list[i]);
                }
        arr.sort();
        str = arr.join("\n");
        CHAT.obj.userlist.innerHTML = str;
        };


CHAT.func.form = {};
CHAT.func.form = {};


CHAT.func.message = {};

CHAT.func.message.create = function(type,text,users)
        {
        var new_msg = new classChatMessage;
        new_msg.type  = type  && type!==null  ? 'msg' : '';
        new_msg.text  = text  && text!==null  ? text  : '';
        new_msg.users = users && users!==null ? users : '';
        return new_msg;
        }

CHAT.func.message.screen = function(chat_msg_arr)
        {
        CHAT.obj.screen.innerHTML = CHAT.func.encode(chat_msg_arr) + CHAT.obj.screen.innerHTML;
        }

CHAT.func.message.receive = function(chat_msg_arr)
        {
        CHAT.func.message.screen(chat_msg_arr);
        }

CHAT.func.message.send = function(chat_msg)     // chat_msg = object or string
        {
        var new_msg;
        new_msg      = new classWsMessage();
        new_msg.type = 'msg';
        new_msg.text = chat_msg && chat_msg!==null ? chat_msg : '';
        CHAT.func.send(new_msg);
        }

CHAT.func.receive = function(msg_str)
        {
        var msg_arr,chat_user;
        CHAT.func.status('receive');
        msg_arr = CHAT.func.decode(msg_str);
        if (!msg_str || msg_str=='' || !isObject(msg_arr))
                {
                CHAT.func.status('receive - message-unknown');
                return;
                }
        if (!(msg_arr && msg_arr.type && msg_arr.socket && msg_arr.text))       // && isObject(msg.users)
                {
                CHAT.func.status('receive - message-no-my-structure');  // handshake
                CHAT.func.message.screen(msg_str);
                return;
                }
        CHAT.func.status('receive ' + msg_arr.type);
        switch (msg_arr.type)
                {
                case 'msg':
                        CHAT.func.userlist.updateUser(msg_arr.socket,CHAT.func.findUserData(msg_arr));
                        CHAT.func.message.receive(msg_arr);
                        break;
                case 'connect':
                case 'connected':
                case 'disconnect':
                        CHAT.func.userlist.updateUser(msg_arr.socket,CHAT.func.findUserData(msg_arr));
                        CHAT.func.message.screen(msg_arr);
                        break;
                case 'ping':
                case 'pong':
                case 'close':   // connect close
                        CHAT.func.message.screen(msg_arr);
                        break;
                default: break;
                }
        }

CHAT.func.connectionOpen = function()   // send user name to all
        {
        var form, new_chat_msg;
        form = CHAT.obj.form;
        new_chat_msg = CHAT.func.message.create(
                'connected',
                '',
                [form['user1'].value]
                );
        CHAT.func.message.send(new_chat_msg)
        form.message.focus();
        }

CHAT.func.send = function(msg_arr)
        {
        if (msg_arr.type=='' || msg_arr.text=='')
                {return;}
        CHAT.WS.cmd.send(CHAT.func.encode(msg_arr));
        }

CHAT.func.formSend = function()
        {
        var form, new_chat_msg;
        form = CHAT.obj.form;
        new_chat_msg = CHAT.func.message.create(
                'msg',
                form['message'].value,
                [form['user1'].value,form['user2'].value]
                );
        CHAT.func.message.send(new_chat_msg);
        form.message.focus();
        }

CHAT.func.formRead = function(event)
        {
        var form, key;
        event = event || window.event;
        key   = event.keyCode || event.which || event.charCode || event.code;
        if (key==13)    // enter key
                {CHAT.func.formSend();}
        }

CHAT.func.sendServerStop = function(msg_arr)
        {
        CHAT.func.message.send('serverstop'); // my stop php web-socket server
        }

// public
CHAT.cmd = {};
CHAT.cmd.formSend = CHAT.func.formSend;
CHAT.cmd.formRead = CHAT.func.formRead;
CHAT.cmd.sendServerStop = CHAT.func.sendServerStop;


// ---

function classTimer()
{
var that   = this;
this.timer = null;
this.interval;
this.repeat_cycles;
this.repeat_index;
this.callback = null;
this.construct = function(func,inverval,repeat)
        {
        that.timer    = null;
        that.interval = inverval ? inverval : 150;
        that.repeat_cycles = repeat ? repeat : 0;
        that.callback = {
                'open' : func['open' ] ? func['open' ] : function(){},
                'run'  : func['run'  ] ? func['run'  ] : function(){},
                'close': func['close'] ? func['close'] : function(){}
                }
        }
this.open = function()
        {
        if (that.timer==null)
                {
                that.callback.open();
                that.repeat_index = that.repeat_cycles;
                that.timer = setInterval(that.run,that.interval);
                return true;
                }
        return false;
        }
this.run = function()
        {
        that.callback.run();
        if (that.repeat_cycles>0)
                {
                that.repeat_index--;
                if (that.repeat_index==0)
                        {
                        that.close();
                        }
                }
        }
this.close = function()
        {
        if (that.timer!=null)
                {
                clearInterval(that.timer);
                that.timer = null;
                that.callback.close();
                return true;
                }
        return false;
        }
}

function classWsMessage()
{
this.type = null;
this.text = null;
}

var WS;
//
// init
WS = {}
// private
WS.cfg = {}
WS.api =  window.WebSocket;
WS.data = {}
WS.func = {};
WS.func.status = function(text) {writeStatus('WS ' + text);};
WS.func.error  = function(text) {writeStatus('WS error ' + text);};

WS.func.construct = function(cfg)       // public
        {
        WS.cfg.server_url = cfg.server_url ? cfg.server_url : '';
        WS.cfg.server_opt = cfg.server_opt ? cfg.server_opt : null;
        WS.data.cb = {};        // call back functions / CHAT
        WS.data.conn = null;
        WS.data.close_timer = new classTimer();
        WS.data.close_timer.construct({'run':WS.func.connCloser},500,5);
        WS.data.close_notclosed = false;
        WS.data.send_timer  = new classTimer();
        WS.data.send_timer.construct({'run':WS.func.msgSender},150,5);
        WS.data.send_buff   = [];
        };

WS.func.onopen = function(event)
        {
        WS.func.status('onopen');
//      WS.func.msgSendTimer();
        WS.func.callCb('connectionOpen');
        };

WS.func.onclose = function(event)
        {
        WS.func.status('onclose');
        WS.data.close_notclosed = false;
        WS.func.callCb('connectionClose');
        };

WS.func.onerror = function(event)
        {
        event = event || window.event;
        WS.func.error(JSON.stringify(event.data || event));
        };

WS.func.onmessage = function(event)
        {
        WS.func.status('onmessage - receive');
        var msg_str;
        event = event || window.event;
//      msg = event && event.data ? (event.data!='' ? WS.decode(event.data) : '') : '';
        msg_str = event && event.data ? event.data : '';
        WS.func.callCb('receive',msg_str);
//      WS.cb.receive(msg)
//      CHAT.receive(msg);
        };


WS.func.callCb = function(name,data)
        {
//alert(WS.data.cb.toSource());
        if (WS.data.cb[name])
                {
                WS.data.cb[name](data);
                }
        }

WS.func.msgSendTimer = function(msg)
        {
        if (WS.conn && WS.conn.readyState==1)
                        {
                        WS.status('msgSendTimer - send');
                        WS.conn.send(msg);
                        return;
                        }
        WS.data.send_buff[WS.data.send_buff.length] = msg;      // add new message to buffer
        WS.data.send_timer.open();
        WS.func.callCb('send',msg);
        }

WS.func.msgSender = function()
        {
        var i, i_end, buff;
        if (WS.data.conn)
                {
                if (WS.data.conn.readyState==1)
                        {
                        buff  = WS.data.send_buff;
                        i_end = buff.length;
                        for (i=0;i<i_end;i++)
                                {
                                WS.func.status('msgSender - send');
                                if (buff[i]!='')
                                        {
                                        WS.data.conn.send(buff[i]);
                                        }
                                }
                        if (WS.data.send_buff.length!=buff.length)      // skip, if added new message
                                {
                                return;
                                }
                        WS.data.send_buff = [];                         // clear, if all sended
                        WS.data.send_timer.close();
                        }
                WS.func.status('msgSender - waiting to send');
                return;
                }
        WS.func.status('msgSender - not connection');
        }

WS.func.connCloseTimer = function()
        {
        if (WS.data.close_notclosed)
                {
                WS.func.status('connCloseTimer');
                WS.data.close_timer.open();
                }
        }

WS.func.connCloser = function()
        {
        if (WS.data.conn)
                {
                if (WS.data.conn.readyState==1)
                        {
                        WS.func.status('connCloser - close');
                        WS.data.conn.close();
                        WS.data.close_notclosed = false;
                        return;
                        }
                WS.func.status('connCloser - waititng to close');
                WS.data.close_notclosed = true;
                return;
                }
        WS.data.close_notclosed = false;
        WS.func.status('connCloser - not connection');
        WS.data.close_timer.close();
        }


WS.func.connect = function()
        {
        var conn;
        WS.func.connCloseTimer();
        WS.func.status('connect');
//      WS.conn = new WS.api(WS.server_url+'?'+query, WS.server_opt);   // + CFG.server_ex.room
        conn = new WS.api(WS.cfg.server_url, WS.cfg.server_opt);        // + CFG.server_ex.room
        conn.onopen    = WS.func.onopen;        // connection open
        conn.onclose   = WS.func.onclose;       // connection close
        conn.onerror   = WS.func.onerror;
        conn.onmessage = WS.func.onmessage;     // receive message
        WS.data.conn = conn;
        };

WS.func.support = function()
        {
        var api;
        api = 'WebSocked';
        if (WS.api)     {WS.func.status(api+' supported');} else {WS.func.error(api+' not supported');}
        api = 'JSON';
        if (JSON)       {WS.func.status(api+' supported');} else {WS.func.error(api+' not supported');}
        }

// public
WS.cmd = {};
WS.cmd.support = function()     {WS.func.support();}
WS.cmd.connect = function()     {WS.func.connect();}
WS.cmd.disconnect = function()  {WS.func.connCloseTimer();}
WS.cmd.send = function(msg_str) {WS.func.msgSendTimer(msg_str);}



//PEERS.construct();
$('server').value = CFG.ws.server_url;
//$('server_data').value     = CFG.server_data.url;
////WS.cmd.support();
////RTC.cmd.support();
WS.func.construct(CFG.ws);
CHAT.func.construct(WS,CFG.chat);
WS.data.cb['receive'] = CHAT.func.receive;
WS.data.cb['connectionOpen'] = CHAT.func.connectionOpen;

fill('peter');
WS.cmd.connect();
//WS.cmd.disconnect();
 
Nahoru Odpovědět 13. září 8:14
Avatar
Peter Mlich
Člen
Avatar
Peter Mlich:13. září 8:21

Tohle by mohl byt puvodni php kod, asi, mozna jen mirne upravyny

<title>WS server</title>
<?php


//$MSG = new class_message;

class class_room
{
private $id, $name, $desc, $users;



private function msgMessage($data)
        {
var_dump('msgMessage',$data);
        if (is_object($data))
                {
                $msg = array(
                        'type'    => 'msguser',
                        'room'    => isset($data->room)    ? $data->room    : '',       //property_exists(), array_key_exists()
                        'name1'   => isset($data->name1)   ? $data->name1   : '',
                        'name2'   => isset($data->name2)   ? $data->name2   : '',
                        'message' => isset($data->message) ? $data->message : '',
                        );
                send_message(mask(json_encode($msg)));
                }
        }
private function msgConnect($ip)
        {
var_dump('msgConnect',$ip);
        $msg = array(
                'type' => 'msgsystem',
                'key'  => 'userjoin',
//              'room' => 'msgsystem',
                'ip'   => $ip
                ); //prepare json data
        send_message(mask(json_encode($msg))); //notify all users about new connection
        }
private function msgDisconnect($ip)
        {
var_dump('msgDisconnect',$ip);
        $msg = array(
                'type' => 'msgsystem',
                'key'  => 'userleave',
//              'room' => 'msgsystem',
                'ip'   => $ip
                );
        send_message(mask(json_encode($msg)));
        }

public function __construct($id=0,$name='',$desc='')
        {
        if ($name=='') {$name = $id;}
        $this->id    = $id;
        $this->name  = $name;
        $this->desc  = $desc;
        $this->users = array();
        }
public function join($ip,$name='?')
        {
        //if (isset($this->users[$name]))
        //      {
        //      }
        //$this->users[$name] = $name;
        $this->msgConnect($ip);
        }
public function leave($ip)
        {
        //unset($this->users[$name]);
        $this->msgDisconnect($ip);
        }
public function message($msg)
        {
        $this->msgMessage($msg);
        }

public function users()
        {
        return $this->users;
        }
}

$ROOM = new class_room(1,'room1','bez popisu');

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, $cfg)
{
        $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'];
        $secKey = isset($headers['Sec-WebSocket-Key']) ? $headers['Sec-WebSocket-Key'] : '';
        $secAccept = base64_encode(pack('H*', sha1($secKey . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')));
        //hand shaking header
        $upgrade  = array(
                "HTTP/1.1 101 Web Socket Protocol Handshake",
                "Upgrade: websocket",
                "Connection: Upgrade",
                "WebSocket-Origin: ".$cfg['host'],
                "WebSocket-Location: ".$cfg['server_url'],
                "Sec-WebSocket-Accept:$secAccept",
                "",
                ""              );
        $upgrade = implode($cfg['header_sep'], $upgrade);
        socket_write($client_conn,$upgrade,strlen($upgrade));
}



//set_time_limit(0);    // how long run server
//set_time_limit(120);  // how long run server
set_time_limit(5);      // how long run server

$cfg = array(
'host' => 'localhost', //host
'port' => '9000', //port
'null' => NULL, //null var
'header_sep' => PHP_EOL ? PHP_EOL : "\r\n"
);
$cfg['server_url'] = "ws://".$cfg['host'].":".$cfg['port']."/demo/ws_server.php";//RTC-chat/php-ws
//echo bin2hex($header_sep[0]).bin2hex($header_sep[1]);

//Create TCP/IP sream socket
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1);//reuseable port
socket_bind($socket, 0, $cfg['port']);//bind socket to specified host
socket_listen($socket);//listen to port
$clients = array($socket);//create & add listning socket to the list

//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); //read data sent by the socket
                perform_handshaking($header, $socket_new, $cfg); //perform websocket handshake
                socket_getpeername($socket_new, $ip); //get ip address of connected socket
//              $response = mask(json_encode(array('type'=>'system', 'message'=>$ip.' connected'))); //prepare json data
//              send_message($response); //notify all users about new connection
$ROOM->join($ip);
                //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)
                {
                        $received_text = unmask($buf); //unmask data
                        $msg = json_decode($received_text); //json decode
$ROOM->message($msg);
                        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')));
//                      send_message($response);
$ROOM->leave($ip);
                }
        }
}
// close the listening socket
socket_close($socket);

js, ktery to obsluhuje by mel vypadat podobne jako v dokumentaci. A nevim, zda to nefunguje jen ve firefoxu. Ale nejspis by to slo obsluhovat normalne pres ajax.
https://developer.mozilla.org/…PI/WebSocket

// Create WebSocket connection.
const socket = new WebSocket('ws://localhost:8080');

// Connection opened
socket.addEventListener('open', function (event) {
    socket.send('Hello Server!');
});

// Listen for messages
socket.addEventListener('message', function (event) {
    console.log('Message from server ', event.data);
});
Editováno 13. září 8:22
 
Nahoru Odpovědět 13. září 8:21
Avatar
Peter Mlich
Člen
Avatar
Peter Mlich:13. září 8:39

Jo, jeste, jak to funguje.
Server

  • spustis si nekonecnou smycku na konkretnim portu
  • smycka posloucha, co se na portu sustne. Obvykle je to ping nebo je tak novy user a nebo se zasila zprava. Musis zjistit, co to je a podle toho zareagovat.

-- ping - muzes ignorovat nebo zareagovat pigme tomu, kdo pingal
-- novy user - pridat sho do tabulky socketu a posles mu hanshake a on ti na nej odpovi, pokud odpovi spravne, tak s nim navazes komunikaci, pokud ne, ukoncis s nim komunikaci, odstranis z tabulky
-- zprava, zjistis si socket usera, zpravu desifrujes (unmasky) a zareagujes, posles treba na vsechny sockety
(Pozor, prvni socket je pro server, tak tam mu nic posilat nemusis. S timhle socketem vsichni komunikuji)
A to je cele.

Pokud chces registraci, autentizaci, musis si to tam implementovat. Tohle resi jen vytvareni a ukoncovni komunikace.
A protoze to bezi neustale, tak by bylo dobre mit tam moznost poslat na socket serveru zpravu, kterou to ukoncis. Pokud ne, tak treba u win to musis zhodit z procesu, jinak xamp server odmita spustit novy server se stejnymi nastavenimi portu a tak :)
To maskovani se tam dela kvuli poruchovosti signalu xorovanim. Bezny texh obsahuje spoustu nul a to maskovani by melo zajistit vetsi stridani nul a jednicek, aby to hw mohl spravne dekodovat na nuly a jednicky. Cili, neni tam implemnetovane ani sifrovani zprav. To si proste musi zaridit user.

Na klienske casti, js/html to funguje tak, ze se ohlasis serveru.
Server ti posle hanshake.
Ty ho nejak zakodujes podle pravidel web-socket komunikace a posles zpet.
A server ti odpovi, zda prijal, nebo dostanes odpoved, ze tvuj socket byl odpojen.
Pokud je vse ok, muzes na socket zasilat zpravy a server je nebude zahazovat.

WebSocket.open - otevres spojeni se serverem
WebSocket.onopen - odpovis na handshake, pripadne muzes hned posilat zpravu
WebSocket.send - odesles message
WebSocket.onmessage - prijeti zpravy
WebSocket.close - odpojeni

Spoustis to opakovane pres interval, kontrolujes socket. Kdyz dlouho nereagujes, server te odpoji. Takze bys mel treba cas od casu poslat prazdnou zpravu.

 
Nahoru Odpovědět 13. září 8:39
Avatar
Odpovídá na Jindřich Máca
Karel Panský:13. září 8:47

Děkuji za odpovědi, ale to opět není to, co potřebuji. Odesílání i příjem zpráv ve Vašich případech opět probíhá pomocí stejné url. Jinak opravdu mi jde o spojení Python - Java, jde o zařízení, které je naprogramované v pythonu a potřebuje na dálku komunikovat pomocí WebSocketu se serverem napsaným v Javě. Vím, opravdu neobvyklé spojení, proto jsem taky skončil tady. :)

 
Nahoru Odpovědět 13. září 8:47
Avatar
Karel Panský:13. září 8:51

Jinak s WebSocketem nejsem žádný nováček. Spojení klient server (JavaScript, Java) je v pohodě. Na problém jsem narazil až mezi těmito jazyky

 
Nahoru Odpovědět 13. září 8:51
Avatar
Peter Mlich
Člen
Avatar
Peter Mlich:13. září 15:30

Aha, tak jestli to cte moderator, tak sim, at odstrani vsechen ten php kod. Jsem netusil, ze to vlozi bez rolovani cele :) Snad si priste vzpomenu a dam kod na externi server, radeji.

Mi slo hlavne o popsani principu. Kdezto to vypada, ze chces kompletni reseni bez prace. Jestli to php vubec nejak funguje, tak by nemel byt problem neco podobneho napsat v pythonu.

 
Nahoru Odpovědět 13. září 15:30
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 8 zpráv z 8.