Thursday, August 23, 2012

TCP/IP Sockets in .NET 4

TCP is one of the core protocols of the Internet and is a bi-directional, client/server protocol. In .NET you can use the TcpListener Class for the server component, which provides simple methods that listen for and accept incoming connection requests from clients. You can use the TcpClient Class for the client component, which provides client connections to TCP server components; it provides simple methods for connecting, sending, and receiving stream-based data over the network.

Here's a standalone C# 4.0 app that demonstrates how to setup a TCP/IP sockets based client and server in .NET. It uses a command line option to determine whether to run the client or server. There's no external files or libraries needed, just the standard ones you get with a C# 4.0 console project. I've tested it with Visual Studio 2010 in Windows, and it will also work unchanged with Mono.

To run the server, from the command prompt...
   SocketsTest -server

To run the client, from another command prompt ...
   SocketsTest -client

Below is the source code ...


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;

/// 
/// Standalone TCP/IP sockets example using C#.NET 4.0
/// 
/// 
/// Compile as a C#.NET console mode application
/// 
/// Command line usage:
///   SocketsTest -client   => run the client
///   SocketsTest -server   => run the server
/// 
class SocketsTest
{
    static TcpListener listener;

    // Sample high score table data
    static Dictionary<string, int> highScoreTable = new Dictionary<string, int>() { 
            { "john", 1001 }, 
            { "ann", 1350 }, 
            { "bob", 1200 }, 
            { "roxy", 1199 } 
    };

    static int Port = 4321;
    static IPAddress IP_ADDRESS = new IPAddress(new byte[] { 127, 0, 0, 1 });
    static string HOSTNAME = "127.0.0.1";
    static int MAX_CLIENTS = 5;

    public static void Main(string[] args)
    {
        if (args.Contains("-server"))
        {
            ServerMain();
        }
        else if (args.Contains("-client"))
        {
            ClientMain();
        }
        else
        {
            Console.WriteLine("Usage: SocketsTest -client   => run the client");
            Console.WriteLine("       SocketsTest -server   => run the server");
        }
    }

    /// 
    /// Server receives player name requests from the client and responds with the score.
    /// 
    private static void ServerMain()
    {
        listener = new TcpListener(IP_ADDRESS, Port);
        listener.Start();
        Console.WriteLine("Server running, listening to port " + Port + " at " + IP_ADDRESS);
        Console.WriteLine("Hit Ctrl-C to exit");
        var tasks = new List();
        for (int i = 0; i < MAX_CLIENTS; i++)
        {
            Task task = new Task(Service, TaskCreationOptions.LongRunning);
            task.Start();
            tasks.Add(task);
        }
        Task.WaitAll(tasks.ToArray());
        listener.Stop();
    }

    private static void Service()
    {
        while (true)
        {
            Socket socket = listener.AcceptSocket();

            Console.WriteLine("Connected: {0}", socket.RemoteEndPoint);
            try
            {
                // Open the stream
                Stream stream = new NetworkStream(socket);
                StreamReader sr = new StreamReader(stream);
                StreamWriter sw = new StreamWriter(stream);
                sw.AutoFlush = true;

                sw.WriteLine("{0} stats available", highScoreTable.Count);
                while (true)
                {
                    // Read name from client
                    string name = sr.ReadLine();
                    if (name == "" || name == null) break;

                    // Write score to client
                    if (highScoreTable.ContainsKey(name))
                        sw.WriteLine(highScoreTable[name]);
                    else
                        sw.WriteLine("Player '" + name + "' was not found.");

                }
                stream.Close();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }

            Console.WriteLine("Disconnected: {0}", socket.RemoteEndPoint);
            socket.Close();
        }
    }

