PHP Websockets Tutorial

9th October 2013   4007 views

Websockets are not fully implemented yet so it is very hard to find a decent tutorial on the internet. I once found a tutorial(phpWebSockets by Moritz Wutz and http://srchea.com/blog/2011/12/build-a-real-time-application-using-html5-websockets/) that used classes to implement a pretty good WebSockets server. However that tutorial provided the code with bugs and not a lot of explanation as to how it works and how can you add more to it. I decided that if I wanted to make my own WS server my best bet would be to take that code and upgrade it so that it works and acts as I want it to. After I got the prototype working I decided I need to share my code with others so that they won't have to go through all that trouble again. This tutorial is not meant to teach you how to write your own server, merely it explains how my code works.

Download the full code here

Demo

Firts off we need some basic HTML structure. This is just the code the original tutorial provided striped off of the unnecessary parts.

1 <div id="wrapper">
2     <div id="container">    
3         <h1>WebSockets Client</h1>
4         <button id="disconnect">Disconnect</button>
5         <span id="status"></span>
6         <span id="ping"></span>
7         <div id="chatLog">
8         
9         </div>
10         <br />
11         <input id="text" type="text" placeholder="type in message"/>
12     </div>
13     <div id="users">
14     
15     </div>
16 </div>

Then the css which is nothing special, again almost left unchaged.

1 body{font-family:ArialHelveticasans-serif;}
2 #container{
3     border:5px solid grey;
4     width:670px;
5     margin:0 auto;
6     padding:10px;
7     float:left;
8 }
9 #users {
10     float:right;
11     width:220px;
12     border2px solid grey;
13     padding2px 0 2px 4px;
14 }
15 #chatLog{
16     padding:5px;
17     border:1px solid black;    
18     height400px;
19     overflowauto;
20 }
21 #chatLog p {
22     margin:0;
23 }
24 .event{
25     color#999999;
26 }
27 .warning{
28     font-weight:bold;
29     color#CCC;
30 }

Javascript

I used JQuery library in this project.

