Posted: Sat Jan 07, 2006 1:52 am Post subject: TCP Server and Client, CPU Issues
I'm running a TCP server for a chat room and therefore have made clients for it that follow the same protocol.
How the chat server works is:
There's a module RainbowBoi::Chat::Server, which takes a protocol string from the user, processes things by modifying files and stuff, and then returns a protocol-like string from the server (if applicable).
So therefore there is a CGI version too, which sends in the query string and prints out the results. The TCP server uses the same thing too but runs as a TCP, sending and receiving these strings over a socket.
The issue is: when I start up the TCP server, the CPU is fine. When I run two moderator bots that connect to this server, the CPU is still fine. And then I sign on with the chat client and it's still fine. But, when I close out of the chat client the CPU spikes up to 100% on the server process. When I end-task the server, the bot process spikes up to 100%.
I figured the problem might be that the server was holding on to dead sockets not knowing they were closed, and maybe doing something recursive with them. I tried programming something to time out old sockets every 15 seconds by sending 'PONG' (a protocol command) to them and closing the ones that fail to receive the message.
When I did this the first time, every now and then it would say "Can't call send on an undefined package" or something and crash (causing all the clients connected to spike up in CPU usage). So I put the entire thing in an eval, hoping it would close what sockets it could without crashing for what sockets it couldn't.
And this still didn't help.
Here are the codes if you wanna take a look:
TcpServer.pl (the TCP Server)
Quote:
#!/usr/bin/perl -w
use strict;
use warnings;
use IO::Socket;
use IO::Select;
use lib "./lib";
use RainbowBoi::Chat::Server;
# Load the server.
our $server = new RainbowBoi::Chat::Server (debug => 0);
require "subroutines.pl";
# Start the TCP Server.
our $sock = IO::Socket::INET->new (
LocalAddr => 'rainbowboi.com',
LocalPort => 7775,
Listen => 1,
Proto => 'tcp',
Reuse => 1,
) or die "Socket error: $!";
our $select = IO::Select->new ($sock);
print "Server started.\n";
my $users = {}; # Connections
# Set a timeout timer for 15 seconds.
our $timer = time() + 15;
while (1) {
# Loop for timeouts.
if (time() - $timer >= 0) {
$timer = time() + 15;
print "Checking timed-out sockets\n";
# Send a pong to every sock.
foreach my $socket (keys %{$users}) {
&reply ($socket,'PONG');
}
}
# Look for new events.
my @ready = $select->can_read (.1);
next unless(@ready);
# Go through each event.
foreach my $socket (@ready) {
# If the listening socket is ready...
if ($socket == $sock) {
my $new = $sock->accept();
$select->add ($new);
print $new->fileno . ": connected\n";
# Create data.
my $nid = $new->fileno;
$users->{$nid} = {
conn => 0,
logn => 0,
nick => '',
};
# Send a response.
&reply ($new,"TCPK");
}
else {
# Get their ID.
my $id = $socket->fileno;
# Read their request.
my $in = '';
$socket->recv ($in, 3072);
chomp $in;
my @lines = split(/\n/, $in);
foreach my $line (@lines) {
# Skip blank lines.
next if $line eq "";
sub flooders {
my ($bot,$user,$msg) = @_;
$user =~ s/\s//g;
# See if their data exists yet.
if (!exists $rb->{users}->{$user}) {
$rb->{users}->{$user} = {
curse_small => 0, # Number of simple curse words.
curse_major => 0, # Number of major curse words.
kicks => 0, # Number of kicks (5 = ban)
warnings => 0, # Number of warnings (3 = kick)
};
}
# Only RoomControl should check these.
return unless $bot =~ /$moderator/i;
# Run FloodCheck.
my $level = $rb->{flood}->check ($user,$msg);
# Are they flooding?
my $warn = 0;
my $kick = 0;
my $ban = 0;
my $reason = 0;
if ($level == 1) {
$kick = 1;
$reason = "for sending large messages";
}
elsif ($level == 2) {
$kick = 1;
$reason = "for spamming large messages";
}
elsif ($level == 3) {
$warn = 1;
$reason = "Stop repeating yourself or you will be kicked from the room";
}
elsif ($level == 4) {
$kick = 1;
$reason = "for repeating too much";
}
elsif ($level == 5) {
$kick = 1;
$reason = "for spamming";
}
# Are they swearing?
if ($warn == 0 && $kick == 0 && $ban == 0) {
foreach my $swear (@swears) {
if ($msg =~ /\b$swear\b/i) {
$warn = 1;
$reason = "Please don't swear in this chat room";
}
}
foreach my $curse (@curses) {
if ($msg =~ /\b$curse\b/i) {
$kick = 1;
$reason = "Please don't swear in this chat room";
}
}
}
# What to do?
if ($warn == 1) {
# Just warn them.
$rb->{users}->{$user}->{warnings}++;
# More than 3 warnings?
if ($rb->{users}->{$user}->{warnings} > 3) {
# Kick them instead.
$rb->{users}->{$user}->{warnings} = 0;
$kick = 1;
}
else {
$rb->{bots}->{$bot}->{client}->sendMessage ("/whisper $user $reason (warning $rb->{users}->{$user}->{warnings} of 3).");
print "\nWarning $user for repeating\n\n";
}
}
if ($kick == 1) {
# Kick them.
$rb->{users}->{$user}->{kicks}++;
# Kicked too many times already?
if ($rb->{users}->{$user}->{kicks} >= 5) {
# Ban him instead.
$ban = 1;
}
else {
$rb->{bots}->{$bot}->{client}->sendMessage ("$user is being kicked from the room: $reason");
$rb->{bots}->{$bot}->{client}->sendMessage ("/kick $user");
print "\nKicked $user $reason\n\n";
}
}
if ($ban == 1) {
# Ban them.
$rb->{users}->{$user}->{bans}++;
$rb->{bots}->{$bot}->{client}->sendMessage ("$user is being banned from the room $reason");
$rb->{bots}->{$bot}->{client}->sendMessage ("/ban $user");
print "\nBanned $user $reason\n\n";
}
# If they were being friendly...
if ($warn == 0 && $kick == 0 && $ban == 0) {
# RainbowBoi should respond to triggers.
Might want to consider using POE for this since it can handle unused sockets and errors. _________________ Check out Botworld! A dev resource for things bot.
Downloads, articles, news, fourm and more.
http://botworld.marzopolis.com
I did lots of debugging and found out what is the problem (or one of the problems)...
For some reason, the server wasn't being able to find out that a client disconnected. Like the "send message or disconnect" wasn't working. But what I narrowed it down to was, the recv command (to receive data from the socket) would continue to work but always return nothing on closed sockets.
I was trying to make a timeout that would update the socket's active time variable on each message, and after disconnecting the value of time - the active time was always 0, so I narrowed it down to... the server thought it was getting messages and always resetting the active time.
But anyway, long story short, I added an "or do" to the recv function, so when it receives an empty message it would disconnect the client. And it works! The CPU no longer spikes up anymore.
Here's the bit of that code:
Code:
# Read their request.
my $in = '';
$socket->recv ($in, 3072) or do {
$select->remove ($users->{$id}->{sock});
$users->{$id}->{sock}->close();
Nice KISS solution. I still prefer POE for this type of stuff rather than completely writing everything using sockets. _________________ Check out Botworld! A dev resource for things bot.
Downloads, articles, news, fourm and more.
http://botworld.marzopolis.com
I looked into some POE stuff and wrote a server using POE::Wheel:: modules and it all works great so far. One problem though is that the put() method seems to add a weird symbol at the end of every message sent.
The compiled chat client writes this in the system messages box (a.k.a. STDOUT and STDERR captor)
Code:
Unknown SERVER Command []
at /PerlApp/RainbowBoi/Chat/Client.pm line 222.
(the [] is a box symbol). The moderator bots (just your normal Perl DOS windows) warned "Unknown SERVER Command " but didn't show any weird symbol.
I remedied that by putting a \n at the end of every put command, since the client is programmed to take the server messages line-by-line. So it gets the data it needs and then warns about the unknown command.
Other than that it works great (although the remote IP is encoded, I saw an example on what to do to get it readible so that's not much a problem)
Here's my new code:
Code:
#!/usr/bin/perl -w
use strict;
use warnings;
use Data::Dumper;
use RainbowBoi::Chat::Server;
use POE qw(Wheel::SocketFactory Wheel::ReadWrite);
# Create the server object.
our $server = new RainbowBoi::Chat::Server (debug => 0);
require "subroutines.pl";