首页 新闻 会员 周边

多客户端问题请教 - C#

0
悬赏园豆:40 [已解决问题] 解决于 2023-08-07 17:56

请教大佬一个网络通信方面的问题,是一个上位机场景下的问题,与多台设备建立连接进行通讯。设备做服务端,每台设备都是一个服务端,我要在程序里创建多个客户端去连接设备。
我现在的程序是这样写的,我自定义了一个类MyTcpClient:

用来存储建立的链接以及每个连接对应的设备地址,通信是这么写的,在主线程里:

 List<DeviceData> resNetPort = devices.Where(x => x.CommMode == "NetPort").ToList();

            foreach (DeviceData item in resNetPort)
            {
                TcpClient client = new TcpClient();
                client.BeginConnect(item.NetworkAddress, int.Parse(item.Port), ConnectToServerAsync, client);
                MyTcpClient myTcpClient = new MyTcpClient(item.NetworkAddress, int.Parse(item.Port), client, item.Address, client.Connected);
                clients.Add(myTcpClient);
            }

            Task.Run(() =>
            {
                while (true)
                {
                    resetEvent.WaitOne();
                    foreach (var item in resNetPort)
                    {
                        resetEvent.WaitOne();
                        var result1 = clients.Where(x => x.ServerName == item.Address).ToList();
                        if (result1.Count > 0)
                        {
                            if (result1[0].Client.Client.Connected)
                            {
                                SendMsg(result1[0].Client, item.Command);
                            }
                        }
                        Thread.Sleep(2000);
                    }
                }
            });

获取是网口方式的设备列表,对每一项进行连接并存储连接内容到List<MyTcpClient>集合clients里,之后开个任务遍历设备列表中的每一项收发命令。异步连接接收啥的回调是这么写的:

       #region Net

        private void SendMsg(TcpClient client, string msg)
        {
            try
            {
                byte[] msgBytes = ByteArrayConversion.HexStringToByteArray(msg);
                client.GetStream().BeginWrite(msgBytes, 0, msgBytes.Length, (ar) => { client.GetStream().EndWrite(ar); }, client);
            }
            catch (IOException) // 无法写入连接,远程主机关闭了一个现有连接
            {
                client.Close();
                var res = clients.Where(x => x.Address == ((IPEndPoint)client.Client.RemoteEndPoint).Address.ToString() && x.Port == ((IPEndPoint)client.Client.RemoteEndPoint).Port).ToList();
                res[0].IsConnected = client.Connected;
                client.BeginConnect(((IPEndPoint)client.Client.RemoteEndPoint).Address.ToString(), ((IPEndPoint)client.Client.RemoteEndPoint).Port, ConnectToServerAsync, client);
            }
        }

        private  byte[] readBytes = new byte[4096];

        private void ConnectToServerAsync(IAsyncResult ar)
        {
            TcpClient temp = (TcpClient)ar.AsyncState;
            try
            {
                if (!temp.Connected)
                {
                    temp.Close();
                    temp = new TcpClient();
                    temp.BeginConnect(((IPEndPoint)temp.Client.RemoteEndPoint).Address, ((IPEndPoint)temp.Client.RemoteEndPoint).Port, ConnectToServerAsync, temp);
                }
                else
                {
                    temp.EndConnect(ar);
                    var res = clients.Where(x => x.Address == ((IPEndPoint)temp.Client.RemoteEndPoint).Address.ToString() && x.Port == ((IPEndPoint)temp.Client.RemoteEndPoint).Port).ToList();
                    res[0].IsConnected = temp.Connected;
                    temp.GetStream().BeginRead(readBytes, 0, readBytes.Length, ReceiveCallBack, temp);
                }
            }
            catch (SocketException)
            {
                var res = clients.Where(x => x.Address == ((IPEndPoint)temp.Client.RemoteEndPoint).Address.ToString() && x.Port == ((IPEndPoint)temp.Client.RemoteEndPoint).Port).ToList();
                res[0].IsConnected = temp.Connected;
            }
        }

        private void ReceiveCallBack(IAsyncResult ar)
        {
            try
            {
                TcpClient temp = (TcpClient)ar.AsyncState;
                int len = temp.GetStream().EndRead(ar);
                if (len > 0)
                {
                    string str = ByteArrayConversion.ByteArrayToHexString(readBytes, 0, len);
                    Invoke(new Action(() =>
                    {
                        richTextBox1.AppendText(str + "\n");
                    }));
                    temp.GetStream().BeginRead(readBytes, 0, readBytes.Length, ReceiveCallBack, temp);
                }
            }
            catch (Exception ex)
            {
                Invoke(new Action(() =>
                {
                    richTextBox1.AppendText(ex.Message + "\n");
                }));
            }
        }

        /// <summary>
        /// 断开连接
        /// </summary>
        public void Close(TcpClient tcpClient)
        {
            if (tcpClient != null && tcpClient.Client.Connected)
                tcpClient.Close();
            if (!tcpClient.Client.Connected)
            {
                tcpClient.Close();//断开挂起的异步连接
            }
        }

        #endregion Net

