请教大佬一个网络通信方面的问题,是一个上位机场景下的问题,与多台设备建立连接进行通讯。设备做服务端,每台设备都是一个服务端,我要在程序里创建多个客户端去连接设备。
我现在的程序是这样写的,我自定义了一个类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、端口号进行重连呢。
假设一开始就是没有设备在线,那我的程序就会报错,各种错误,建立连接后断开设备也会报错:
想请教一下大佬们有什么好的方案
断线重连是TCP客户端的基础功能,重连逻辑应该实现在TCP客户端类里面,每个客户端负责的自己的重连流程,不存在在不知道服务器IP、端口的情况。
客户端自己开个Task一直轮询做连接检测和重连,只能用Task实现,而且Task里面的休眠间隔函数必须是Task.Delay,不然线程会一直被占用。
而且第一次连接的逻辑可以替换为启动重连逻辑,这样即使第一次设备没上线也不会报错,上线后也能自动连接。
只是要注意在主动关闭连接的函数里面实现重连逻辑的停止,一般也就关闭程序时才会主动断开连接。
嗯嗯,思路清晰,说的很多诶。刚才设计了一个客户端类,和你说的思路大差不差,在客户端类对传入的TCPClient进行单独的连接、收发以及断线重连,只程序中只需要存储每个客户端类的实例,但是没有像你说的这样客户端开个Task,我是在每次发送消息的时候在发送之前判断一次连接状态,正常就发,连接断开了就释放链接重连。这样在后台程序中就只需要遍历建立连接的List,不需要关心有没有发出去或者有没有正常连接😁
说的很多诶 => 说的很对,打字手残。。。
以下是一个示例的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
方法时,你可以通过DeviceAddress
和DevicePort
属性来指定设备的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
方法,以适应你的项目。
感谢告知,刚才已经有了解决方案,但是还是感谢你的思路方法
下午看到一篇在自定义类中进行服务器连接,在他的基础上封装一个异步TCP客户端类,在类库中对传入的IP地址和端口号进行单例通信,同时自定义一个接收事件以告知外界状态和消息改动,同时单例通信也解决了断线重连以及设备不在线等问题