直接上代码:
################################################################################ # Filename: # websocket.pl # Description: # Perl mutithreaded TCP server of HTML5 Websocket-draft-76 # Test enviroment: # OS - Windows XP # Perl - ActivePerl 5.12.1 # Browser - Chrome 8.0 ################################################################################ #!/usr/bin/perl -w use strict; use warnings; use 5.010; use IO::Socket::INET; use Digest::MD5 qw/md5/; use Redis; use POSIX ':sys_wait_h'; ### main part ################################################################## ### system level options my %VARS = ( DEBUG => 1, LOG2FILE => 0 ); #common global variables my @TIMER_TASK; #its element is %{task_name, interval fun_ref} ### stdout to log file if( $VARS{LOG2FILE} ){ open LOG, ">>syslog.txt"; select LOG; } ### redis db initialization my $r = Redis->new; $r->ping or die now() . "[Server]: connect Redis failed"; $r->flushall; my $redis_sock = $$r{sock}; $r->hset("sys:sock", "redis", $redis_sock); say now() . " | Redis: " . $redis_sock->sockhost() . ":" . $redis_sock->sockport() . " <---> " . $redis_sock->peerhost() . ":" . $redis_sock->peerport(); ### tcp server my $PORT = 8000; my $server = IO::Socket::INET->new( Proto => 'tcp', LocalPort => $PORT, Listen => 255, Reuse => 1); $server or die now() . "[Server]: Err = can't setup server"; say now() . " | Server: Listening at TCP:" . $server->sockport; #zombie my $zombies = 0; $SIG{CHLD} = sub {$zombies++;}; while(my $client = $server->accept()){ #zombie reaper while($zombies) { $zombies = 0; while ((my $zombie = waitpid( -1, WNOHANG)) != -1){} }; $client->autoflush(1); #handshake sysread $client, my $handshake_req, 1024; my($handshake_resp,$page) = handshake($handshake_req); print $client $handshake_resp; close $client unless $client; my $client_info = $client->peerhost() . ":" . $client->peerport() . "$page"; $r->hset("sys:sock", $client_info,$client); say now() . " | Client: connected from " . $client_info; #data framing $client->blocking(0); #fork child for new connection if(my $child = fork()){ $/ = "\xff"; #new line seperator my($req,@resp); while(<$client>){ /\x00(.*)\xff/; $req = $1; say now() . " | ws://" . "$client_info >>> [$req]" if $VARS{DEBUG}; @resp = request($req); foreach my $resp (@resp){ say now() . " | ws://" . "$client_info <<< [$resp]" if $VARS{DEBUG}; print $client "\x00$resp\xff"; } } return; #go back to parent } } ### sub routines ############################################################### # Description: Request format from webSocket # Format: command param1,param2, ... ,paramN sub request{ my @resp = (); $_ = shift; my($cmd, @param) = split / /; $cmd = "req_$cmd"; if(defined(&$cmd)){ no strict 'refs'; @resp = $cmd->(@param); }else{ push @resp, "!$_"; } return @resp; } sub req_echo{ return @_; } sub req_random{ $_ = shift; my $r; if(/float/){ #float $r = rand(100); }else{ #integer $_ = "int"; $r = int(rand(100)); } return ("random: $_ = $r"); } sub req_showsock{ my @socks = (); my %s = $r->hgetall("sys:sock"); say ".............."; foreach (keys %s){ say ; #push @socks, $item; } return @socks; } sub now{ my($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(time); $year += 1900; return sprintf("%.4d-%.2d-%.2d %2d:%.2d:%.2d",$year,$mon,$mday,$hour,$min,$sec); } sub handshake{ my $req = shift; ($req, my $key3) = split /\r\n\r\n/, $req; my @req = split /\r\n/, $req; my $field = shift @req; $field =~ /GET (.*) HTTP/; my $page = $1; $field = shift @req; $field = shift @req; $field = shift @req; $field =~ /Host: (.*)/; my $location = "ws://" . $1 . $page; $field = shift @req; $field =~ /Origin: (.*)/; my $origin = $1; $field = shift @req; $field =~ /Sec-WebSocket-Key1: (.*)/; $field = $1; my @key1= $field =~ /([0-9])/g; my $key1 = join('', @key1) + 0; my @space1 = $field =~ /\x20/g; my $space1 = @space1 + 0; my $part1 = $key1 / $space1; my $part1_hex = pack 'L>', $part1; $field = shift @req; $field =~ /Sec-WebSocket-Key2: (.*)/; $field = $1; my @key2 = $field =~ /([0-9])/g; my $key2 = join('', @key2) + 0; my @space2 = $field =~ /\x20/g; my $space2 = @space2 + 0; my $part2 = $key2 / $space2; my $part2_hex = pack 'L>', $part2; my $challenge = md5($part1_hex, $part2_hex, $key3); my $resp = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" . "Upgrade: WebSocket\r\n" . "Connection: Upgrade\r\n" . "Sec-WebSocket-Origin: $origin\r\n" . "Sec-WebSocket-Location: $location\r\n\r\n" . $challenge; return ($resp, $page); } sub hexcode{ my $str = unpack 'H*', shift; $str =~ s/(.)(.)/\U$1$2 /g; return $str; }