views:

41

answers:

1

What would be a simple design pattern for sharing a COM port over TCP to multiple clients?

For example, a local GPS device that could transmit co-ordinates to remote hosts in realtime.

So I need a program that would open the serial port and accept multiple TCP connections like:

class Program
{
    public static void Main(string[] args)
    {
        SerialPort sp = new SerialPort("COM4", 19200, Parity.None, 8, StopBits.One); 

        Socket srv = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        srv.Bind(new IPEndPoint(IPAddress.Any, 8000));
        srv.Listen(20);

        while (true)
        {
            Socket soc = srv.Accept();
            new Connection(soc);
        }
    }
}

I would then need a class to handle the communication between connected clients, allowing them all to see the data and keeping it synchronized so client commands are received in sequence:

class Connection
{
    static object lck = new object();
    static List<Connection> cons = new List<Connection>();

    public Socket socket;
    public StreamReader reader;
    public StreamWriter writer;

    public Connection(Socket soc)
    {
        this.socket = soc;
        this.reader = new StreamReader(new NetworkStream(soc, false));
        this.writer = new StreamWriter(new NetworkStream(soc, true));
        new Thread(ClientLoop).Start();
    }

    void ClientLoop()
    {
        lock (lck)
        {
            connections.Add(this);
        }
        while (true)
        {
            lock (lck)
            {
                string line = reader.ReadLine();
                if (String.IsNullOrEmpty(line))
                    break;

                foreach (Connection con in cons)
                    con.writer.WriteLine(line);
            }
        }
        lock (lck)
        {
            cons.Remove(this);
            socket.Close();
        }
    }
}

The problem I'm struggling to resolve is how to facilitate communication between the SerialPort instance and the threads.

I'm not certain that the above code is the best way forward, so does anybody have another solution (the simpler the better)?

+1  A: 

Why write at such a low-level (sockets)? Why not use WCF as the communication between the clients and the server and present a cleaner, strongly-typed interface instead of raw access to the GPS device?

Devices like this are often best managed independently from the clients calling in - i.e. you have your own separate thread that talks to the GPS device, polling it at the appropriate interval and populating shared data structures with the current location - while the clients make service calls and are supplied with data from the shared data structures. All error handling and recovery for the sometimes unreliable device connection is handled by the GPS thread and the clients don't need to each get involved with such nastiness. They can make non-blocking calls to get status updates and those updates might include a status 'position unavailable' while the GPS thread is frantically trying to re-establish communication.

So I would create a service that abstracts the particulars of dealing with this specific device and provides a clean interface to the clients. It might for example offer a services like GetPosition() which returns some class like "GeoCoordinate". That way if you ever need to support other location sensing devices you can add them without making any changes to the client code.

                   GPS <--Serial--> Server <--WCF--> Clients

I have a system that communicates with hundreds of different devices, many over serial ports and other semi-reliable connections and this is the approach I use. See http://blog.abodit.com.

----- per your additional requirement to use TELNET: maybe something like:

Create a thread that handles all communication with the device itself.

Create a class that encapsulates a single WorkItem - what to send, the response, and a WaitHandle.

Use a Queue to queue up requests from clients. Each client waits on the WaitHandle for its response to be ready.

Let the single communication thread pull work items off that queue, send them to the GPS device, get the response, store the response in the WorkItem (or set a flag for failures), and then set the wait handle to say that the WorkItem is done.

If the requests come in faster than the GPS can handle, add code so it can return cached values for requests coming within a small time window from the last successful request to the device.

In effect you are now presenting a virtual GPS device to all the clients but internally you are serializing all their requests (on a Queue) and managing communication with the GPS device on a single thread so you can do the Request-Response cycle easily without interference.

This also allows you to time-out nicely (on the wait handle) to inform a client that no response is currently available.

Hightechrider
Thank you for your suggestion to use WCF. However, I neglected to mention in my question that I would like to use telnet clients to connect and that it would be two way communication, not just receive.I was curious to see your implementation, but I couldn't find any code on your blog - do you have any examples? I'd still like to work at socket level, but I'm stalled at how to communicate with the different threads.
No code on the blog, sorry, it's a massive system. I'll add some further suggestions to my answer.
Hightechrider
Thanks for the update and the architecture suggestion - using the queue is an effective solution. I also found the following link that shows how to link a COM port to a single TCP connection: http://www.javiervalcarce.eu/wiki/Access_to_serial_port_from_Internet