因为是多个设备,加入连不上,在ConnectToServerAsync或者发送过程中连接断开了,进行断线重连的时候怎样能准确的获取到我要对哪一个设备的IP、端口号进行重连呢。
假设一开始就是没有设备在线,那我的程序就会报错,各种错误,建立连接后断开设备也会报错:

想请教一下大佬们有什么好的方案

MonoiF的主页 MonoiF | 菜鸟二级 | 园豆:266
提问于:2023-08-07 11:26
< >
分享
最佳答案
0

断线重连是TCP客户端的基础功能,重连逻辑应该实现在TCP客户端类里面,每个客户端负责的自己的重连流程,不存在在不知道服务器IP、端口的情况。
客户端自己开个Task一直轮询做连接检测和重连,只能用Task实现,而且Task里面的休眠间隔函数必须是Task.Delay,不然线程会一直被占用。
而且第一次连接的逻辑可以替换为启动重连逻辑,这样即使第一次设备没上线也不会报错,上线后也能自动连接。
只是要注意在主动关闭连接的函数里面实现重连逻辑的停止,一般也就关闭程序时才会主动断开连接。

收获园豆:25
二次元攻城狮 | 菜鸟二级 |园豆:462 | 2023-08-07 17:22

嗯嗯,思路清晰,说的很多诶。刚才设计了一个客户端类,和你说的思路大差不差,在客户端类对传入的TCPClient进行单独的连接、收发以及断线重连,只程序中只需要存储每个客户端类的实例,但是没有像你说的这样客户端开个Task,我是在每次发送消息的时候在发送之前判断一次连接状态,正常就发,连接断开了就释放链接重连。这样在后台程序中就只需要遍历建立连接的List,不需要关心有没有发出去或者有没有正常连接😁

MonoiF | 园豆:266 (菜鸟二级) | 2023-08-07 17:54

说的很多诶 => 说的很对,打字手残。。。

MonoiF | 园豆:266 (菜鸟二级) | 2023-08-07 17:55
其他回答(2)
0

以下是一个示例的ConnectToServerAsync方法的实现,包括断线重连的逻辑和错误处理:

class MyTcpClient
{
    public string DeviceAddress { get; set; } // 设备的IP地址
    public int DevicePort { get; set; } // 设备的端口号
    public TcpClient TcpClient { get; set; } // TCP客户端连接

    public async Task ConnectToServerAsync()
    {
        while (true)
        {
            try
            {
                // 连接设备的逻辑...
                TcpClient = new TcpClient();
                await TcpClient.ConnectAsync(DeviceAddress, DevicePort);
                
                // 连接成功,跳出循环
                break;
            }
            catch (Exception ex)
            {
                // 处理连接异常...
                Console.WriteLine($"连接设备 {DeviceAddress}:{DevicePort} 失败,原因:{ex.Message}");
                
                // 等待一段时间后进行重连
                await Task.Delay(TimeSpan.FromSeconds(5));
            }
        }
    }

    // 其他方法...
}

在使用ConnectToServerAsync方法时,你可以通过DeviceAddressDevicePort属性来指定设备的IP地址和端口号。如果连接失败,它会在控制台输出错误信息,并等待一段时间后进行重连。

以下是一个示例的使用代码:

List<MyTcpClient> clients = new List<MyTcpClient>();

// 获取设备列表,假设是一个包含设备IP地址和端口号的集合
List<string> deviceList = GetDeviceList();

// 创建MyTcpClient实例并连接设备
foreach (var device in deviceList)
{
    MyTcpClient client = new MyTcpClient();
    client.DeviceAddress = device.Address;
    client.DevicePort = device.Port;
    
    clients.Add(client);
    
    // 连接设备
    await client.ConnectToServerAsync();
}

这样,即使一开始没有设备在线,程序也不会报错,并会在设备上线后自动进行断线重连。

GetDeviceList方法是根据你的具体需求来定义的。以下是一个示例的GetDeviceList方法,用于获取设备列表:

List<string> GetDeviceList()
{
    List<string> deviceList = new List<string>();

    // 根据你的需求,添加设备IP地址到设备列表
    // 例如,假设有3个设备的IP地址是192.168.0.1、192.168.0.2和192.168.0.3

    deviceList.Add("192.168.0.1");
    deviceList.Add("192.168.0.2");
    deviceList.Add("192.168.0.3");

    return deviceList;
}

根据你的具体情况,你可以在GetDeviceList方法中添加设备的IP地址。例如,如果你的设备是通过网络自动发现的,你可以在此方法中实现设备的自动发现逻辑,并将发现的设备IP地址添加到设备列表中。

请根据你的具体需求修改GetDeviceList方法,以适应你的项目。

收获园豆:15
lanedm | 园豆:2381 (老鸟四级) | 2023-08-07 14:13

感谢告知,刚才已经有了解决方案,但是还是感谢你的思路方法

支持(0) 反对(0) MonoiF | 园豆:266 (菜鸟二级) | 2023-08-07 16:40
0

下午看到一篇在自定义类中进行服务器连接,在他的基础上封装一个异步TCP客户端类,在类库中对传入的IP地址和端口号进行单例通信,同时自定义一个接收事件以告知外界状态和消息改动,同时单例通信也解决了断线重连以及设备不在线等问题

MonoiF | 园豆:266 (菜鸟二级) | 2023-08-07 16:45
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册