mchat_client.pl (Chapter 21) The mchat_client.pl script implements the client side of the multicast chat system developed in Chapter 21. 0 #!/usr/bin/perl -w 1 # file: chat_client.pl 2 # chat client using UDP 3 use strict; 4 use IO::Socket; 5 use IO::Select; 6 use ChatObjects::ChatCodes; 7 use ChatObjects::MComm; 8 use IO::Socket::Multicast; 9 use Sys::Hostname; 10 $SIG{INT} = $SIG{TERM} = sub { exit 0 }; 11 my ($nickname,$comm,$mcomm); 12 # dispatch table for commands from the user 13 my %COMMANDS = ( 14 channels => sub { $comm->send_event(LIST_CHANNELS) }, 15 join => sub { $comm->send_event(JOIN_REQ,shift) }, 16 part => sub { $comm->send_event(PART_REQ,shift) }, 17 users => sub { $comm->send_event(LIST_USERS) }, 18 public => sub { $comm->send_event(SEND_PUBLIC,shift) }, 19 private => sub { $comm->send_event(SEND_PRIVATE,shift) }, 20 login => sub { $nickname = do_login() }, 21 quit => sub { undef }, 22 ); 23 # dispatch table for messages from the server 24 my %MESSAGES = ( 25 ERROR() => \&error, 26 LOGIN_ACK() => \&login_ack, 27 JOIN_ACK() => \&join_part, 28 PART_ACK() => \&join_part, 29 PUBLIC_MSG() => \&public_msg, 30 PRIVATE_MSG() => \&private_msg, 31 USER_JOINS() => \&user_join_part, 32 USER_PARTS() => $user_join_part, 33 CHANNEL_ITEM() => \&list_channel, 34 USER_ITEM() => \&list_user, 35 SET_MCAST_PORT() => \&create_msocket, 36 ); 37 # Create and initialize the UDP socket 38 my $servaddr = shift 'localhost'; 39 my $servport = shift 2027; 40 my $mcast_port = shift 2028; 41 # create comm object for communicating with chat server 42 $comm = ChatObjects::Comm->new(PeerAddr => "$servaddr:$servport") or die $@; 43 # Try to log in 44 $nickname = do_login(); 45 die "Can't log in.\n" unless $nickname; 46 # Read commands from the user and messages from the server 47 my $select = IO::Select->new($comm->socket,\*STDIN); 48 LOOP: 49 while (1) { 50 my @ready = $select->can_read; 51 foreach (@ready) { 52 if ($_ eq $STDIN) { 53 do_user(\*STDIN) last LOOP; 54 } else { 55 do_server($_); 56 } 57 } 58 } 59 #create multicast socket in response to SET_MCAST_PORT event 60 sub create_msocket { 61 my ($code,$port) = @_; 62 return unless $port =~ /^\d+$/; 63 $select->remove($mcomm->socket) if defined $mcomm; 64 # create multicast comm object for receiving multicast channel messages 65 $mcomm=ChatObjects::MComm->new($port) or die $@; 66 $select->add($mcomm->socket); 67 } 68 # called to handle a command from the user 69 sub do_user { 70 my $h = shift; 71 my $data; 72 return unless sysread($h,$data,1024); # longest line 73 return 1 unless $data =~ /\S+/; 74 chomp($data); 75 my($command,$args) = $data =~ m!^/(\S+)\s*(.*)!; 76 ($command,$args) = ('public',$data) unless $command; 77 my $sub = $COMMANDS{lc $command}; 78 return warn "$command: unknown command\n" unless $sub; 79 return $sub->($args); 80 } 81 # called to handle a message from the server 82 sub do_server { 83 die "invalid socket" unless my $s = ChatObjects::Comm->sock2comm(shift); 84 die "can't receive: $!" unless 85 my ($mess,$args) = $s->recv_event; 86 my $sub = $MESSAGES{$mess} return warn "$mess: unknown message from server\n"; 87 $sub->($mess,$args); 88 return $mess; 89 } 90 # try to log in (repeatedly) 91 sub do_login { 92 $comm->send_event(LOGOFF,$nickname) if $nickname; 93 my $nick = get_nickname(); # read from user 94 my $select = IO::Select->new($comm->socket); 95 for (my $count=1; $count <= 5; $count++) { 96 warn "trying to log in ($count)...\n"; 97 $comm->send_event(LOGIN_REQ,$nick); 98 next unless $select->can_read(6); 99 return $nick if do_server($comm->socket) == LOGIN_ACK; 100 $nick = get_nickname(); 101 } 102 } 103 # prompt user for his nickname 104 sub get_nickname { 105 while (1) { 106 local $ = 1; 107 print "Your nickname: "; 108 last unless defined(my $nick = <STDIN>); 109 chomp($nick); 110 return $nick if $nick =~ /^\S+$/; 111 warn "Invalid nickname. Must contain no spaces.\n"; 112 } 113 } 114 # handle an error message from server 115 sub error { 116 my ($code,$args) = @_; 117 print "\t** ERROR: $args **\n"; 118 } 119 # handle login acknowledgment from server 120 sub login_ack { 121 my ($code,$nickname) = @_; 122 print "\tLog in successful. Welcome $nickname.\n"; 123 } 124 # handle channel join/part messages from server 125 sub join_part { 126 my ($code,$msg) = @_; 127 my ($title,$users,$mcast_addr) = $msg =~ /^(\S+) (\d+) ([\d.]+)/; 128 if ($code == JOIN_ACK) { 129 # add multicast address to the list that we receive 130 $mcomm->socket->mcast_add($mcast_addr); 131 print "\tWelcome to the $title Channel ($users users)\n"; 132 } else { 133 $mcomm->socket->mcast_drop($mcast_addr); 134 print "\tYou have left the $title Channel\n"; 135 } 136 } 137 # handle channel listing messages from server 138 sub list_channel { 139 my ($code,$msg) = @_; 140 my ($title,$count,$mcast_addr,$description) = $msg =~ /^(\S+) (\d+) ([\d.]+) (.+)/; 141 printf "\t%-20s %-40s %3d users\n","[$title]",$description,$count; 142 } 143 # handle a public message from server 144 sub public_msg { 145 my ($code,$msg) = @_; 146 my ($channel,$user,$text) = $msg =~ /^(\S+) (\S+) (.*)/; 147 print "\t$user [$channel]: $text\n"; 148 } 149 # handle a private message from server 150 sub private_msg { 151 my ($code,$msg) = @_; 152 my ($user,$text) = $msg =~ /^(\S+) (.*)/; 153 print "\t$user [**private**]: $text\n"; 154 } 155 # handle user join/part messages from server 156 sub user_join_part { 157 my ($code,$msg) = @_; 158 my $verb = $code == USER_JOINS ? 'has entered' : 'has left'; 159 my ($channel,$user) = $msg =~ /^(\S+) (\S+)/; 160 print "\t<$user $verb $channel>\n"; 161 } 162 # handle user listing messages from server 163 sub list_user { 164 my ($code,$msg) = @_; 165 my ($user,$timeon,$channels) = $msg =~ /^(\S+) (\d+) (.+)/; 166 my ($hrs,$min,$sec) = format_time($timeon); 167 printf "\t%-15s (on %02d:%02d:%02d) Channels: %s\n",$user,$hrs,$min,$sec,$channels; 168 } 169 # nicely formatted time (hr, min sec) 170 sub format time { 171 my $sec = shift; 172 my $hours = int( $sec/(60*60) ); 173 $sec -= ($hours*60*60); 174 my $min = int( $sec/60 ); 175 $sec -= ($min*60); 176 return ($hours,$min,$sec); 177 } 178 END { 179 if (defined $comm) { 180 $comm->send_event(LOGOFF,$nickname); 181 $comm->close; 182 } 183 } |