Re: TCP-based chat program



phider1 said:

I wrote a little UDP-based chat program. It's not very user-friendly,
requiring every user to add every other user to a list. Anyway, I
want to adapt it to TCP so you don't have to do that, as well as the
ability to talk to people behind firewalls without reconfiguring them.
The first problem I have is how to have the server application relay
messages to all the clients. The other problem is how to format the
output on the client application so that it can show what other people
are saying as well as having a prompt at the bottom. Currently I have
2 seperate apps, one that listens for incoming messages, and one to
send messages, but I don't think that'd work with TCP.

No doubt you'll get lots of complicated answers, and no doubt they'll be
good answers.

Me? I'm simple, me. I'd do it like this:

1) the server

In one way, this is simpler than the client, because you don't have to
worry about a GUI if you don't want to.

It is *not* my purpose here to teach you how to program TCP sockets in any
kind of detail; rather, I'll just touch on what I consider a nice simple
design that doesn't involve threads or anything scary like that.

Ingredients

(a) a listening socket
(b) two circular lists, LOGINLIST and CHATLIST

Method

do all the initialisation work for the listening socket
while not done
select on the listening socket, with a delay of 0 - it'll come
right back to you, telling you yay or nay

if there's an incoming connection request
create a new entry in the LOGINLIST, which will contain
the new socket for the connection, any login data, and
anything else you need to record for this user
endif

select on all the login sockets
for up to L entries in the LOGINLIST that have messages waiting
perform one step in your login protocol (e.g. get a user name,
or get a password, or reject a password, or whatever it is)
for this user, such that the /next/ step (if any) is up to the
user
if this step completes the login process
remove this item from LOGINLIST
add this item to CHATLIST
endif
rotate the list one place
endfor

select on all the chat sockets
for up to C entries in the CHATLIST that have messages waiting
receive the message from THISUSER
if this is a chat message
while the current user's *successor* is not THISUSER
send out the message to the current user in the CHATLIST
rotate the CHATLIST one place
endwhile
else
deal with the message as appropriate (eg PRIVMSG, LOGOFF,
or whatever)
endif
rotate the CHATLIST one place
endfor
endwhile

This is about as simple as it gets, and doesn't require you to have an
outlandish number of threads going - in fact, it's single-threaded and
non-blocking.

The "while not done" thing can be done using signal() to intercept Ctrl-C
and set a volatile sig_atomic_t to 1:

volatile sig_atomic_t done;

void term(int sig)
{
done = 1;
}

Register that function for handling SIGINTs, using signal().

Then your main loop is simply:

while(!done)
{
all that stuff I said
}

cleanup goes here - shutting down the listening socket, releasing
dynamically allocated memory, etc.



2) the client

The client is easier on the socket side, but harder on the interface side.
On Win32, the fast-n-dirty solution would be to use the console functions
(look up something like AllocConsole in MSDN, and then browse around that
family of functions - you should soon work it out). On Linux, probably
curses - or rather, ncurses.

If you can find an ncurses for Windows, you're laughing.

The point is that either of these techniques will allow you to address the
screen at a point of your choosing. This allows you to have a chat log
(typically a very large area) and a separate input zone (typically a
single line at the bottom), and the output messages won't have to choose
between waiting till you're done or corrupting your typing line. Some
clients also have other stuff in other screen areas - e.g. user name
lists, channel lists, etc.

The comms design is easy enough:

do the whole connection thing
while not done
select on the server socket
if there's a message waiting on the server
get the message
if it's a chat message
stick it in the chatlog zone
else
deal with it appropriately
endif
endif
if the user just typed a line
wrap it up as and if necessary, and send it to the server
clear the input zone
endif
endwhile
cleanup

Note: I took a quick look at your source to find out which platforms you're
interested in, and I saw this:

scanf("%s",name);

That is so 1988. Nowadays, we protect our buffers, by using a line-reading
buffer-protecting routine such as fgets, and then parsing it as necessary.
If you don't want to do that, at the very least you should tell scanf your
maximum buffer size, e.g. scanf("%11s", name); for your 12-byte buffer.

Note, too, that scanf will return the number of fields successfully
converted. If it's fewer than you asked for, that's obviously a problem,
and one that you can detect and correct for.

Incidentally, you have a WSACleanup call in your Linux send code. :-)

--
Richard Heathfield <http://www.cpax.org.uk>
Email: -http://www. +rjh@
Google users: <http://www.cpax.org.uk/prg/writings/googly.php>
"Usenet is a strange place" - dmr 29 July 1999
.



Relevant Pages

  • [PATCH 0/5] [RFC] AF_RXRPC socket family implementation [try #3]
    ... These patches together supply secure client-side RxRPC connectivity as a Linux ... kernel socket family. ... presentation side is left to the client. ... Each connection goes to a particular "service". ...
    (Linux-Kernel)
  • [PATCH 0/5] [RFC] AF_RXRPC socket family implementation
    ... These patches together supply secure client-side RxRPC connectivity as a Linux ... Make it possible for the client socket to be used to go to more than one ... Each connection goes to a particular "service". ...
    (Linux-Kernel)
  • [PATCH 0/5] [RFC] AF_RXRPC socket family implementation [try #2]
    ... These patches together supply secure client-side RxRPC connectivity as a Linux ... Make it possible for the client socket to be used to go to more than one ... Each connection goes to a particular "service". ...
    (Linux-Kernel)
  • Re: How do I tell an object to free up an owned object from thta object itself?
    ... I tested running a sequence of connect/disconnect from the client ... client address for those connections that were active. ... In the ClientConnect event I create a handler object for processing ... >Socket is in fact a TServerClientWinSocket which acts as a end-connection ...
    (comp.lang.pascal.delphi.misc)
  • Re: question about class Socket
    ... run the following code, the client send a test string, but it doesn't ... // State object for receiving data from remote device. ... // Client socket. ...
    (microsoft.public.dotnet.framework)