in reply to Building a chat server and client

You make a few big errors in your client. 1. a different socket number from server. 2. No gtk2 main loop in client so it exits immediately. 3. No callback for the Gtk2->Helper to watch the socket. 4. Need to return 1 from Helper to keep it in play.

Try this, it works on linux.

#server #!/usr/bin/perl use warnings; use strict; use IO::Socket; use Gtk2 -init; use Glib qw/TRUE FALSE/; my $main_window = Gtk2::Window->new("toplevel"); $main_window->signal_connect(delete_event => sub{Gtk2->main_quit}); my $buffer = Gtk2::TextBuffer->new; my $table = Gtk2::Table->new(3, 2, FALSE); my $label = Gtk2::Label->new("Chat Client Test"); my $textview = Gtk2::TextView->new_with_buffer($buffer); my $swindow = Gtk2::ScrolledWindow->new( undef, undef); $swindow->set_size_request (100, 150); my $button = Gtk2::Button->new("Send"); my $entry = Gtk2::Entry->new(); $textview->set_cursor_visible (FALSE); $textview->set_editable(FALSE); $buffer->create_mark ('end', $buffer->get_end_iter, FALSE); $buffer->signal_connect (insert_text => sub {$textview->scroll_to_mark + ($buffer->get_mark ('end'),0.0, TRUE, 0, 0.5)}); $swindow->set_policy( 'automatic', 'automatic'); $swindow->set_shadow_type( 'etched-out'); $swindow->add( $textview); $table->attach_defaults($label, 0, 1, 0, 1); $table->attach_defaults($swindow, 0, 2, 1, 2); $table->attach_defaults($entry, 0, 1, 2, 3); $table->attach_defaults($button, 1, 2, 2, 3); $main_window->add($table); $main_window->show_all(); $button->signal_connect("clicked" =>sub { #get the contents of the entry my $msg_send = $entry->get_text; #clear the entry $entry->set_text(""); #grab focus again for the next round of talks $entry->grab_focus; #if there was bogus input, ignore it! if ($msg_send !~ m/^\s*$/){ #open up a UDP client connection my $SEND_PORT = 5152; my $server_host = "localhost"; my $sock = IO::Socket::INET->new(Proto => 'udp', PeerPort => +$SEND_PORT, PeerAddr => $server_host) or die "Creating socket: $!\n" +; #send the message out on the socket $sock->send($msg_send."\n") or die "send: $!"; #update the screen locally #&update_buffer($buffer,$msg_send,TRUE); } }); Gtk2->main;
and the client
#!/usr/bin/perl -w use strict; use IO::Socket; use Gtk2 -init; use Glib qw/TRUE FALSE/; use Gtk2::Helper; my($sock,$MAXLEN, $LISTEN_PORT, $SEND_PORT, $tag, $tag_send, $tag_receive,$img_big,$img_send,$img_rec); $MAXLEN = 1024; $LISTEN_PORT = 5152; my $tview; #set up a udp server waiting for incomming messages $sock = IO::Socket::INET->new(LocalPort => $LISTEN_PORT, Proto => 'ud +p') or die "socket: $@"; #add a Gtk2::Helper watch on any incomming connections print "Awaiting UDP messages on port $LISTEN_PORT\n"; $tag = Gtk2::Helper->add_watch ( fileno($sock), 'in', sub { if (eof($sock)) { Gtk2::Helper->remove_watch ($tag); close($sock); } else { my $line = <$sock>; #$buffer->insert($buffer->get_end_iter,$line); print "$line"; } return 1; }); Gtk2->main;

I'm not really a human, but I play one on earth. Cogito ergo sum a bum