    /// 
    /// Client requests a player name's score from the server.
    /// 
    private static void ClientMain()
    {
        TcpClient client = new TcpClient(HOSTNAME, Port);
        try
        {
            // Open the stream
            Stream stream = client.GetStream();
            StreamReader sr = new StreamReader(stream);
            StreamWriter sw = new StreamWriter(stream);
            sw.AutoFlush = true;

            // Read and output the first line from the service, which
            // contains the number of players listed in the table.
            Console.WriteLine(sr.ReadLine());

            while (true)
            {
                // Input player name
                Console.Write("Enter player name: ");
                string name = Console.ReadLine();

                // Write name to server
                sw.WriteLine(name);
                if (name == "") break;

                // Read score from server
                Console.WriteLine(sr.ReadLine());
            }
            stream.Close();
        }
        finally
        {
            // Close the connection
            client.Close();
        }
    }
}




Follow @dodgy_coder

Subscribe to posts via RSS

18 comments:

  1. This is a great simple example! It really helped me to better understand how to create a simple client-server application. Thanks! :)

    ReplyDelete
    Replies
    1. Hi Derek, you're welcome, glad you found it useful. Out of interest this will also work unchanged on a Linux machine running via the Mono runtime. I have also recently tried it on a Raspberry Pi single board computer running under Debian linux.

      Delete
  2. I'm developing a Windows Form that executes some of these functions in a Background Worker, but for some reason, the Worker seems to be timing out. Have you worked with TCP/IP as a Background Worker before?

    ReplyDelete
    Replies
    1. Yes I have client code similar to the above running in a BackgroundWorker (from inside a WPF app) ... and haven't had any timeout problems. It sounds like your problem might be with the TCP/IP server side or a connection issue rather than the background worker itself?

      Delete
  3. Hi,

    I am getting an error..."The type or namespace 'task' could not be found(are you missing a using directive or an assembly reference?)

    I believe I have all the proper using statements. Could you please assist?

    ReplyDelete
    Replies
    1. I am thinking that I should be having a Class defining task, so that I can create the List, as in - var tasks = new List(); -

      Is this the correct assumption. Could you please advise?

      I want to create a TCP server that listens to multiple clients and I think if I could use your code as a starting point I can achieve that. Please assist.

      Delete
    2. Hi Onsongo, yes I believe you can use this code as a starting point for your project ... it definitely handles multiple clients. Regarding the message about the 'Task' class ... the 'Task' class is found in the .Net 4.0 assemblies and should be already referenced from a .Net 4.0 console application project. Are you using visual studio 2010 with .net 4.0? Or another environment?

      Delete
    3. Thank you for your reply.

      I actually solved the problem by referencing the System.Threading.Tasks.dll explicitly, from the C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETCore\v4.5 folder. Incidentally, I am using .NET 4.5 Framework (VS 2012).

      I also noticed that I was using task instead of Task in my declaration. Silly me, I was not looking carefully at the code.

      I am sure now that i can move forward from here.

      Thanks again for you reply. It gave me an idea on how to resolve this.

      Kind Regards,

      Onsongo Moseti.

      Delete
  4. Hello Dodgy,
    Do you happen to have a version of this program that allows the server to handle multiple clients? I have seen plenty of bad examples out there but not a single good one yet!

    Thanks,
    Sainey

    ReplyDelete
    Replies
    1. This will work with multiple clients ... check out the MAX_CLIENTS constant (currently set to 5) ... I admit, this isn't the most efficient nor elegant code out there, but it works, AFAIK :-)

      Delete
  5. Nice!

    I know you're not updating the dictionary anywhere, but perhaps you should consider ConcurrentDictionary to avoid concurrency issues, and while you're at it, use TryGetValue to get the value while checking for it.

    ReplyDelete
  6. What would you do if it was a chat server with thousands of clients? Do you have a sample for something like that ?

    ReplyDelete
    Replies
    1. Theoretically it should be able to handle it ... just set the MAX_CLIENTS value to 1000 or whatever your limit is. I haven't tried it though. You might also want to take a look at golang or node.js, which might be better suited to your project.

      Delete
  7. C# multi threaded socket programming

    http://csharp.net-informations.com/communications/csharp-multi-threaded-socket.htm

    ling

    ReplyDelete
  8. Had to change this line:
    var tasks = new List<Task>();
    Then it complies and works well.
    Thanks

    ReplyDelete
  9. Thanks, I have been looking for something like this for a while! Brian

    ReplyDelete