1 $(document).ready(function() 
2 {
3     $('#chatLog, input, button, #examples').hide();
4     if(!("WebSocket" in window)){    
5         $('<p>Oh no, you need a browser that supports WebSockets. How about <a href="http://www.google.com/chrome">Google Chrome</a>?</p>').appendTo('#container');        
6     }
7     else
8     {
9         //The user has WebSockets
10         var socket;
11         var host "ws://109.255.177.28:8080";
12         var reconnect=true;
13         var ping;
14         var pingInt;
15     
16         function wsconnect()
17         {
18             try{
19                 socket = new WebSocket(host);
20                 socket.onopen = function(){
21                     status('<p class="event">Socket Status: '+socket.readyState+' (open)');
22                     socket.send('login: '+name);
23                     pingInt setInterval(ping1000);
24                 }
25                 
26                 socket.onerror = function(error){
27                     status('<p class="event">Socket Status: '+socket.readyState+' ('+error+')');    
28                 }
29                 
30                 //receive message
31                 socket.onmessage = function(msg){
32                     var data JSON.parse(msg.data);
33                     if(data.message!=undefined)
34                     {
35                         message('<p class="message">'+data.message+'</p>');
36                     }
37                     if(data.ping!=undefined)
38                     {
39                         var time = new Date().getTime();
40                         time time ping;
41                         $('#ping').html('Ping: '+time+'ms');
42                     }
43                     if(data.users!=undefined)
44                     {
45                         var str "";
46                         var i;
47                         for(i in data.users)
48                         {
49                             str += data.users[i]+'<br />';
50                         }
51                         $('#users').html('<h2>Users loged in:</h2>'+str);
52                     }
53                 }
54                 
55                 socket.onclose = function(e){
56                     clearInterval(pingInt);
57                     if(reconnect){
58                         wsconnect();
59                     }
60                     status('<p class="event">Socket Status: '+socket.readyState+' (Closed)');
61                 }            
62                     
63             } catch(exception){
64                 message('<p>Error '+exception);
65             }
66         }
67         //End connect()    
68         
69         function ping()
70         {
71             if(reconnect)
72             {
73                 ping = new Date().getTime();
74                 socket.send('ping');
75             }
76         }
77             
78         function send()
79         {
80             var text = $('#text').val();
81             if(text==""){
82                 alert('Please enter a message');
83                 return ;    
84             }
85             try{
86                 socket.send(text);
87                 message('<p class="event">'+name+': '+text)
88             } catch(exception){
89                 message('<p class="warning">'+exception);
90             }
91             $('#text').val("");//clear text input field
92         }
93         
94         function message(msg)
95         {
96             $('#chatLog').append(msg);
97             $('#chatLog').scrollTop($('#chatLog')[0].scrollHeight);
98         }
99         
100         function status(msg)
101         {
102             $('#status').html(msg+'</p>');
103         }
104         
105         $('#text').keypress(function(event
106         {
107             if (event.keyCode == '13') {
108                 send();
109             }
110         });    
111         
112         $('#disconnect').click(function()
113         {
114             reconnect=false;//dont try to reconnect again
115             socket.close();
116         });
117
118         var name='';
119         while(name=='')
120             name prompt("Your name:");
121         if(name!=null)
122         {
123             $('#chatLog, input, button, #examples').fadeIn("fast");
124             wsconnect();
125         }
126     }
127 });

Ok, so there's our client. First off we check if the browser supports WebSockets. We obviously need an address to which we will connect: var host = "ws://109.255.177.28:8080" ; The function 'wsconnect' is the main function, when you call it the browser will try to estabilish connection. Function named 'send' is called when there is a message to be send from the user to the server and 'socket.send(text);' takes care of that, the rest is just checking if there's any input. The other functions are just for easier putting/getting data on our website and they are self explanatory. socket.onmessage = function( msg ){ That method is called whenever a message comes from the server. I use JSON to pass data as it is the easient way to manage data transmitted between PHP and JavaScript IMHO. 'data' contains the data from server already encoded. The if statement checks what kind of data was received and carries out approbiate action(plain message, ping response, info about logged on users). socket.onclose = function(e){ The onclose method is called if the connection has been terminated. If the connection was not terminated by user it tries to connect again.

PHP

The socket.class.php file and the daemon file was almost left untouched by me so I will not bother with explanation.

I will only discuss the most important parts of the main file.

1 private function run()
2 {
3     while(true)
4     {
5         $this->i++;
6         # because socket_select gets the sockets it should watch from $changed_sockets
7         # and writes the changed sockets to that array we have to copy the allsocket array
8         # to keep our connected sockets list
9         $changed_sockets $this->allsockets;
10
11         # blocks execution until data is received from any socket
12         //wait 1ms(1000us) - should theoretically put less pressure on the cpu
13         $num_sockets socket_select($changed_sockets,$write=NULL,$exceptions=NULL,0,1000);
14
15         # foreach changed socket...
16         foreach( $changed_sockets as $socket )
17         {
18             # master socket changed means there is a new socket request
19             if( $socket==$this->master )
20             {
21                 # if accepting new socket fails
22                 if( ($client=socket_accept($this->master)) < )
23                 {
24                     $this->console('socket_accept() failed: reason: ' socket_strerror(socket_last_error($client)));
25                     continue;
26                 }
27                 # if it is successful push the client to the allsockets array
28                 else
29                 {
30                     $this->allsockets[] = $client;
31
32                     # using array key from allsockets array, is that ok?
33                     # i want to avoid the often array_search calls
34                     $socket_index array_search($client,$this->allsockets);
35                     $this->clients[$socket_index] = new stdClass;
36                     $this->clients[$socket_index]->socket_id $client;
37
38                     $this->console($client ' CONNECTED!');
39                 }
40             }
41             # client socket has sent data
42             else
43             {
44                 $socket_index array_search($socket,$this->allsockets);
45                 $user $this->users[$socket_index];
46
47                 # the client status changed, but theres no data ---> disconnect
48                 $bytes = @socket_recv($socket,$buffer,2048,0);
49                 if( $bytes === )
50                 {
51                     $this->disconnected($socket);
52                 }
53                 # there is data to be read
54                 else
55                 {
56                     # this is a new connection, no handshake yet
57                     if( !isset($this->handshakes[$socket_index]) )
58                     {
59                         $this->do_handshake($buffer,$socket,$socket_index);
60                     }
61                     # handshake already done, read data
62                     else
63                     {
64                         $action $this->unmask($buffer);
65                         if($action=='')
66                         {    
67                             $this->disconnected($socket);
68                             continue;
69                         }
70                         
71                         //that was some hack I forgot to properly comment and I dont know what it does
72                         if($action==chr(3).chr(233))
73                         {    
74                             $this->disconnected($socket);
75                             continue;
76                         }
77                         
78                         $output = array();
79                         if(($pos=strpos($action,'login'))===&& $user=='')
80                         {
81                             $name substr($action,$pos+7);
82                             $this->users[$socket_index] = $name;
83                             $this->console('Loged in as: '.$name);
84                         }
85                         else if($action=="ping")
86                         {
87                             $output['ping'] = true;
88                             $this->send($socketjson_encode($output));
89                         }
90                         else
91                         {    
92                             $skipSockets = array($this->master,$socket);
93                             $them array_diff($this->allsockets,$skipSockets);
94                             $output['message'] = $user.': '.$action;
95                             foreach($them as $sock)
96                             {
97                                 $this->send($sock,json_encode($output));
98                             }
99                         }
100
101                     }
102                 }
103             }
104         }
105         
106         $timeDiff =  (microtime(true) - $this->lastTime)*1000;
107         //server messages
108         if($timeDiff>1000)//send messages out every 1000 ms
109         {
110             $output = array();
111             $this->lastTime microtime(true);
112             $output['message'] = "Server Message".$this->i;
113             $output['users'] = $this->users;
114             $destinSockets array_diff($this->allsockets,array($this->master));
115             foreach($destinSockets as $sock)
116             {
117                 $this->send($sockjson_encode($output));
118             }
119         }
120     }
121 }

The main method is made up of an infinite while loop. The loop has two parts, listening to the sockets and sending messages out. $num_sockets = socket_select($changed_sockets,$write=NULL,$exceptions=NULL,0,1000); This is the line that listens on all sockets until timeout or until a sockets state has changed.

Inside a foreach loop it checks wheather the socket is a new connection or an existing one. If new it tries to estabilish a connection, if existing it takes the data off it. If there was no data received then the connection is wrong so disconnect, else check if there is a handshake already done with this socket, if not then this must be a handshake data. If the handshake has already been done then the data received must be an ordinary data with which you can do whatever you want but first you need to decode it: $action = $this->unmask($buffer); I will discuss the 'unmask' method in a second.

The second part of the while loop broadcasts the data. We don't want the badwidth to be flooded with our messages so I wrote a simple if statement that checks if there has passes more than 1ms since last time. The destination sockets must be all sockets except the master socket.

1 private function do_handshake($buffer,$socket,$socket_index)
2 {
3     list($resource,$host,$origin,$key) = $this->getheaders($buffer);
4     $retkey base64_encode(sha1($key."258EAFA5-E914-47DA-95CA-C5AB0DC85B11",true));
5     $upgrade  "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: {$retkey}\r\n\r\n";
6     $this->handshakes[$socket_index] = true;
7     socket_write($socket,$upgrade,strlen($upgrade));
8     $this->console("Done handshaking...\n");
9 }

The hanshake method is as simple as that. You extract the data from the handhsake that the client has sent. The 'getheaders' does that by using simple string methods. You take the key part and concatenate with the other part of the key. The second part of the key is always the same and is specified as a part of the WS specs. You need to sha1 and then base64 encode the whole thing. As 'Sec-WebSocket-Accept' you put the return key you just generated create a header for it and send the handshake back to the browser. You could send more info in the header but for that I suggest reading the specs.

The messages that are transmitted back and forth need to be encoded and decoded. There is a very complicated way that the specs say you need to obey. See this SO question

1 private function unmask($payload
2 {
3     $length ord($payload[1]) & 127;
4  
5     if($length == 126) {
6         $masks substr($payload44);
7         $data substr($payload8);
8         $len = (ord($payload[2]) << 8) + ord($payload[3]);
9     }
10     elseif($length == 127) {
11         $masks substr($payload104);
12         $data substr($payload14);
13         $len = (ord($payload[2]) << 56) + (ord($payload[3]) << 48) + (ord($payload[4]) << 40) + (ord($payload[5]) << 32) + 
14             (ord($payload[6]) << 24) + (ord($payload[7]) << 16) + (ord($payload[8]) << 8) + ord($payload[9]);
15     }
16     else {
17         $masks substr($payload24);
18         $data substr($payload6);
19         $len $length;
20     }
21  
22     $text '';
23     for ($i 0$i $len; ++$i) {
24         $text .= $data[$i] ^ $masks[$i%4];
25     }
26     return $text;
27 }

The character at position 1 (i.e., the second byte of the message) is a combination of one bit denoting whether the message is masked (i.e., encoded) and seven bits denoting the payload (i.e., data) length. The first bit must always be 1 according to the specs because the message must always be masked. The next seven bits when converted to a decimal give you the length. However, because you can only go up to 127 with seven bits, there must be a way of getting the length of messages that are longer than that. According to the specs, if the message is longer than 125 characters then the seven bits marking the payload length must be 126, and the actual length is then stored in the 3rd and 4th bytes in the message. If the message is longer than 65,535 characters, then the seven bits marking the payload length must be 127, and the actual length is stored in the 3rd through 8th bytes. In order to convert the binary bits that make up the bytes into a decimal for the length, you have to use the bitwise operators as I have done in the solution linked above and in order to get the length stored in the last seven bits of the 2nd byte in the message, you need to use logical AND (i.e., the & operator) to essentially ignore the 1 set for the first bit used to denoted whether the message is masked or not. For details on how data is framed, please see the official WebSocket specs. Also, if you need info on what bitwise operators are and how to use them in PHP, please see the following: php.net/manual/en/language.operators.bitwise.php – HartleySan

1 private function encode($text
2 {
3     // 0x1 text frame (FIN + opcode)
4     $b1 0x80 | (0x1 0x0f);
5     $length strlen($text);
6     
7     if($length <= 125)
8         $header pack('CC'$b1$length);
9     elseif($length 125 && $length 65536)
10         $header pack('CCS'$b1126$length);
11     elseif($length >= 65536)
12         $header pack('CCN'$b1127$length);
13  
14     return $header.$text;
15 }

Encoding the data goes exactly the same way. The first byte is a never changing byte (in my case I set it in '$b1'). The second byte will either be 126, 127 or the length of the text if less than 126. You create a string out of those bytes and put it in front of your message.

Extras:

To run the WS server from a web browser on LINUX you can use this code:

1 $pid exec('pidof php');
2 exec('kill -9 '.$pid);
3 exec('php -q server/startDaemon.php > output.txt &');

Just save this code as start.php and run this file in the browser. To stop use the same code without line 3. You can also run the startDaemon.php file in CLI.

Comments:

Noticed any bugs? Want to to tell me how to improve my code? Share with others your thoughts? Leave a comment.

Your Name:   Bot check: 88 + 41 =
Added by: eretyggkj03 Nov 2019
klkmùm
Browser: Firefox 70OS: Windows 10
Added by: ...03 Nov 2019
...
Browser: Chrome 78OS: Mac OS X 10.1
Added by: Mister Unknown03 Nov 2019
Working like a charm
Browser: Firefox 70OS: Windows 10
Added by: df12 Oct 2019
avc
Browser: Chrome 77OS: Windows 7
Added by: Hansraj07 Oct 2019
trtr
Browser: Chrome 77OS: Windows 10
Added by: fefe07 Oct 2019
ewew
Browser: Chrome 77OS: Windows 10
Added by: t23 Sep 2019
qq
Browser: Chrome 76OS: Windows 10
Added by: Yoshiʼs Island20 Sep 2019
qweqweqweqweqwe
Browser: Chrome 76OS: Mac OS X 10.1
Added by: phil08 Sep 2019
test this
Browser: Firefox 69OS: Mac OS X 10.1
Added by: Test07 Sep 2019
Welcome
Browser: Firefox 68OS: Windows 10
Added by: Test07 Sep 2019
Hello
Browser: Chrome 76OS: Windows 10
Added by: azerty05 Sep 2019
salut
Browser: Opera 62OS: Windows 10
Added by: test05 Sep 2019
hello
Browser: Chrome 76OS: Windows 7
Added by: accountanta05 Sep 2019
as
Browser: Chrome 76OS: Windows 7
Added by: hi04 Sep 2019
hhh
Browser: Firefox 68OS: Windows 7
Added by: hi04 Sep 2019
ujj
Browser: Firefox 68OS: Windows 7
Added by: sd30 Aug 2019
asd
Browser: Firefox 68OS: Ubuntu
Added by: Tiger29 Aug 2019
Test
Browser: Chrome 76OS: Android 9;
Added by: fggg22 Aug 2019
xdcfgjn
Browser: Chrome 76OS: Windows 7
Added by: Somebody13 Aug 2019
<script>eval("alert('test');")</script>
Browser: Chrome 75OS: Chromium OS
Added by: hossein10 Aug 2019
sallam
Browser: Chrome 76OS: Windows 7
Added by: ajit10 Aug 2019
hii
ajit sir how are u?
are u hearing me ?
Browser: Chrome 74OS: Windows 7
Added by: sonal10 Aug 2019
gghh
Browser: Chrome 74OS: Windows 7
Added by: 01 Aug 2019
<script>Alert('test');</script>
Browser: Chrome 75OS: Windows 10
Added by: 01 Aug 2019
Browser: Chrome 75OS: Windows 10
Added by: mmmm31 Jul 2019
mmmmmmmmmm
Browser: Chrome 75OS: Mac OS X 10.1
Added by: alal25 Jul 2019
dQDFqf
Browser: Firefox 68OS: Windows 10
Added by: 19 Jul 2019
Test
Browser: Chrome 75OS: Windows 10
Added by: Dedicated Server19 Jul 2019
asdasd
Browser: Chrome 75OS: Windows 10
Added by: Dedicated Server19 Jul 2019
asdasd
Browser: Chrome 75OS: Windows 10
Added by: William18 Jul 2019
oi
Browser: Chrome 75OS: Windows 10
Added by: jjjj17 Jul 2019
HIIII
Browser: Chrome 75OS: Windows 10
Added by: ds15 Jul 2019
sdf
Browser: Chrome 75OS: Windows 10
Added by: wertwe12 Jul 2019
wetw
Browser: Chrome 75OS: Windows 10
Added by: ASAs12 Jul 2019
ASAS
Browser: Chrome 75OS: Windows 10
Added by: joy11 Jul 2019
dmmd
Browser: Chrome 75OS: Windows 10
Added by: fghfgh06 Jul 2019
gfhhfg
Browser: Chrome 74OS: Windows 10
Added by: HVENetworks05 Jul 2019
Thanks
Browser: Chrome 75OS: Windows 10
Added by: tester03 Jul 2019
test
Browser: Chrome 75OS: Windows 10
Added by: tester03 Jul 2019
test
Browser: Chrome 75OS: Windows 10
Added by: jhgjhgj03 Jul 2019
yy
Browser: Chrome 75OS: Windows 10
Added by: BRS30 Jun 2019
selam
Browser: Firefox 67OS: Ubuntu
Added by: plamen29 Jun 2019
hi
Browser: Chrome 75OS: Windows 10
Added by: JOhn07 Jun 2019
Testing
Browser: Chrome 74OS: Windows 7
Added by: http://canadianorderpharmacy.com/04 Jun 2019

Excellent article. Keep posting such kind of info on your page. Im really impressed by your site.
Hi there, You have performed a fantastic job. I will definitely digg it and in my opinion suggest to my friends. I am sure they will be benefited from this website.
Browser: Chrome 68OS: Windows 7
Added by: dfg04 Jun 2019
fgxgfhdfg
Browser: Chrome 74OS: Windows 10
Added by: Davidhax02 Jun 2019
«Папиловит» — быстро и безопасно избавит от любых папиллом и бородавок.
Наш сайт: https://lcokbhlw.bestseller-super.ru
Browser: Chrome 67OS: Windows 7
Added by: Davidhax02 Jun 2019
«Папиловит» — быстро и безопасно избавит от любых папиллом и бородавок.
Наш сайт: https://lcokbhlw.bestseller-super.ru
Browser: Opera 54OS: Windows 7
Added by: Davidhax01 Jun 2019
«Папиловит» — быстро и безопасно избавит от любых папиллом и бородавок.
Наш сайт: https://lcokbhlw.bestseller-super.ru
Browser: Chrome 66OS: Windows 7
Added by: Davidhax01 Jun 2019
«Папиловит» — быстро и безопасно избавит от любых папиллом и бородавок.
Наш сайт: https://lcokbhlw.bestseller-super.ru
Browser: Chrome 67OS: Windows 8.1
Added by: Martin31 May 2019
Hi
Browser: Chrome 74OS: Mac OS X 10.1
Added by: Davidhax31 May 2019
«Папиловит» — быстро и безопасно избавит от любых папиллом и бородавок.
Наш сайт: https://lcokbhlw.bestseller-super.ru
Browser: Chrome 67OS: Windows 7
Added by: fsd29 May 2019
cdfsdfd
Browser: Chrome 74OS: Windows 10
Added by: Davidhax18 May 2019
«Папиловит» — быстро и безопасно избавит от любых папиллом и бородавок.
Наш сайт: https://lcokbhlw.bestseller-super.ru
Browser: Chrome 66OS: Windows 7
Added by: Michaelsiz15 May 2019
Настройте организм на жиросжигание за 7 дней

SLIM BIOTIC - это кардинально новое средство в системе похудения. Комплекс препарата разработан с учетом всех физиологических потребностей организма и работает на уровней нейронов. Это значит, что за курс применения SLIM BIOTIC ваш организм на мышечном уровне настроится на правильное пищевое поведение и снизит жировую массу до абсолютной нормы. Жми сюда --> http://v.ht/LwWP
Browser: Chrome 67OS: Windows 10
Added by: asd14 May 2019
asd
Browser: Chrome 74OS: Windows 10
Added by: animesoul.com10 May 2019
animesoul.com
Browser: Chrome 73OS: Mac OS X 10.1
Added by: -===-04 May 2019
я ебу собак
Browser: Opera 58OS: Windows 10
Added by: Terrellled28 Apr 2019
Как избавиться от боли в суставах? Пантогор - гель для суставов быстро избавит от боли в суставах без вреда для здоровья. Полная потеря дееспособности и жизнь без надежды на будущее - вот, что вам грозит, если вы не позаботитесь о своих суставах! Жить полноценной жизнью или провести остаток своих дней в инвалидном кресле - решать вам! По данным Всемирной организации по борьбе <b>с болезнями суставов</b>, 80% людей в мире имеют проблемы с суставами. Самое страшное, что заболевания суставов приводят <b>к параличу и инвалидности</b>. На сегодняшний день есть одно единственное средство, которое отличается от всех существующих до этого средств. Это средство Пантогор. Основной компонент Пантогора - <b>панты канадского марала</b>. Панты канадского марала <b>активируют</b> природные <b>силы организма и запускают</b> процесс его <b>самовосстановления</b>. Все остальные компоненты действуют <b>синергически - дополняют и усиливают</b> действие друг друга. <b>Прием</b> средства Пантогор <b>гарантирует</b> вам <b>избавление от боли в суставах</b> всего <b>за 1 курс</b>. Внимание! Акция действует только на территории России и стран СНГ! Успейте оформить заказ по выгодной промо-цене! <b>Заказать Пантогор Здесь</b> --> http://c.trktp.ru/ud6b
Browser: Chrome 67OS: Windows 8.1
Added by: sza23 Apr 2019
assaaa
Browser: Chrome 73OS: Windows 10
Added by: xia10 Apr 2019
222e3e322
Browser: Chrome 63OS: Windows 10
Added by: luiz05 Apr 2019
o
Browser: Chrome 72OS: Windows 10
Added by: asdfgasdfg04 Apr 2019
sdfgdfgsdfg
Browser: Firefox 66OS: Windows 10
Added by: Slezinger03 Apr 2019
Yehohafatz
Browser: Chrome 73OS: Windows 10
Added by: dgfgdfg28 Mar 2019
gdfgdfg
Browser: Chrome 61OS: Windows 8.1
Added by: hose26 Mar 2019
test
Browser: Chrome 67OS: Mac OS X 10.9
Added by: Clement24 Mar 2019
Its too long
Browser: Opera 50OS: Android 6.0
Added by: User23 Mar 2019
Hello
Browser: Chrome 73OS: Windows 10
Added by: fgh16 Mar 2019
fgh
Browser: Chrome 72OS: Windows 10
Added by: ggg15 Mar 2019
yellow
Browser: Chrome 72OS: Windows 10
Added by: endy07 Mar 2019
hii its endy
Browser: Chrome 72OS: Windows 10
Added by: io06 Mar 2019
231
Browser: Chrome 72OS: Windows 10
Added by: Nurmyrat05 Mar 2019
Hello
Browser: Chrome 72OS: Windows 7
Added by: Sam04 Mar 2019
Test
Browser: Chrome 72OS: Windows 7
Added by: 12312304 Mar 2019
1232
Browser: Chrome 72OS: Windows 10
Added by: aa28 Feb 2019
dsfgdfg
Browser: Firefox 60OS: Linux
Added by: test28 Feb 2019
hello
Browser: Firefox 65OS: Windows 10
Added by: test28 Feb 2019
hello
Browser: Firefox 65OS: Windows 10
Added by: 12328 Feb 2019
Allo
Browser: Safari 12.OS: iOS 1
Added by: microsoft26 Feb 2019
fbdfbdfbdf
Browser: Chrome 69OS: Linux
Added by: Ravi22 Feb 2019
sss
Browser: Chrome 72OS: Windows 8.1
Added by: wsdsadad19 Feb 2019
Hii
Browser: Chrome 72OS: Windows 7
Added by: asdadaasd19 Feb 2019
adsasdsad
Browser: Chrome 72OS: Windows 7
Added by: jose19 Feb 2019
hai hello
Browser: Chrome 72OS: Windows 7
Added by: t118 Feb 2019
test message
Browser: Chrome 72OS: Windows 10
Added by: ddd13 Feb 2019
hey
Browser: Chrome 72OS: Windows 10
Added by: Minh10 Feb 2019
qwerty
Browser: Chrome 72OS: Windows 10
Added by: Ibai09 Feb 2019
Hey
Browser: Chrome 71OS: Windows 10
Added by: tens20 Jan 2019
Bagus nich...
Thank...
Browser: Firefox 64OS: Windows 7
Added by: dddddd17 Jan 2019
<?php echo 'dsd';exit;?>
Browser: Firefox 62OS: Windows 10
Added by: Gopi16 Jan 2019
Hi everyone
Browser: Chrome 71OS: Android 4.4
Added by: irfan16 Jan 2019
hello
Browser: Chrome 71OS: Windows 10
Added by: xss.comxss.comxss.comxss.comxss.comxss.comxss.com14 Jan 2019
xss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.comxss.com
Browser: Chrome 71OS: Windows 10
Added by: vd31 Dec 2018
ok
Browser: Chrome 71OS: Windows 8.1
Added by: dfgdfg28 Dec 2018
dfgdfg
Browser: Chrome 71OS: Windows 7
Added by: Dharman23 Dec 2018
For anyone reading this tutorial in 2019. This was written in 2013 as a learning exercise when I was in college. If you find this tutorial useful I am glad, but there are much better tutorials on the net. As pointed out by some people the code maintained over here has some bugs and it was never intended for production use. The website dharman.eu hasn't been maintained ever since and most likely will be taken down or switched off soon.
Browser: Chrome 71OS: Windows 10
Added by: ppiuuooui08 Sep 2017
pp^p$^pàoà
Browser: Chrome 60OS: Windows 7
Added by: ppiuuooui07 Sep 2017
pp^p$^pàoà
Browser: Chrome 60OS: Windows 7
Added by: ppiuuooui05 Sep 2017
pp^p$^pàoà
Browser: Chrome 60OS: Windows 7
Added by: ppiuuooui05 Sep 2017
pp^p$^pàoà
Browser: Chrome 60OS: Windows 7