IntroductionDownload: SimpleChat.zip
For multi-person chat programming, programmer might use pthread (multi-threading) or fork (multi-processing) for multiplexing. In the early of 1990s, select() was frequently used for non-blocking communication. and it still been used for nowadays server. As we know, socket is just a one of the file descriptor. Thus, someone could enable the non-blocking property through fcntl() (control open file descriptors) for non-blocking communcation. However, the performance issue might come after this setting. So, in this topic, I would like to show you how to use the conventional way to implement Chat Server via select(). For chat client, I will aslo introduce you two methods, one is select() in perl, another one is QThread in Qt.
Server-Side
#! /usr/bin/perl -w
###############################################################
# Project: Perl Server Socket for multiperson chat
# Description: A convention way to implement multiperson chat
# via select().
# Date: 2009.05.16
# Programmer: Lin Xin Yu
# Website:http://importcode.blogspot.com
###############################################################
use strict;
use warnings;
use IO::Socket;
use IO::Select;
#=============== Network Settings =============================
my $MAX_LEN = 1024;
my $host = 'localhost';
my $port = shift || 3456;
#==============================================================
#********** THIS IS THE MAIN PROGRAM **************************
#==============================================================
my $buf;
# Create the receiving socket
my $server = new IO::Socket::INET( LocalPort => $port,
Proto => 'tcp',
Listen => SOMAXCONN,
Reuse => 1,)
or die "Could not create socket: $!\n";
#=============== Select() Settings ============================
# create handle set for reading
my $readSet = new IO::Select();
# add server socket into readSet
$readSet->add($server);
$readSet->add(\*STDIN);
my %socketSet = ();
#=============== Main Process =================================
while (1) {
# get a set of readable socket (blocks until at least one socket is ready)
my ($readableSet) = IO::Select->select($readSet, undef, undef, 0);
# take all readable socket in turn
foreach my $socket (@$readableSet) {
# accpeting new client if the socket is serverSocket
if ($socket == $server) {
my $client = $server->accept();
$readSet->add($client);
$socketSet{$client} = $client;
}elsif($socket == \*STDIN){
$buf = <STDIN>;
if($buf =~ /^(exit|quit)/){
foreach my $closeSocket(values %socketSet){
syswrite($closeSocket, "Server is going to offline after 5 Seconds\n", $MAX_LEN);
}
sleep(5);
foreach my $closeSocket(values %socketSet){
close($closeSocket);
}
close($server);
exit(0);
}
}
# otherwise it is an ordinary socket and we should read and process the request
else {
sysread($socket, $buf, $MAX_LEN);
if($buf) {
if($buf =~ /^(:exit|:quit|:q)/i){
$buf =~ s/^(:exit|:quit|:q)[ ]+//;
delete $socketSet{$socket};
$readSet->remove($socket);
close($socket);
}
foreach my $closeSocket(values %socketSet){
if($closeSocket != $socket){
syswrite($closeSocket, $buf, $MAX_LEN);
}
}
} else { # the client has closed the socket or aborted
# remove the socket from the $readSet and close it
delete $socketSet{$socket};
$readSet->remove($socket);
close($socket);
}
}
}
}
#==============================================================
#********** END OF THE MAIN PROGRAM ***************************
#==============================================================
As you can see, server just simply check the available socket and given the suitable processing. In addition to use select(), programmer must remember to use system IO rather than buffering IO for non-blocking communication.
Client-Side
Perl Client
#! /usr/bin/perl
###############################################################
# Project: Perl Client Socket for multiperson chat
# Description: A convention way to implement multiperson chat
# via select().
# Date: 2009.05.16
# Programmer: Lin Xin Yu
# Website:http://importcode.blogspot.com
###############################################################
use strict;
use warnings;
use IO::Socket;
use IO::Select;
#=============== Network Settings =============================
my $MAX_LEN = 1024;
my $remote_host = shift || 'localhost';
my $remote_port = shift || 3456;
my $client;
#==============================================================
#********** THIS IS THE MAIN PROGRAM **************************
#==============================================================
# get use name from input
print "please enter your name:";
chomp(my $name = <STDIN>);
print STDERR $name.":";
# Socket connection for Client.
$client = new IO::Socket::INET( PeerAddr => $remote_host,
PeerPort => $remote_port,
Proto => 'tcp',
Type => SOCK_STREAM,
Timeout => 20)
or die "Couldn't connect to $remote_host:$remote_port : $@\n";
my $buf;
#=============== Select() Settings ============================
# create handle set for reading
my $readSet = new IO::Select();
# add client socket into readSet
$readSet->add($client);
$readSet->add(\*STDIN);
while (1) {
# get a set of readable socket (blocks until at least one socket is ready)
my ($readableSet) = IO::Select->select($readSet, undef, undef, 0);
# take all readable socket in turn
foreach my $socket (@$readableSet) {
if($socket == \*STDIN){
chomp($buf = <STDIN>);
if($buf =~ /^(:exit|:quit|:q)/){
close($client);
exit(0);
}else{
syswrite($client, "$name:$buf\n", $MAX_LEN);
print STDERR $name.":";
}
}
# otherwise it is an ordinary socket and we should read and process the request
else {
sysread($socket, $buf, $MAX_LEN);
if($buf) {
my $i=0;
while($i++<256){
print STDERR "\b";
}
print STDERR $buf;
print STDERR $name.":";
} else { # the server has close the socket or aborted
# remove the socket from the $readSet and close it
close($client);
exit(0);
}
}
}
}
#==============================================================
#********** END OF THE MAIN PROGRAM ***************************
#==============================================================
One of the major problem for this perl chat client is your standard output might be interrupted by incoming message. Do NOT report it as a bug, it's a characteristic of system IO.
Qt Client
I don't want to show the whole bunch source code on the blog. Here I just post the most important sections of incoming and outcoming.
incoming parts in charclient.cpp
bool ChatClient::onSendClicked(){
int ret;
char msg[256];
QString qmsg = nickName+":"+ui.lineEdit_input->text();
ui.textEdit_output->append(qmsg);
ui.lineEdit_input->setText("");
qmsg += "\n";
QStringToChar(qmsg, msg, qmsg.length());
try{
ret = client->send(msg);
if(ret <= 0 ) throw ret;
}catch(int err){
if(err == 0) ui.textEdit_output->append("Server is offline!\n");
else if(err == -1) ui.textEdit_output->append("Fail to send message to Server!\n");
}
return true;
}
outcoming parts in ClientRecvThread.cpp
void ClientRecvThread::run(){
int ret;
char buf[MAX_BUF];
while((ret = socket->recv(buf)) > 0){
buf[ret-1]='\0';
ui->textEdit_output->append(buf);
}
if(ret == 0){
ui->textEdit_output->append("Server is offline or close the connection");
}else{
ui->textEdit_output->append("Fail to receive message from server!");
}
exit(0);
}
The reasonable solution for chat client is a GUI program. Thus, we can separate input and output message into two widgets, and processing it respectively. Here, I use C++ and Qt framework for this fancy work.
After a successful connection, this program will have two threads at the same time. One is the main thread which was created by main program itself, another one is ClientRecvThread which will been activated when the connection success.
The ClientRecvThread class which inherit from QThread was created for listening the incoming message only. It must be a great challenge while doing the multi-threading programming in this project, especially when you encountering the race condition or thread collection. Any single mistabke might kick your program in the hell. For instance, in Qt chat client, those two threads(main and ClientRecvThread) will sometimes use the same resource (QTextEdit) at the same time, and probably crash the whole program. A possible solution for these situation is locking the resource while one thread is now processing with it. but in this simple demonstration, I didn't fix it.
#For rapidly development, it's better to use QThread and QTcpSocket rather than pthread and self-implemented socket.
#QThread is similar to Java's runnable, so you might feel comfortable if you already know Java.
Compiling
There is no need to tell you how to compile perl program ( casue it's a interpreted language ). For Qt Client, you just need to follow the following steps:
- Download and unzip source code
- Type 'make' to compile it
- Waiting for fews seconds
- Run the program or just click it
This program was developed with Qt 4.5.1, so I don't pretty sure that the old version compiler can work without any error.
Screenshot
Client - Xinyu

Client - Lin Xin Yu

Conclusion- Socket is a typical solution for the integration with several different programming languages.
- Select() is still a good way for non-blocking communication.
- Multi-threading can save more resources than multi-processing, but difficult to handle.
- A good C++ style program can even have the same performance with C.
- Let the heavy loading program as simple as possible.
Any suggestions would be appreciated, but may not be taken.
For more detailed information, please see the source code.