TCP Chat - Server
I've seen many networking tutorials that start you with the client code. I think it's better to start off with the server code first. And there is quite a bit here.
The main two classes that are used here are TcpClient
& TcpListener
. In a nutshell, we use TcpListener
to listen for incoming connections, then once someone has connected, we spin up a new TcpClient
to talk to that remote process.
Here is the code for the server:
When instancing the class TcpChatServer
, the listener is created but doesn't start listening until we call Start()
on it in the Run()
method. IPAddress.Any
, means "let anyone from any network interface connect to me."
The Run()
method is the main heart of the program. Like said above, when we make the call to _listener.Start()
, the TcpListener
will start listening for new incoming TCP connections the port we fed it (6000 our case). If there is a new pending connection (checked via the Pending()
method), we break into a function see who the new client is.
_handleNewConnection()
does what the name implies. After the connection has been established we can grab it's TcpClient
instance by calling AcceptTcpClient
in the listener, the client should send a message stating either viewer
or name:MY_NAME
.
- If we have a new Viewer, recognize that client as a viewer, and then send them a welcome message
- If we have a new Messenger, verify that
MY_NAME
hasn't been taken yet- If so, enqueue a new message to tell the Viewers that a new friend has joined us
- Else, close the connection
- Anything else, say that we couldn't identify who they are, and then close the connection
Back in Run()
, we check for disconnects, new messages, and then send queued ones in the rest of the loop. There is also a call to Thread.Sleep(10)
, so that we save on CPU resources.
_checkForDisconnects()
are two foreach
loops that go through all of the clients and use the _isDisconnected()
method to test for the FIN/ACK packets that might have been sent.
The Connected
property of TcpClient
might seem like the right thing to use here instead of _isDisconnected()
, but there's a problem with that. Connected
returns true
only if the last IO operation was a success. This means that the FIN/ACK might have been sent, but since there were no IO operations on our end, Connected
will still return true
. Be careful of this.
_checkForNewMessages()
is also a foreach
check of all the Messengers. We can use the Available
property to see how big their message is (in bytes), and then read that from the client's NetworkStream
.
_sendMessages()
empties the _messageQueue
by writing the the Viewer's streams.
There is also the function _cleanupClient()
. It's a small helper that closes both the TcpClient
's NetworkStream
and itself.
If you are wondering why we need to manually close the stream ourselves, is because the stream is created from the underlying Socket
object (which can be accessed via TcpClient
's Client
property). This means you need to clean it up manually. Read more about it here.