simple TcpClient echo client/server example
I was trying to help a beginner dev that (like many early on) wanted to tackle an irc bot as their first project. They had apparently googled for C# irc and came across this code. Now, having spent a couple of my college years running my own irc server on EFNet, I'm not really inclined to post another irc bot by any means, but I would like to post a tiny and intentionally naïve TcpClient example that improves on the linked code on a couple of issues, specifically:
- Dispose of your IDisposable types! Brad and Joe already know how I feel about that, of course :) (Hint: Close() was there so they called it - the API didn't guide them forcefully enough to use Dispose() instead, but that's IMHO of course)
- Never flush manually if the API will do it for you. You're sure to miss a Flush call that was needed.
static void Main(string[] args)
{
Thread serverThread = new Thread((ThreadStart)delegate
{
TcpListener listener = new TcpListener(IPAddress.Loopback, 1234);
listener.Start();
using (TcpClient client = listener.AcceptTcpClient())
using (NetworkStream stream = client.GetStream())
using (StreamReader reader = new StreamReader(stream))
using (StreamWriter writer = new StreamWriter(stream))
{
writer.AutoFlush = true;
string inputLine;
while ((inputLine = reader.ReadLine()) != null)
{
writer.WriteLine("Echoing string: " + inputLine);
}
Console.WriteLine("Server saw disconnect from client");
}
});
serverThread.Start();
string[] linesToSend = new string[] { "foo", "bar", "ack" };
using (TcpClient client = new TcpClient("127.0.0.1", 1234))
using (NetworkStream stream = client.GetStream())
using (StreamReader reader = new StreamReader(stream))
using (StreamWriter writer = new StreamWriter(stream))
{
writer.AutoFlush = true;
foreach (string lineToSend in linesToSend)
{
Console.WriteLine("Sending to server: {0}", lineToSend);
writer.WriteLine(lineToSend);
string lineWeRead = reader.ReadLine();
Console.WriteLine("Received from server: {0}", lineWeRead);
Thread.Sleep(2000); // just for effect
}
Console.WriteLine("Client is disconnecting from server");
}
// wait for the server to exit before Main
// this way, we make sure to keep Console
// around since the server writes to it
serverThread.Join();
}
What do you think about stacking using blocks like this? Should each have its own brackets, putting the current body 3 more levels deep? Is this formatting obvious or does the sacrifice of additional whitespace put more burden on the person reading the code?
I like to take advantage of my disposable types that need to live (approximately) the same lifetimes in this manner, but it’s surprisingly (to me) controversial as it’s a departure from the conventional “always add the brackets” stance. However, that stance is more about not trying to save vertical whitespace (which in those if/for/while loops, I tend to like the additional vertical whitespace the brackets bring) but instead saving horizontal whitespace.
I tend to rationalize this kind of departure as working around a weakness in the using syntax – had these 4 instances all been of the same type, I could have combined them into a single using statement that would have clearly been one level of indentation instead of 4, so it doesn’t seem fair that them being 4 different types should result in a punishment of a 12x8 block of additional whitespace.
Also, I’m cheating in that the server could fail to bind the port (listener.Start()) fast enough for it to be ready for the client’s attempted connect.
Comments
- Anonymous
December 19, 2004
Cyrus was the first where I learned about that multiple "using"s are stackable without additional braces. Much better to read than the indented version would be. - Anonymous
December 20, 2004
James, I'm probably going to reveal some of my ignorance here, but I would think a big part of whether you want the additional braces or not depends on how (and how much) you do exception handling.
I would think that if you want to clearly handle each using statement's potential exceptions, you'd want the separate brackets so you can clearly show which exceptions can happen at which phase. If you don't really care to show that (or if you're just going to catch Exception or some rollups or if each of these can throw roughly the same set of exceptions), things might be different.
But in the more general sense, I wouldn't want to address that kind of thing "definitively" if I knew the code wasn't "done" yet anyway - that's where one of those //TODO: statements should show up :)
Chris - Anonymous
June 18, 2009
PingBack from http://adirondackchairshub.info/story.php?id=3368