Replies are listed 'Best First'.
Re^2: Building a chat server and client
by deadpickle (Pilgrim) on Jan 09, 2008 at 21:52 UTC
    Thanks for the replies, they are very helpful! But I'm still lost.
    The server should listen for incoming messages. When it receives messages it should then broadcast them to all the connected users. Also, the server is in charge of the connected users. This entails checking for duplicate usernames and informing the connected users of new and leaving users. The thing is that I don't know where to start in this whole diboggle. I'm not familiar with sockets and writing server scripts.

    For the client it should connect to the server and share its user name. The users connection should be rejected if there is a similar username or should be forced to have a different name. with that taken care of, it should work just like a chat client does. you type and then the message is sent to the server where it is broadcast to all servers even the owner.

    I have been trying to build the scripts from scratch and so far I have a client:

    #server #!/usr/bin/perl use warnings; use strict; use IO::Socket; my $SERVER = "Deadpickle-hobo"; my $SEND_PORT = 5152; my $line; my $send_socket = IO::Socket::INET->new( PeerAddr => $SERVER, PeerPort => $SEND_PORT, Proto => 'tcp', Reuse => 1 ) or die "socket: $@"; #ask for the user name and send it to the server #in order to verifiy that its unique print "Username:"; my $user = <>; $send_socket->send($user); $send_socket->recv($line, 80); print $line; $send_socket->recv($line, 80); print $line;
    and a server:
    #!/usr/bin/perl -w use strict; use IO::Socket; use IO::Select; #variables my $LISTEN_PORT = 5152; my $user; my @users; my $fh; my @ready; #open a new socket my $listening_socket = IO::Socket::INET->new( LocalPort => $LISTEN_PORT, Proto => 'tcp', Listen => 1, Reuse => 1 ) or die "socket: $@"; my $select = new IO::Select($listening_socket); #wait for a client to connect to the server print "Awaiting TCP messages on port $LISTEN_PORT\n" if $listening_soc +ket; while(@ready = $select->can_read) { foreach $fh (@ready) { if($fh == $listening_socket) { #Create a new socket and get the user ID my $new = $listening_socket->accept; $new->recv($user, 80); #Check the newly connected user for copies if (my $new_user = grep($user, @users)) { } else { $select->add($new); push(@users, $user); $new->send("Hello\n"); $new->send("@users"); } } else { # Process socket # Maybe we have finished with the socket $select->remove($fh); $fh->close; } } } print "END\n";
    I am requesting for help on implementing the ideas I mentioned above.
      I'm not in the mood right now for writing your code, but study the following tcp example, it shows the basic idea, You need to push the socket filehandle and an associated name into an array or hash, then loop thru them to send to all socket filehandles. I don't believe you can just send to an array of filehandles, like you show with $new->send("@users");

      Also you need to listen to more than 1, for a multiecho operation.

      #!/usr/bin/perl #server use IO::Socket; use IO::Select; my @sockets; my $machine_addr = 'localhost'; $main_sock = new IO::Socket::INET(LocalAddr=>$machine_addr, LocalPort=>1200, Proto=>'tcp', Listen=>3, Reuse=>1, ); die "Could not connect: $!" unless $main_sock; print "Starting Server\n"; $readable_handles = new IO::Select(); $readable_handles->add($main_sock); while (1) { ($new_readable) = IO::Select->select($readable_handles, undef, undef +, 0); foreach $sock (@$new_readable) { if ($sock == $main_sock) { $new_sock = $sock->accept(); $readable_handles->add($new_sock); } else { $buf = <$sock>; if ($buf) { print "$buf\n"; my @sockets = $readable_handles->can_write(); #print $sock "You sent $buf\n"; foreach my $sck(@sockets){print $sck "$buf\n";} } else { $readable_handles->remove($sock); close($sock); } } } } print "Terminating Server\n"; close $main_sock; getc();
      #!/usr/bin/perl -w # interactive client use strict; use IO::Socket; my ( $host, $port, $kidpid, $handle, $line ); ( $host, $port ) = ('localhost',1200); my $name = shift || ''; if($name eq ''){print "What's your name?\n"} chomp ($name = <>); # create a tcp connection to the specified host and port $handle = IO::Socket::INET->new( Proto => "tcp", PeerAddr => $host, PeerPort => $port ) or die "can't connect to port $port on $host: $!"; $handle->autoflush(1); # so output gets there right away print STDERR "[Connected to $host:$port]\n"; # split the program into two processes, identical twins die "can't fork: $!" unless defined( $kidpid = fork() ); # the if{} block runs only in the parent process if ($kidpid) { # copy the socket to standard output while ( defined( $line = <$handle> ) ) { print STDOUT $line; } kill( "TERM", $kidpid ); # send SIGTERM to child } # the else{} block runs only in the child process else { # copy standard input to the socket while ( defined( $line = <STDIN> ) ) { print $handle "$name->$line"; } }

      I'm not really a human, but I play one on earth. Cogito ergo sum a bum
        I'm finally getting it (I'm a little slow). Here is a Gtk2 script that loads the client GUI and asks for a username. But I have ran into a problem, I receive this error:
        *** unhandled exception in callback:
        *** Undefined subroutine &main:: called at chat-client.pl line 54.
        *** ignoring at chat-client.pl line 54.
        This error seems to stem from the wait_for_msg timer that I have added and the error references the Gtk2->main loop. When the timer is commented out the program works fine. What can I do.
        #!/usr/bin/perl use warnings; use strict; use Gtk2 -init; use Glib qw/TRUE FALSE/; use IO::Socket::INET; use Tie::RefHash; use IO::Select; #global variables my $buffer; my $host = "Deadpickle-hobo"; my $port = 6666; my $conn_stat = 'idle'; my %inbuffer = (); my %outbuffer = (); my %ready = (); my $select; #my $timer_waiting = Glib::Timeout->add(100, wait_for_msg()); #the main chat widget my $main_window = Gtk2::Window->new("toplevel"); $main_window->signal_connect(delete_event => sub {Gtk2->main_quit;}); $main_window->set_default_size(250, 200); my $table = Gtk2::Table->new(4, 2, FALSE); $buffer = Gtk2::TextBuffer->new; my $button = Gtk2::Button->new("Send"); my $entry = Gtk2::Entry->new(); my $label = Gtk2::Label->new("Chat Client Test"); my $textview = Gtk2::TextView->new_with_buffer($buffer); $textview->set_cursor_visible (FALSE); my $swindow = Gtk2::ScrolledWindow->new( undef, undef); $swindow->set_policy( 'automatic', 'automatic'); $swindow->set_shadow_type( 'etched-out'); $swindow->add( $textview); $table->attach_defaults($label, 0, 1, 0, 1); $table->attach_defaults($swindow, 0, 2, 1, 3); $table->attach_defaults($entry, 0, 1, 3, 4); $table->attach_defaults($button, 1, 2, 3, 4); $main_window->add($table); $main_window->show_all(); #run the login dialog dialog($buffer); Gtk2->main; #-------------------Login Dialog------------------- sub dialog{ my $buffer = shift; my $dialog_window = Gtk2::Window->new('toplevel'); $dialog_window->signal_connect(delete_event => sub {Gtk2->main_quit} +); my $dialog_table = Gtk2::Table->new(2, 2, FALSE); my $dialog_label1 = Gtk2::Label->new('Chat Login:'); my $dialog_label2 = Gtk2::Label->new('User:'); my $dialog_label3 = Gtk2::Label->new('Host:'); my $chat_user = Gtk2::Entry->new(); $chat_user->set_text(''); my $server_user = Gtk2::Entry->new(); $server_user->set_text('uas'); my $host = Gtk2::Entry->new(); $host->set_text('updraft.unl.edu'); my $dialog_button1 = Gtk2::Button->new('Connect'); $dialog_table->attach_defaults($dialog_label1, 0, 1, 0, 1); $dialog_table->attach_defaults($chat_user, 1, 2, 0, 1); $dialog_table->attach_defaults($dialog_button1, 1, 2, 1, 2); $dialog_button1->signal_connect("clicked" => sub {my $tag = $chat_us +er->get_text; $dialog_window->destroy; $buffer->insert(($buffer->get_ +end_iter), "Username: $tag...\n"); connect_server()}); $dialog_window->add($dialog_table); $dialog_window->show_all; return 1; } #------------------Connect to server--------------------- #establishes connection to the server sub connect_server{ if ($conn_stat ne 'connected') { $buffer->insert(($buffer->get_end_iter), "Connecting to Server $ho +st:$port...\n"); my $conn = IO::Socket::INET->new(PeerAddr => $host, PeerPort => $p +ort, Proto => 'tcp') or popup_err(1); if ($conn) { %inbuffer = (); %outbuffer = (); %ready = (); tie %ready, 'Tie::RefHash'; nonblock($conn); $select = IO::Select->new($conn); $conn_stat = 'connected'; $buffer->insert(($buffer->get_end_iter), "Connected!\n"); #my $timer_waiting = Glib::Timeout->add(100, wait_for_msg()); } } } #-------------------Error popup------------------- # pops up an error message sub popup_err{ my ($error_code) = @_; my $error; if ($error_code == 1) {$error = "Cannot create Socket!"}; $buffer->insert(($buffer->get_end_iter), "$error\n"); my $error_dialog = Gtk2::MessageDialog->new($main_window, 'destroy-w +ith-parent', 'error', 'ok', "$error"); $error_dialog->run; $error_dialog->destroy; } #-------------------blocking------------------- # nonblock($socket) puts socket into nonblocking mode sub nonblock { my $socket = shift; $socket->blocking(0); } #-------------------Message Waiting------------------- # Wait for incoming messages from the server relayed from clients sub wait_for_msg { if ($conn_stat eq 'connected') { my ($list_size, $msg); my $server; my $rv; my $data; # check for new information on the connections we have # anything to read or accept? foreach $server ($select->can_read(1)) { # read data $data = ''; $rv = $server->recv($data, 'POSIX::BUFSIZ', 0); unless (defined($rv) && length $data) { # This would be the end of file, so close the client delete $inbuffer{$server}; delete $outbuffer{$server}; delete $ready{$server}; $select->remove($server); close $server; next; } $inbuffer{$server} .= $data; # test whether the data in the buffer or the data we # just read means there is a complete request waiting # to be fulfilled. If there is, set $ready{$client} # to the requests waiting to be fulfilled. while ($inbuffer{$server} =~ s/(.*\n)//) { push( @{$ready{$server}}, $1 ); } } # Any complete requests to process? foreach $server (keys %ready) { handle($server); } } }

        UPDATE: I accidentally clipped the use references.