<?PHP
class Server
{
var $host;
var $port;
var $socket;
var $model;
var $clients;
function __construct($host,$port)
{
$this->host = $host;
$this->port = $port;
$this->socket=null;
$this->model = new Model_server();
}
public function run() //Запуск сервера
{
$this->start();
$this->clients = array($this->socket); $null=null;
do
{
$read = $this->clients;
if (socket_select($read, $write = NULL, $except = NULL, 0) < 1) //ждем нового события {
continue;
}
if (in_array($this->socket, $read)) //принимаем новые подключения {
// accept the client, and add him to the $clients array
// send the client a welcome message
$this->handshake($data,$newsock);
$msg = $newsock." connected to the server";
$this->send($msg);
$msg = "<hr>".$msg."<hr>\n";
$this->console($msg);
//socket_getpeername($newsock, $ip);
//echo " Client ip: {$ip}\n<hr>";
// remove the listening socket from the clients-with-data array
}
foreach ($read as $read_sock)
{
{
continue;
}
// проверка подключен ли пользователь
if ($data === false)
{
// удаление клиента из массива
$this->close($read_sock);
continue;
}
else
{
// декодируем сообщение
$data = $this->decode($data);
$msg = explode(":",$data['payload']);
if($data['payload']=='close')
{
$this->close($read_sock);
continue;
}
elseif($data['payload']=="online")
{
$this->console(count($this->clients)." users online<br />\n"); $this->sendMessage(count($this->clients)." users online",$read_sock); }
else
{
if($data['payload']!="")
{
$msg = trim($data['payload']); if($msg!=' ' && $msg!="")
{
$msg = $read_sock." Say: ".$msg;
$this->send($msg);
$this->console($msg."<br />\n");
}
}
}
}
}
$this->event();
}
while(true);
//
$this->shutdown();
}
//////////////////////////////////////////////////////////////
private function console($msg,$decode=false) //вывод сообщения присланные клиентом на сервере
{
echo iconv('windows-1251','utf-8' ,$msg); }
//////////////////////////////////////////////////////////////
private function send($msg,$type = 'text', $masked = false)//Отправка сообщения пользователю
{
$msg = $this->encode($msg,$type,$masked);
$i=1;
$num=0;
foreach ($this->clients as $read_sock)
{
if($num>=1)
{
}
$num++;
}
}
/////////////////////////////////////////////////////////////
private function sendMessage($msg,$read_sock,$type = 'text', $masked = false)//Отправка сообщения пользователю
{
$msg = $this->encode($msg,$type,$masked);
}
//////////////////////////////////////////////////////////////
private function close($read_sock) //Закрывает конкретное соединение
{
{
//socket_close($read_sock);
{
unset($this->clients[$key]); }
$this->console($read_sock." Disconnected <br />");
$this->send($read_sock." Disconnected");
}
}
//////////////////////////////////////////////////////////////
private function shutdown() //Остановка всего сервера
{
if (isset($this->socket)) {
$this->console("<br /><hr>Server shutdown---OK <br /><hr>");
return;
}
}
//////////////////////////////////////////////////////////////
private function start()
{
$this->console("-=SERVER v2.1=-<hr>\n");
$this->console("Socket create ------");
if(($this->socket = socket_create(AF_INET
, SOCK_STREAM
, SOL_TCP
))===false) {
}
else
{
$this->console("OK<br />\n");
}
$this->console("Socket bind --------");
//привязываем его к указанным ip и порту
if((socket_bind($this->socket, $this->host, $this->port))===false) {
}
else
{
$this->console("OK <br />\n");
}
socket_set_option($this->socket, SOL_SOCKET
, SO_REUSEADDR
, 1
);//разрешаем использовать один порт для нескольких соединений
$this->console("Socket listening-----");
{
}
else
{
$this->console("OK <br />\n");
}
$this->console("Server started-------OK<hr>\n");
//
}
//////////////////////////////////////////////////////////////
private function handshake($receved_header,$client_conn)//выполняет рукопожатие сервера и клиента
{
foreach($lines as $line)
{
if(preg_match('/\A(\S+): (.*)\z/', $line, $matches)) {
$headers[$matches[1]] = $matches[2];
}
}
$secKey = $headers['Sec-WebSocket-Key'];
//hand shaking header
$upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" .
"Upgrade: websocket\r\n" .
"Connection: Upgrade\r\n" .
"Sec-WebSocket-Origin: http://$this->host\r\n" .
"Sec-WebSocket-Location: ws://$this->host:$this->port\r\n".
"Sec-WebSocket-Accept: $secAccept\r\n\r\n";
}
//////////////////////////////////////////////////////////////
private function decode($data) //Декорирует сообщение пришедшее от пользователся
{
$payloadLength = '';
$mask = '';
$unmaskedPayload = '';
// estimate frame type:
$firstByteBinary = sprintf('%08b', ord($data[0
])); $secondByteBinary = sprintf('%08b', ord($data[1
])); $isMasked = ($secondByteBinary[0] == '1') ? true : false;
$payloadLength = ord($data[1
]) & 127;
// close connection if unmasked frame is received:
if($isMasked === false)
{
//$this->close(1002);
}
switch($opcode)
{
// text frame:
case 1:
$decodedData['type'] = 'text';
break;
case 2:
$decodedData['type'] = 'binary';
break;
// connection close frame:
case 8:
$decodedData['type'] = 'close';
break;
// ping frame:
case 9:
$decodedData['type'] = 'ping';
break;
// pong frame:
case 10:
$decodedData['type'] = 'pong';
break;
default:
// Close connection on unknown opcode:
//$this->close(1003);
break;
}
if($payloadLength === 126)
{
$payloadOffset = 8;
}
elseif($payloadLength === 127)
{
$payloadOffset = 14;
$tmp = '';
for($i = 0; $i < 8; $i++)
{
}
$dataLength = bindec($tmp) + $payloadOffset; }
else
{
$payloadOffset = 6;
$dataLength = $payloadLength + $payloadOffset;
}
/**
* We have to check for large frames here. socket_recv cuts at 1024 bytes
* so if websocket-frame is > 1024 bytes we have to wait until whole
* data is transferd.
*/
if(strlen($data) < $dataLength) {
return false;
}
if($isMasked === true)
{
for($i = $payloadOffset; $i < $dataLength; $i++)
{
$j = $i - $payloadOffset;
{
$unmaskedPayload .= $data[$i] ^ $mask[$j % 4];
}
}
$decodedData['payload'] = $unmaskedPayload;
}
else
{
$payloadOffset = $payloadOffset - 4;
$decodedData['payload'] = substr($data, $payloadOffset); }
return $decodedData;
}
//////////////////////////////////////////////////////////////
private function encode($payload, $type, $masked)//Кодирует сообщение перед отправкой пользователю
{
$frame = '';
$payloadLength = strlen($payload);
switch($type)
{
case 'text':
// first byte indicates FIN, Text-Frame (10000001):
$frameHead[0] = 129;
break;
case 'close':
// first byte indicates FIN, Close Frame(10001000):
$frameHead[0] = 136;
break;
case 'ping':
// first byte indicates FIN, Ping frame (10001001):
$frameHead[0] = 137;
break;
case 'pong':
// first byte indicates FIN, Pong frame (10001010):
$frameHead[0] = 138;
break;
}
// set mask and payload length (using 1, 3 or 9 bytes)
if($payloadLength > 65535)
{
$frameHead[1] = ($masked === true) ? 255 : 127;
for($i = 0; $i < 8; $i++)
{
$frameHead[$i+2
] = bindec($payloadLengthBin[$i]); }
// most significant bit MUST be 0 (close connection if frame too big)
if($frameHead[2] > 127)
{
//$this->close(1004);
return false;
}
}
elseif($payloadLength > 125)
{
$frameHead[1] = ($masked === true) ? 254 : 126;
$frameHead[2
] = bindec($payloadLengthBin[0
]); $frameHead[3
] = bindec($payloadLengthBin[1
]); }
else
{
$frameHead[1] = ($masked === true) ? $payloadLength + 128 : $payloadLength;
}
// convert frame-head to string:
{
$frameHead[$i] = chr($frameHead[$i]); }
if($masked === true)
{
// generate a random mask:
for($i = 0; $i < 4; $i++)
{
}
}
// append payload to frame:
for($i = 0; $i < $payloadLength; $i++)
{
$frame .= ($masked === true) ? $payload[$i] ^ $mask[$i % 4] : $payload[$i];
}
return $frame;
}
}