So Chrome 14 has implemented hybi10 version of websockets. I have a in house program that our company uses via chrome that uses websockets which is broken with this change.
Has anyone been successful framing the data using a php server? I am able to get the new handshake to work but I can't seem to figure out the framing. There is a python example here https://github.com/kanaka/websockify/blob/master/websocket.py#L233 but I am having a difficult time converting this to php, anyone have a suggestion?
I should mention that the function in question on the python example is decode_hybi().
i just completed a class wich makes the PHP-Websocket-Server of Nico Kaiser (https://github.com/nicokaiser/php-websocket) capable of handling hybi-10 frames and handshake. You can download the new class here: http://lemmingzshadow.net/386/php-websocket-serverclient-nach-draft-hybi-10/ (Connection.php)
This code assumes no errors or malformed frames and is based on this answer - How to (de)construct data frames in WebSockets hybi 08+?.
This code is very basic and is far from a complete solution. It works for my purposes (which are pretty basic). Hopefully it is of use to others.
function handle_data($data){
$bytes = $data;
$data_length = "";
$mask = "";
$coded_data = "" ;
$decoded_data = "";
$data_length = $bytes[1] & 127;
if($data_length === 126){
$mask = substr($bytes, 4, 8);
$coded_data = substr($bytes, 8);
}else if($data_length === 127){
$mask = substr($bytes, 10, 14);
$coded_data = substr($bytes, 14);
}else{
$mask = substr($bytes, 2, 6);
$coded_data = substr($bytes, 6);
}
for($i=0;$i<strlen($coded_data);$i++){
$decoded_data .= $coded_data[$i] ^ $mask[$i%4];
}
$this->log("Server Received->".$decoded_data);
return true;
}
Here is the code to send data back. Again this is pretty basic, it assumes you are sending a single text frame. No continuation frames etc. No error checking either. Hopefully others find it useful.
public function send($data)
{
$frame = Array();
$encoded = "";
$frame[0] = 0x81;
$data_length = strlen($data);
if($data_length <= 125){
$frame[1] = $data_length;
}else{
$frame[1] = 126;
$frame[2] = $data_length >> 8;
$frame[3] = $data_length & 0xFF;
}
for($i=0;$i<sizeof($frame);$i++){
$encoded .= chr($frame[$i]);
}
$encoded .= $data;
write_to_socket($this->socket, $encoded);
return true;
}
Related
I'm trying to generate an access token in php for user authorization
I used the following:
base64_encode(com_create_guid());
is this correct use in this context? and what is meant by "cryptographically secure"?
EDIT:
First, I was going to generate a random string using a function like this,
function generateRandomString($length = 10) {
$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$charactersLength = strlen($characters);
$randomString = '';
for ($i = 0; $i < $length; $i++) {
$randomString .= $characters[rand(0, $charactersLength - 1)];
}
return $randomString;
}
but then I read about the token has to be cryptographically secure, and I can't really get what is this exactly or precisely.
then I went to com_create_guid() , but I noticed that com_create_guid() generates UUID, which does not include all characters (just numbers + a-f of hex). So I thought that base64 encoding may be suitable to convert this into a full character token (not for security).
here I'm asking about this being suitable for generating an access token, and if not is there a better way for generating it?
Returns a random string with crypto-strength given in $len. The string is always longer than $len by 3-4 expansion (that is, it takes 4 characters to encode 3 length). Pass a $len of at least 16 for good strength.
Stolen from https://stackoverflow.com/a/8429944/14768 and edited to use /dev/random instead of /dev/urandom
function generateRandomString($len)
{
$fp = #fopen('/dev/random','rb');
$result = '';
if ($fp !== FALSE) {
$result .= #fread($fp, $len);
#fclose($fp);
}
else
{
trigger_error('Can not open /dev/urandom.');
}
// convert from binary to string
$result = base64_encode($result);
// remove none url chars
$result = strtr($result, '+/', '-_');
// Remove = from the end
$result = str_replace('=', ' ', $result);
return $result;
}
When wanting crypt-strength stuff use crypt-strength stuff. guidv4 is not guaranteed to be crypt-strength.
You might want to use this:
function guidv4()
{
if (function_exists('com_create_guid') === true)
return trim(com_create_guid(), '{}');
$data = openssl_random_pseudo_bytes(16);
$data[6] = chr(ord($data[6]) & 0x0f | 0x40); // set version to 0100
$data[8] = chr(ord($data[8]) & 0x3f | 0x80); // set bits 6-7 to 10
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}
source: http://php.net/manual/en/function.com-create-guid.php#117893
This code is more cryptographically secure, however, for what you are doing you should be fine.
Cryptographically secure in layman's terms is something that is random, and is hard to recreate or predict.
Hope this wasn't solved here before, but I was really trying to look for answer!
I Have a problem with receiving frames via socket_recv in PHP server. Problem appears when I send more than 1 messages from client in one time (e.g. in cycle). By the stream length it's match frames count but I'm not able to unmask frames correctly and my unmask function return only first frame. I'm using part of php server I found here and for one message it's working properly, but when I receive more messages at time, it don't unmask all.
I tryed to add loop to go thru all frames, but I'm not able to find end of frame and continue to other.
Here is main while of server:
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, 10240); //read data sent by the socket
perform_handshaking($header, $socket_new, $host, $port); //perform websocket handshake
socket_getpeername($socket_new, $ip); //get ip address of connected socket
$response = mask(json_encode(array('type' => 'system', 'status' => true, "id" => "SRV_CONNECTED", 'message' => $ip . ' connected'))); //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]);
}
foreach ($changed as $changed_socket) {
while (#socket_recv($changed_socket, $buf, 1024, 0) >= 1) {
$received_text = unmask($buf); //unmask data
$tst_msg = json_decode($received_text, true); //json decode
$response_text = parse_msg($tst_msg, $changed_socket);
break 2; //exit this loop
}
$buf = #socket_read($changed_socket, 10240, 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);
}
}
I Need to call parse_msg by count of frames received in #socket_recv which should be returned by unmask function.
And here is unmask function:
function unmask($payload){
$decMessages = Array();
do { // This should be running until all frames are unmasked and added to $decMessages Array
$length = ord($payload[1]) & 127;
if($length == 126) {
$masks = substr($payload, 4, 4);
$data = substr($payload, 8);
$len = (ord($payload[2]) << 8) + ord($payload[3]);
}elseif($length == 127) {
$masks = substr($payload, 10, 4);
$data = substr($payload, 14);
$len = (ord($payload[2]) << 56) + (ord($payload[3]) << 48) +
(ord($payload[4]) << 40) + (ord($payload[5]) << 32) +
(ord($payload[6]) << 24) +(ord($payload[7]) << 16) +
(ord($payload[8]) << 8) + ord($payload[9]);
}else {
$masks = substr($payload, 2, 4);
$data = substr($payload, 6);
$len = $length;
}
$text = '';
for ($i = 0; $i < $len; ++$i) {
$text .= $data[$i] ^ $masks[$i%4];
}
$decMessages[] = $text;
// Here is problem. It doesn't put correct substr to $payload, so it could run again on stream shorted by last message
$payload = substr($payload, $length, strlen($payload));
}while (($len < strlen($data)) and $countert < 10);
return $decMessages;
}
I think my problem is here: $payload = substr($payload, $length, strlen($payload)); Where I'm putting wrongly rest of stream?
So my questions are:
Why #socket_recv return more than one frame
How can i change my unmask function to go thru all frames, not just a first one
I will be extremelly greatfull for any help, because I'm fighting with this more than I should.
Thanks in advance
Klara!
so, I found solution for this. As I thought, problem was multiple frames in one receive. I needed to edit my unmask function to check if there are still frames in stream.
Basically, what I had was right but $length in
$payload = substr($payload, $length, strlen($payload));
was wrong. Instead of $length I used $len + 8, 14 od 6 depend on $length. I'm not sure how to describe why this but I'm sure somebody will. That's all. Now it's working as it should!
Thanks!
I set up a basic video chat app using the WebRTC APIs in Chrome along with a WebSocket script I wrote myself following the W3C specs and other questions here on SO.
Sometimes though, when one PC sends ICE candidate info to the other PC via the WebSocket connection, a bunch of garbled text is attached to the end of the JSON-stringified candidate info.
This problem only happens sometimes though, and it never happens with the SDP info sent via the createOffer and createAnswer methods.
Please see the following link for an example of what I'm talking about:
http://s1290.beta.photobucket.com/user/HartleySan83/media/NGdata_zps0a7203e7.png.html?sort=3&o=0
Because the JSON-stringified candidate info always ends with '}}', by adding an if condition to the WebSocket server script, I was able to circumvent this problem and get the video chat app to work. Unfortunately, this is a hack that I'd like to avoid. Plus, I'd like to know why this is happening in the first place.
It's worth noting that when I either alert or echo the candidate info to the console on the client side before it's sent to the WebSocket server script, none of the extra garbled text is present, so I'm not sure why it's present with the candidate info on the server side and only sometimes.
The following is a code snippet of the client-side code where the candidate info is sent to the server-side script:
function startPeerConnection() {
navigator.webkitGetUserMedia({ audio: true, video: true }, function (stream) {
document.getElementById('vid1').src = webkitURL.createObjectURL(stream);
pc = new webkitRTCPeerConnection(null);
pc.onicecandidate = function (evt) {
if (evt.candidate) {
socket.send(JSON.stringify({ candidate: evt.candidate }));
}
};
pc.onaddstream = function (evt) {
document.getElementById('vid2').src = webkitURL.createObjectURL(evt.stream);
};
pc.addStream(stream);
}, function () {});
}
And the following is the server-side code that unmasks the received WebSocket data:
$len = ord($buffer[1]) & 127;
if ($len === 126) {
$masks_start = 4;
} else if ($len === 127) {
$masks_start = 10;
} else {
$masks_start = 2;
}
$masks = substr($buffer, $masks_start, 4);
$data = substr($buffer, $masks_start + 4);
$len = strlen($data);
$text = '';
for ($i = 0; $i < $len; $i++) {
$text .= $data[$i] ^ $masks[$i % 4];
}
if (($end = strpos($text, '}}')) !== false) {
// This if condition eliminates the garbled text.
// Without it, a "Could not decode a text frame as UTF-8"
// error is output to the Chrome console.
$text = substr($text, 0, $end + 2);
$len = strlen($text);
}
if ($len <= 125) {
$header = pack('C*', 129, $len);
} else if (($len > 125) && ($len < 65536)) {
$header = pack('C*', 129, 126, ($len >> 8) & 255, $len & 255);
} else if ($len >= 65536) {
$header = pack('C*', 129, 127, ($len >> 56) & 255, ($len >> 48) & 255, ($len >> 40) & 255, ($len >> 32) & 255, ($len >> 24) & 255, ($len >> 16) & 255, ($len >> 8) & 255, $len & 255);
}
$server_response = $header . $text;
foreach ($users as $user) {
if ($user !== $users[$user_idx]) {
#socket_write($user['socket'], $server_response, strlen($server_response));
}
}
I've searched high and low on the Internet for anyone else with the same problem, but I can't find anyone or anything in the specs that talks about this, so I imagine it's some problem with my code.
Any guidance that anyone can offer as to the source of the problem would be much appreciated.
Thank you.
Well, I finally found the problem. My server-side WebSocket code was indeed wrong. The problem was that I was miscalculating the length. I, unfortunately, was relying on some page I found about WebSockets in PHP, and as it turns out, the page had a number of errors in its code, which I slowly started to realize more and more. Anyway, here's the proper way to calculate the length of messages sent from a client to the server:
$len = ord($buffer[1]) & 127; // This is the default payload length.
if ($len === 126) { // If 126, then need to use the payload length at the 3rd and 4th bytes.
$masks_start = 4;
$len = (ord($buffer[2]) << 8) + ord($buffer[3]);
} else if ($len === 127) { // If 127, then need to use the next 8 bytes to calculate the length.
$masks_start = 10;
$len = (ord($buffer[2]) << 56) + (ord($buffer[3]) << 48) + (ord($buffer[4]) << 40) + (ord($buffer[5]) << 32) + (ord($buffer[6]) << 24) + (ord($buffer[7]) << 16) + (ord($buffer[8]) << 8) + ord($buffer[9]);
} else { // Otherwise, the default payload length is correct.
$masks_start = 2;
}
After doing that, everything worked great. Well, I'm still haven't figured out how to properly close a WebSocket connection, but other than that, the WebRTC video is working great.
Is the garbled binary data being added by client1 before it sends it to the Websocket server? Or are you only seeing it on the client2 after it's been processed by the websocket server? I ask because I ran into a similar problem where my signaling server (SignalR in this case) had a bug that corrupted the SDP I was sending in-between PeerConnections.
Hi I have a running socket server written with PHP.
The server is listening for connections.. any idea how my client(written in javascript) is going to connect to the server and send data to it?
PS: I only know how to connect a php client to the socket server but unsure how to connect a javascript client.
Thanks all for your time.
I use standard WebSocket API for client.
And core PHP socket for server side.
know, send and received data use a header on the browser with websocket. But the code PHP socket, send and received without header and just send plain data.
So we need to simulate header on the socketing server side.
For learning and know how do it, I write this clear sample code, With this code you can send a phrase to server and receive reverse phrase that in client.
server.php
<?php
//Code by: Nabi KAZ <www.nabi.ir>
// set some variables
$host = "127.0.0.1";
$port = 5353;
// don't timeout!
set_time_limit(0);
// create socket
$socket = socket_create(AF_INET, SOCK_STREAM, 0)or die("Could not create socket\n");
// bind socket to port
$result = socket_bind($socket, $host, $port)or die("Could not bind to socket\n");
// start listening for connections
$result = socket_listen($socket, 20)or die("Could not set up socket listener\n");
$flag_handshake = false;
$client = null;
do {
if (!$client) {
// accept incoming connections
// client another socket to handle communication
$client = socket_accept($socket)or die("Could not accept incoming connection\n");
}
$bytes = #socket_recv($client, $data, 2048, 0);
if ($flag_handshake == false) {
if ((int)$bytes == 0)
continue;
//print("Handshaking headers from client: ".$data."\n");
if (handshake($client, $data, $socket)) {
$flag_handshake = true;
}
}
elseif($flag_handshake == true) {
if ($data != "") {
$decoded_data = unmask($data);
print("< ".$decoded_data."\n");
$response = strrev($decoded_data);
socket_write($client, encode($response));
print("> ".$response."\n");
socket_close($client);
$client = null;
$flag_handshake = false;
}
}
} while (true);
// close sockets
socket_close($client);
socket_close($socket);
function handshake($client, $headers, $socket) {
if (preg_match("/Sec-WebSocket-Version: (.*)\r\n/", $headers, $match))
$version = $match[1];
else {
print("The client doesn't support WebSocket");
return false;
}
if ($version == 13) {
// Extract header variables
if (preg_match("/GET (.*) HTTP/", $headers, $match))
$root = $match[1];
if (preg_match("/Host: (.*)\r\n/", $headers, $match))
$host = $match[1];
if (preg_match("/Origin: (.*)\r\n/", $headers, $match))
$origin = $match[1];
if (preg_match("/Sec-WebSocket-Key: (.*)\r\n/", $headers, $match))
$key = $match[1];
$acceptKey = $key.'258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
$acceptKey = base64_encode(sha1($acceptKey, true));
$upgrade = "HTTP/1.1 101 Switching Protocols\r\n".
"Upgrade: websocket\r\n".
"Connection: Upgrade\r\n".
"Sec-WebSocket-Accept: $acceptKey".
"\r\n\r\n";
socket_write($client, $upgrade);
return true;
} else {
print("WebSocket version 13 required (the client supports version {$version})");
return false;
}
}
function unmask($payload) {
$length = ord($payload[1]) & 127;
if ($length == 126) {
$masks = substr($payload, 4, 4);
$data = substr($payload, 8);
}
elseif($length == 127) {
$masks = substr($payload, 10, 4);
$data = substr($payload, 14);
}
else {
$masks = substr($payload, 2, 4);
$data = substr($payload, 6);
}
$text = '';
for ($i = 0; $i < strlen($data); ++$i) {
$text .= $data[$i] ^ $masks[$i % 4];
}
return $text;
}
function encode($text) {
// 0x1 text frame (FIN + opcode)
$b1 = 0x80 | (0x1 & 0x0f);
$length = strlen($text);
if ($length <= 125)
$header = pack('CC', $b1, $length);
elseif($length > 125 && $length < 65536)$header = pack('CCS', $b1, 126, $length);
elseif($length >= 65536)
$header = pack('CCN', $b1, 127, $length);
return $header.$text;
}
client.htm
<html>
<script>
//Code by: Nabi KAZ <www.nabi.ir>
var socket = new WebSocket('ws://localhost:5353');
// Open the socket
socket.onopen = function(event) {
var msg = 'I am the client.';
console.log('> ' + msg);
// Send an initial message
socket.send(msg);
// Listen for messages
socket.onmessage = function(event) {
console.log('< ' + event.data);
};
// Listen for socket closes
socket.onclose = function(event) {
console.log('Client notified socket has closed', event);
};
// To close the socket....
//socket.close()
};
</script>
<body>
<p>Please check the console log of your browser.</p>
</body>
</html>
Manual: first run php server.php on CLI and then open http://localhost/client.htm on browser.
You can see result:
http://localhost/client.htm
> I am the client.
< .tneilc eht ma I
php server.php
< I am the client.
> .tneilc eht ma I
Be careful it's just a sample code for test send and receive data, And it is not useful for executive work.
I suggest you use these projects:
https://github.com/ghedipunk/PHP-Websockets
https://github.com/esromneb/phpwebsocket
https://github.com/acbrandao/PHP/tree/master/ws
https://github.com/srchea/PHP-Push-WebSocket/
http://socketo.me/
And also I suggest you these articles for more details:
http://www.abrandao.com/2013/06/websockets-html5-php/
http://cuelogic.com/blog/php-and-html5-websocket-server-and-client-communication/
http://srchea.com/build-a-real-time-application-using-html5-websockets
Answering an old question in case people find it as I did via Google.
Nowadays nearly all contemporary browsers support the WebSocket Javascript API. Via WS it's possible for client JS in the browser to open full duplex sockets to severs written in PHP or other languages. The server must implement the WS protocol, but there are WS libraries now for PHP, Java, and other languages.
At this moment of writing, WS implementations still seem like a bit of a moving target, but, I'm currently working with WS/JS browser clients communicating with a WS/Java server and it does seem to be working.
Suggest Googling for WS implementations in your server language of choice.
Hope this helps!
I'm not aware of anything that provides arbitrary socket capabilities for JS. There is limited support for Web Sockets (which I think will require you to modify the server to conform to the space). Failing that, simple XHR might meet your needs (which would require that you modify the server to act as a web service). If the service runs on a different origin to the page, then you will need to use CORS or use a work around such as JSONP.
Try this:
http://code.google.com/p/phpwebsocket/
Shortly saying - you can't do that - it would be a security breach to let client side code open socket connections.
However, you could simulate that - send your data to another PHP page as an AJAX request, then make that PHP page communicate through the socket.
Update 2017:
In the mean time, websockets became a thing. Please note that the websocket protocol is a different thing than generic networking sockets
I am able to make a simple php websocket server with libevent , but I am stuck when I'm trying to make it multiprocessing.
for example this is single processing
<?php
$socket = stream_socket_server ('tcp://0.0.0.0:2000', $errno, $errstr);
stream_set_blocking($socket, 0);
$base = event_base_new();
$event = event_new();
event_set($event, $socket, EV_READ | EV_PERSIST, 'ev_accept', $base);
event_base_set($event, $base);
event_add($event);
event_base_loop($base);
$GLOBALS['connections'] = array();
$GLOBALS['buffers'] = array();
function ev_accept($socket, $flag, $base) {
static $id = 0;
$connection = stream_socket_accept($socket);
stream_set_blocking($connection, 0);
$id += 1;
$buffer = event_buffer_new($connection, 'ev_read', NULL, 'ev_error', $id);
event_buffer_base_set($buffer, $base);
event_buffer_timeout_set($buffer, 30, 30);
event_buffer_watermark_set($buffer, EV_READ, 0, 0xffffff);
event_buffer_priority_set($buffer, 10);
event_buffer_enable($buffer, EV_READ | EV_PERSIST);
// we need to save both buffer and connection outside
$GLOBALS['connections'][$id] = $connection;
$GLOBALS['buffers'][$id] = $buffer;
}
function ev_error($buffer, $error, $id) {
event_buffer_disable($GLOBALS['buffers'][$id], EV_READ | EV_WRITE);
event_buffer_free($GLOBALS['buffers'][$id]);
fclose($GLOBALS['connections'][$id]);
unset($GLOBALS['buffers'][$id], $GLOBALS['connections'][$id]);
}
function ev_read($buffer, $id) {
while ($read = event_buffer_read($buffer, 256)) {
var_dump($read);
}
}
?>
But when I do this in function ev_read
function ev_read($buffer, $id) {
while ($read = event_buffer_read($buffer, 256)) {
$pid = pcntl_fork();
switch ($pid) {
case -1: // Error
die('Fork failed, your system is b0rked!');
break;
case 0: // Child
event_buffer_write($buffer,"asdawdasd");
exit(0);
break;
}
} }
it doesnt send the data...
So how can I make a multiprocessing php socket server?
While nanoserv is an excellent library, it does not use libevent. Infact the author himself has written, in his blog, that he would like to convert nanoserv to use libevent at some point of time. See his blog post here: http://blog.si.kz/index.php/2010/02/03/libevent-for-php
There is also a comment by Alix Axel on May 22 '11 at 12:19 regarding the same.
Update: A little more research led me to http://phpdaemon.net/ . It seems they are using libevent to build a whole host of network servers
Updated 30th May 2021:
Recently there has been https://www.swoole.co.uk/ & https://reactphp.org/ which provide good async libraries for sockets.
phpdaemon has a new url at https://daemon.io/
Updated 31st August 2022:
Swoole has now been forked into two separate projects: Open Swoole & Swoole