使用socket 与西门子PLC仿真通讯时,连接plc没有问题,读取数据时报错:远程主机强迫关闭了一个现有的连接。
定位到代码 tcpClient.Receive(buffer, SocketFlags.None);
相关截图
相关代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using thinger.DataConvertLib;
using xktComm;
namespace FrmLogin
{
public partial class FrmMain : Form
{
public FrmMain()
{
InitializeComponent();
SiemensS7 s7 = new SiemensS7();
//取消跨线程访问问题
Control.CheckForIllegalCrossThreadCalls = false;
}
//声明一个socket对象
Socket tcpClient;
//创建取消数据源
private CancellationTokenSource cts;//= new CancellationTokenSource();
bool isConn;
///
/// 连接PLC
///
///
///
private void Btn_Conn_Click(object sender, EventArgs e)
{
if (this.btn_Conn.Text == "连接")
{
//实例化
tcpClient = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
cts = new CancellationTokenSource();
EndPoint ep = new IPEndPoint(IPAddress.Parse(txtb_IPAddress.Text), int.Parse(txtb_Port.Text));
try
{
tcpClient.Connect(ep);
this.btn_Conn.Text = "断开";
this.btn_Conn.BackColor = Color.Green;
isConn = true;
}
catch (Exception ex )
{
ShowMessage("连接失败:" + ex.Message);
return;
}
Task.Run(new Action(() =>
{
GetPLCValue();
}));
}
else
{
tcpClient?.Close();
cts.Cancel();
isConn = false;
this.btn_Conn.Text = "连接";
this.btn_Conn.BackColor = DefaultBackColor;
}
}
int i = 0;
private void GetPLCValue()
{
while (isConn)//(!cts.IsCancellationRequested)
{
Thread.Sleep(10);
byte[] buffer = new byte[200];
int length = -1;
try
{
length = tcpClient.Receive(buffer,SocketFlags.None);
}
catch (Exception ex)
{
// i += 1;
ShowMessage("本次数据读取失败:" + ex.Message);
}
if (length == 20 )
{
byte[] result = thinger.DataConvertLib.ByteArrayLib.GetByteArrayFromByteArray(buffer,0,length);
float aa = thinger.DataConvertLib.FloatLib.GetFloatFromByteArray(result, 0, DataFormat.ABCD);
txtsh_data.VarValue = aa.ToString();
}
}
}
///
/// 清空显示信息
///
///
///
private void Btn_Clear_Click(object sender, EventArgs e)
{
this.txtb_Message.Clear();
i = 0;
}
private void ShowMessage(string str)
{
if (!txtb_Message.Text.Contains(str))
{
txtb_Message.AppendText(str + "\r\n");
}
}
private void Button1_Click(object sender, EventArgs e)
{
byte[] buffer = new byte[200];
int length = -1;
try
{
length = tcpClient.Receive(buffer, SocketFlags.None);
}
catch (Exception ex)
{
i += 1;
ShowMessage("本次数据读取失败:" + (i).ToString() + "次:" + ex.Message);
}
if (length == 20)
{
byte[] result = thinger.DataConvertLib.ByteArrayLib.GetByteArrayFromByteArray(buffer, 0, length);
float aa = thinger.DataConvertLib.FloatLib.GetFloatFromByteArray(result, 0, DataFormat.ABCD);
// ShowMessage(aa.ToString());
}
}
}
}
使用网口助手和PLC进行通讯正常,使用网口助手做服务器,C#程序做客户端也能正常通讯,但是使用C#程序做客户端访问plc服务器时报错。
建议wireshark抓取以下两个网络包,然后分析比较有何不同
1.使用网口助手和PLC进行通讯正常时
2.使用C#程序做客户端访问plc服务器报错时
plc作为硬件只支持有限连接-----原因你应该可以想到,单片也好,plc也罢毕竟都是单一芯片,有限内存
所以当一个连接连接上去,长时间不发数据包,也不发心跳。他会认为你在玩他,所以会一脚把你T了
当然这代码目前看来毛病也不少,不过到不是你现在问题的原因,现在的问题其实错误本身就告诉你了“远程主机主动断开”--就是说远程主机主动发送了一个“close”或者“abandon”给你
你说,你用网口助手测试过,那么请在网口助手上主动关闭服务模拟 或者 主动close你这个client看看,你会模拟出这种错误异常来
ps:实际开发中tcp有窗口机制,所以你这里legth==20 不靠谱,同时因为窗口机制,对方发送会等待你缓冲区确认,如果你这里太过耗时,那么对方可能收到一个timeout异常,通常情况下俺们大部分人的处理方式就是如果tmeout我就主动close了,这样你也会得到一个“主动关闭”的异常
另外在附加一些常规服务器认为应该把你T掉的原因:
1.长时间没有任何主动性动作,T掉
2. Tcp窗口时间内,发送后长期不给确认,发送超时,T掉
3.长期发送错误数据包格式或伪造数据包格式,T掉
4.任何会引发 await Task.WhenAll (接收任务,发送任务) 这个异常的client,T掉
通过请教大神,发现是在虚拟PLC中,将PLC做服务器,在TCP连接的配置时,限定了伙伴端口为3000,而使用C#上位机连接虚拟PLC时,使用的随机的端口(非3000端口号),而网口助手能连上的原因是可以设置客户端的本地端口号,已验证:1、将PLC的TCP连接配置将伙伴端口的端口号取消限定,2、C#上位机的客户端Bind3000端口号,该两种方法已解决本问题。当然,在查找问题的途中,发现C#的TCP连接时虚假的成功,即TCP客户端连接指令未报错,但也未成功。原因是电脑的防火墙中值了某些进程。
怎么解决的?