Posted to tcl by crshults at Thu Nov 20 17:46:51 GMT 2014view pretty
#I usually shuffle this stuff off into my list_tools and binary_tools modules proc convert_binary_to_bit_list {input} { binary scan $input B* bit_string split $bit_string "" } proc invert_bit_list {bit_list} { foreach bit $bit_list { switch $bit { 0 {lappend inverted_bit_list 1} 1 {lappend inverted_bit_list 0} } } return $inverted_bit_list } package require list_tools proc bassign {input fields_and_lengths} { set bit_list [convert_binary_to_bit_list $input] foreach {field length} $fields_and_lengths { uplevel "scan [join [lpop bit_list $length] ""] %b $field" } return [binary format B* [join $bit_list ""]] } #Here's the start of the actual thing package require TclOO package require sha1 oo::class create websocket_server { variable clients handler server_socket constructor {port callback} { set clients [list] set handler $callback set server_socket [socket -server [list [self] accept_connection] $port] } destructor { close $server_socket } method accept_connection {client_socket address port} { lappend clients $client_socket chan configure $client_socket -blocking no -buffering none -encoding iso8859-1 -translation crlf chan event $client_socket readable [list [self] perform_handshake $client_socket] } method perform_handshake {client_socket} { set request [split [chan read $client_socket] \n] if {[chan eof $client_socket]} { chan close $client_socket set clients [lsearch -inline -all -not -exact $clients $client_socket] } else { set key [lindex [lindex $request [lsearch $request Sec-WebSocket-Key*]] end] chan puts $client_socket "HTTP/1.1 101 Switching Protocols" chan puts $client_socket "Upgrade: websocket" chan puts $client_socket "Connection: Upgrade" chan puts $client_socket "Sec-WebSocket-Accept: [binary encode base64 [sha1::sha1 -bin ${key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11]]" chan puts $client_socket "" chan event $client_socket readable [list [self] read_data $client_socket] } } method read_data {client_socket} { set received_message [chan read $client_socket] if {[chan eof $client_socket]} { chan close $client_socket set clients [lsearch -inline -all -not -exact $clients $client_socket] } while {[string length $received_message] > 0} { set received_message [bassign $received_message {fin 1 rsv1 1 rsv2 1 rsv3 1 opcode 4 mask 1 payload_length 7}] switch $payload_length { 126 {set received_message [bassign $received_message {payload_length 16}]} 127 {set received_message [bassign $received_message {payload_length 64}]} } binary scan $received_message c1c1c1c1c$payload_length masking_key(0) masking_key(1) masking_key(2) masking_key(3) payload set received_message [string range $received_message [expr {$payload_length+4}] end] for {set i 0} {$i < $payload_length} {incr i} { append message [binary format c1 [expr {[lindex $payload $i] ^ $masking_key([expr $i % 4])}]] } $handler $message set message {} } } method broadcast {message} { set length [string length $message] if {$length < 126} { set message \x81[binary format H* [format %02x [string length $message]]]$message } elseif {$length < 65536} { set message \x81\x7e[binary format H* [format %04x [string length $message]]]$message } else { #client will close the connection if you try to send a single message this big set message \x81\x7f[binary format H* [format %08x [string length $message]]]$message } foreach client $clients { puts -nonewline $client $message } } } #Example usage: proc callback {message} {puts $message} websocket_server create myServer 9999 callback #Then at the FireBug console: var sock = new WebSocket("ws://localhost:9999"); sock.onmessage = function(message) {console.log(message)}; sock.send("This is a message from the browser"); #And back at tkcon: myServer broadcast "Hello from the WebSocket server!"