通过TCP共享COM端口

3

如何简单地实现将COM端口通过TCP共享给多个客户端的设计模式?

例如,一个本地GPS设备可以在实时情况下向远程主机传输坐标。

因此,我需要一个程序,它会打开串口并接受多个TCP连接,例如:

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);
        }
    }
}

我需要一个类来处理连接客户端之间的通信,使它们都能看到数据并保持同步,以便客户端命令按顺序接收:

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();
        }
    }
}

我正在努力解决的问题是如何促进SerialPort实例和线程之间的通信。

我不确定上面的代码是否是最好的解决方案,所以是否有其他解决方案(越简单越好)?

2个回答

2
为什么要在低级别(套接字)编写代码?为什么不使用WCF作为客户端和服务器之间的通信,提供一个更干净、强类型的接口,而不是直接访问GPS设备呢?
像这样的设备通常最好独立于调用者进行管理——也就是说,您有自己的单独线程来与GPS设备通信,以适当的间隔轮询它,并使用当前位置填充共享数据结构——而客户端则进行服务调用,并从共享数据结构中获取数据。所有错误处理和恢复工作都由GPS线程处理,客户端不需要涉及此类问题。他们可以进行非阻塞调用以获取状态更新,这些更新可能包括状态“位置不可用”,而GPS线程正在疯狂地尝试重新建立通信。
因此,我会创建一个服务,抽象出处理这个特定设备的细节,并向客户端提供一个清晰的接口。例如,它可以提供像GetPosition()这样的服务,返回一些类似于“GeoCoordinate”的类。这样,如果您需要支持其他位置感知设备,可以添加它们而不必更改客户端代码。
                   GPS <--Serial--> Server <--WCF--> Clients

我有一个与数百个不同设备通信的系统,许多设备通过串行端口和其他半可靠连接进行通信,这是我使用的方法。详情请参见http://blog.abodit.com
----- 根据您的额外要求使用TELNET:可能像下面这样:
创建一个处理与设备本身所有通信的线程。
创建一个封装单个WorkItem的类 - 要发送的内容、响应以及WaitHandle。
使用队列将客户端请求排队。每个客户端都在WaitHandle上等待其响应准备就绪。
让单个通信线程从该队列中拉出工作项,将其发送到GPS设备,获取响应,在WorkItem中存储响应(或为失败设置标志),然后设置等待句柄以表示WorkItem完成。
如果请求比GPS设备处理的速度快,添加代码使其可以返回来自最后一个成功请求到设备的小时间窗口内到达的请求的缓存值。
实际上,您现在向所有客户端呈现虚拟的GPS设备,但在内部,您正在对他们的所有请求进行序列化(在队列上)并在单个线程上管理与GPS设备的通信,以便可以轻松地进行请求-响应周期而没有干扰。
这还允许您在等待句柄上很好地超时,以通知客户端当前没有可用的响应。

感谢您建议使用WCF。但是,我在问题中忘了提到我想使用telnet客户端进行连接,并且这将是双向通信,而不仅仅是接收。我很好奇看到您的实现,但是我在您的博客上找不到任何代码 - 您有任何示例吗?我仍然想在套接字级别上工作,但我不知道如何与不同的线程通信。 - Mark G
抱歉,博客上没有代码,因为它是一个庞大的系统。我会在我的答案中添加一些进一步的建议。 - Ian Mercer
感谢更新和架构建议 - 使用队列是一个有效的解决方案。我还发现了以下链接,显示了如何将COM端口链接到单个TCP连接:http://www.javiervalcarce.eu/wiki/Access_to_serial_port_from_Internet - Mark G

0

你可能已经使用了socat、ser2net和其他程序,但我的经验非常糟糕...无法正常工作。我写了这个小的Python程序,可能会有用。更新端口、波特率...然后使用任何TCP客户端。如果不想将其用作自动可执行脚本,请删除第一行。

#!/usr/bin/python

import socket
import sys
import serial

#open serial port
ser = serial.Serial('/dev/ttyAMA0', 115200, timeout=0)
#create socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)

#bond to the port. Don't use localhost to accept external connections
server_address = ('', 2105)
print('starting up on {} port {}'.format(*server_address))
sock.bind(server_address)

#listen
sock.listen(1)

#loop
while True:
    #waits for a new connection
    print('waiting for a connection')
    connection, client_address = sock.accept()
    try:
        print('connection from', client_address)
        #continously send from serial port to tcp and viceversa
        connection.settimeout(0.1)
        while True:
            try:
                data = connection.recv(16)
                if data == '': break
                ser.write(data)
            except KeyboardInterrupt:
                connection.close()
                sys.exit()
            except Exception as e:
                pass
            received_data = ser.read(ser.inWaiting())
            connection.sendall(received_data)
    except Exception as e:
        print e

    finally:
        #clean up connection
        connection.close()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接