TCP服务突然断线,客户端如何检测到并重连

巴士上的邂逅 2019-11-12 05:05:39
准备做一个串口转TCP的小工具,为了替代串口服务器



在SerialPort.DataReceived事件里直接tcpClient.GetStream().Write。现在已经做到:如果server没有启动,客户端一直尝试连接服务器,直到连通。
现在的问题是如果客户端已经和服务端正常连接,突然服务端关机、掉线,客户端却不知道,下边的代码,在服务端掉线后第一次,能正常执行,第二次才能抛异常,这样已经掉了一个包。
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
var buffer = new byte[SerialPort.BytesToRead];
SerialPort.Read(buffer, 0, buffer.Length);
DataReceived?.Invoke(this, new ClientDataReceivedEventArgs(this, buffer));
if (tcpClient.Connected && NetworkStream != null)
{
NetworkStream.Write(buffer, 0, buffer.Length);
NetworkStream.Flush();
}
}

如何才能监听tcp客户端与服务端的连接的正确性。
...全文
2969 43 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
43 条回复
切换为时间正序
请发表友善的回复…
发表回复
巴士上的邂逅 2019-12-05
  • 打赏
  • 举报
回复
引用 48 楼 jy251 的回复:
usb,rs232互转的东西淘宝上一大把。。。
设备的usb插到电脑上需要安装驱动,你确定有将USB转rs232的转接线?
BruCe1107 2019-12-03
  • 打赏
  • 举报
回复
ondisconnected 和 心跳
jy251 2019-12-03
  • 打赏
  • 举报
回复
usb,rs232互转的东西淘宝上一大把。。。
AQing阿清 2019-11-22
  • 打赏
  • 举报
回复
tcp挂掉不好捕捉的。说的乱七八糟的,看不懂。tcp挂掉确实不好检测,你说的情况我遇到过。我都是主动关断tcp,让客户端自动发起重连
巴士上的邂逅 2019-11-18
  • 打赏
  • 举报
回复
谢谢各位的解答,虽然届大众没有完全合适的答案,但从中得到了很多启发,问题解决了
之前可能又一些细节没描述:1、由于客户端的端口号是固定的,用IOControl来设置KeepAlive是不行的(通过在下载区下载的TCPReconnetDemo稍微改动了一下验证的);
2、由于本小程序需要透传数据不能增加或者减少数据,所以增加心跳数据是不可以的;
3、由于服务端是监听固定端口号,并且本小程序是多客户端集成的,于是我单独加了一个心跳客户端(端口号是系统分配的)来监听服务端状态。
luj_1768 2019-11-18
  • 打赏
  • 举报
回复
usb口是串口的正对升级版!还是问一问硬件驱动,应该不复杂。你的做法牺牲了很多性能和可靠性,还增加了资源消耗。
OrdinaryCoder 2019-11-15
  • 打赏
  • 举报
回复
通讯通讯 一定是双方的 即使这个工具作为透传 那么 他也是连接双方的工具 这样的话 完全可以用心跳的方式判断通断(我做过一个透传服务器 里面判断客户端断线是判断多久没有收到数据包 超时就踢掉这个客户端) 串口什么的并不需要关注 只不过是换了一个收发数据的方式 还有,我之前遇到一个问题 比如客户端是一个运动的实体(机器人) 当他运动到网络外面(局域网WIFI连接) 我写的TCP程序并没有断连反馈
ycmk2000 2019-11-15
  • 打赏
  • 举报
回复
学习学习了。
  • 打赏
  • 举报
回复
引用 4 楼 巴士上的邂逅的回复:
引用 3 楼 良朋 的回复:
串口转TCP,哈,只有你想不到的,没有你做不到的!
原来的架构是依附于串口服务器开发的,突然遇到一个设备没有串口,是USB的!串口服务器没有USB接口,此USB设备可以直接接到电脑上虚拟出一个串口来,所以我想着原来的流程不动(改架构台麻烦了)通过写个小程序替代串口服务器。
这个你都不用自己搞,网上有现成的程序可用!
SZ深呼吸 2019-11-14
  • 打赏
  • 举报
回复
引用 30 楼 by_封爱 的回复:
[quote=引用 29 楼 baiydn 的回复:] [quote=引用 7 楼 wanghui0380 的回复:] 我很好奇,不管你们用where(true) 也好,还是异步也好,还是所谓的高级xxx园技术Iocp,Nio,还是用的啥XX园的天下第一的supersockect,HighSocket,还是netty,为啥你们你们不是在断线的第一时间收到异常,收到断线事件,收到“已关闭的xxx” 还有人信誓旦旦的说,tcp断线不是第一时间收到断线消息,根据TCP协议,他的2个小时后才能收到消息,那我就纳闷了,这是那些傻叉们设计的tcp协议啊,居然还用了60多年
确实有些情况,第一时间是收不到对方关闭消息的。在局域网中测试很难出现这种情况,经常会出现在比较复杂的网络环境中,网络经过了很多次路由之后,我在做移动或NB-Iot项目的时候,这种现象经常会有,设备端断电强制关机或直接拔网线,服务端有可能没有任何异常,检查tcp连接还在,但实际上已经断开了。 针对这种情况,服务器端需要用心跳或检测读写超时的方式把这种无效的连接定时清理掉,否则2小时后才会收到异常,这时应用程序才会认为连接已经断开。[/quote] 所以说你年轻啊.拔网线了 服务端不能马上收到断开的事件 这件事你不去找原因? 最终就采用定时清理这种解决方案? 你能想到的 人家肯定也能想到.. 既然想到 肯定不能用这种方式去解决问题.. 针对你2小时后收到信息 我是这样做的. 在客户端连接的时候 设置SocketAsyncEventArgs对象.

ar.AcceptSocket.IOControl(IOControlCode.KeepAliveValues, KeepAlive(1, 1000, 1000), null);
其中KeepAlive方法如下.

public static byte[] KeepAlive(int onOff, int keepAliveTime, int keepAliveInterval)
        {
            byte[] buffer = new byte[12];
            BitConverter.GetBytes(onOff).CopyTo(buffer, 0);
            BitConverter.GetBytes(keepAliveTime).CopyTo(buffer, 4);
            BitConverter.GetBytes(keepAliveInterval).CopyTo(buffer, 8);
            return buffer;
        }
我以前也是遇到你所谓的 拔网线不能实时收到消息.. 自从用了这个 完美解决.. [/quote] 我那个回复只是想表达,确实存在2小时后才收到连接断开事件或异常的情况,并不是某些人所谓的什么“信誓旦旦、傻叉...”。 你说的解决办法,可能是重新设置了keepAlive的属性,你是windows平台的。我的项目是linux平台的,java开发的,可能并不支持你所说的解决办法,解决办法有很多种,我只是说了一种简单有效通用的办法。
SZ深呼吸 2019-11-14
  • 打赏
  • 举报
回复
引用 12 楼 by_封爱 的回复:
引用 6 楼 巴士上的邂逅 的回复:
[quote=引用 5 楼 by_封爱 的回复:]
谢谢你的回答,我做的小工具是因为设备只有usb,串口服务器没有USB口。我的串口转tcp是属于Tcp客户端,这个是毋庸置疑的,我之前好像找过相关软件,但是tcp客户端的端口号是随机(应该是递增的)不是固定的
你品 你仔细品.. 你的"串口转TCP服务" 怎么可能是"客户端"? 首先看名字. 你就知道 他应该是一个"服务". 另外,你这个东西 肯定是运行在有串口那台电脑上 一直运行. 然后其他电脑通过ip.port去连接这个机器 进行tcp数据通讯 然后你的tcp服务会把串口的数据 转发给客户端 并且把客户端的数据write到串口 没错吧? 所以 你在品品 是客户端吗? 好 如果你不认同..我们在换一个方式去解释 去说明. 你这个串口的电脑 部署了你这个exe之后 会公开一个IP 让别人去通过tcp连接 读取串口数据 对吧 那么你告诉我 是谁连谁? 难道不是其他 想读取串口数据的机器 去连接这台部署了串口转tcp这台服务器吗? [/quote] 你看清楚楼主的图,是设备端通过转换连接服务器,设备通过串口连接转换设备,转换设备从串口接收到数据后,再通过TCP连接服务器,服务器端才是服务端,转换器是客户端。
by_封爱 版主 2019-11-14
  • 打赏
  • 举报
回复
针对这个问题 熊猫兄弟说了已经非常明确了.. 没有讨论的必要了. 当然不排除很多人带着异样的眼光.. 包括服务端还是客户端 解决方案 思路已经非常明确...LZ还是不信的话 那讨论下去 也是没意义的
by_封爱 版主 2019-11-14
  • 打赏
  • 举报
回复
引用 29 楼 baiydn 的回复:
[quote=引用 7 楼 wanghui0380 的回复:] 我很好奇,不管你们用where(true) 也好,还是异步也好,还是所谓的高级xxx园技术Iocp,Nio,还是用的啥XX园的天下第一的supersockect,HighSocket,还是netty,为啥你们你们不是在断线的第一时间收到异常,收到断线事件,收到“已关闭的xxx” 还有人信誓旦旦的说,tcp断线不是第一时间收到断线消息,根据TCP协议,他的2个小时后才能收到消息,那我就纳闷了,这是那些傻叉们设计的tcp协议啊,居然还用了60多年
确实有些情况,第一时间是收不到对方关闭消息的。在局域网中测试很难出现这种情况,经常会出现在比较复杂的网络环境中,网络经过了很多次路由之后,我在做移动或NB-Iot项目的时候,这种现象经常会有,设备端断电强制关机或直接拔网线,服务端有可能没有任何异常,检查tcp连接还在,但实际上已经断开了。 针对这种情况,服务器端需要用心跳或检测读写超时的方式把这种无效的连接定时清理掉,否则2小时后才会收到异常,这时应用程序才会认为连接已经断开。[/quote] 所以说你年轻啊.拔网线了 服务端不能马上收到断开的事件 这件事你不去找原因? 最终就采用定时清理这种解决方案? 你能想到的 人家肯定也能想到.. 既然想到 肯定不能用这种方式去解决问题.. 针对你2小时后收到信息 我是这样做的. 在客户端连接的时候 设置SocketAsyncEventArgs对象.

ar.AcceptSocket.IOControl(IOControlCode.KeepAliveValues, KeepAlive(1, 1000, 1000), null);
其中KeepAlive方法如下.

public static byte[] KeepAlive(int onOff, int keepAliveTime, int keepAliveInterval)
        {
            byte[] buffer = new byte[12];
            BitConverter.GetBytes(onOff).CopyTo(buffer, 0);
            BitConverter.GetBytes(keepAliveTime).CopyTo(buffer, 4);
            BitConverter.GetBytes(keepAliveInterval).CopyTo(buffer, 8);
            return buffer;
        }
我以前也是遇到你所谓的 拔网线不能实时收到消息.. 自从用了这个 完美解决..
SZ深呼吸 2019-11-14
  • 打赏
  • 举报
回复
引用 7 楼 wanghui0380 的回复:
我很好奇,不管你们用where(true) 也好,还是异步也好,还是所谓的高级xxx园技术Iocp,Nio,还是用的啥XX园的天下第一的supersockect,HighSocket,还是netty,为啥你们你们不是在断线的第一时间收到异常,收到断线事件,收到“已关闭的xxx” 还有人信誓旦旦的说,tcp断线不是第一时间收到断线消息,根据TCP协议,他的2个小时后才能收到消息,那我就纳闷了,这是那些傻叉们设计的tcp协议啊,居然还用了60多年
确实有些情况,第一时间是收不到对方关闭消息的。在局域网中测试很难出现这种情况,经常会出现在比较复杂的网络环境中,网络经过了很多次路由之后,我在做移动或NB-Iot项目的时候,这种现象经常会有,设备端断电强制关机或直接拔网线,服务端有可能没有任何异常,检查tcp连接还在,但实际上已经断开了。 针对这种情况,服务器端需要用心跳或检测读写超时的方式把这种无效的连接定时清理掉,否则2小时后才会收到异常,这时应用程序才会认为连接已经断开。
wayafa 2019-11-14
  • 打赏
  • 举报
回复
学习一种编程语言,首先要找一款合用的集成开发工具,似乎是自然而然的想法。
SZ深呼吸 2019-11-14
  • 打赏
  • 举报
回复
它这里的tcpclient就是这个串口转tcp的设备USR-504,硬件实现的转换,图中的tcpserver就是服务器,没有其他电脑了
by_封爱 版主 2019-11-14
  • 打赏
  • 举报
回复
引用 32 楼 baiydn 的回复:
你看清楚楼主的图,是设备端通过转换连接服务器,设备通过串口连接转换设备,转换设备从串口接收到数据后,再通过TCP连接服务器,服务器端才是服务端,转换器是客户端。
我觉得你可能走进了跟楼主一个误区. 按照LZ的思想.. A电脑连接了串口服务器. B电脑想要这个串口数据. 那么LZ的意思 是写一个exe在A电脑上.读取串口数据 作为tcpclient发送给B电脑 B电脑部署一个tcpserver. 按照这个意思 那么a的确是b的客户端.. 但是这正常套路吗? 如果是我的话 我会问. 为什么a.exe不直接通过http把组合好的串口数据发给b? 这样不是更简单吗 还有.如果有2台电脑同时想要A的数据 你怎么办? a.exe启动的时候 去连接2次服务器?显然不行. 我如果在运行的时候 要加一台 你怎么办呢?也不行..难道重启? 如果这几台想要数据的机器的IP每次不一样? 你启动这个服务的时候都要配置一下IP? 所以这套让exe作为客户端的办法显然有很多问题. 针对上面的问题 如果你把a.exe做成tcpserver 就完全可以解决.他只监听串口服务器所在的那个电脑 而且IP一般来说不会变 这个作为服务器. 那么 所有想要数据的"机器" 使用tcpclient去连.去收数据. 这个时候 在服务端写代码也非常简单.. 伪代码如下

static list<tcpclient> clients
oncon(tcpclient client)
{
  clients.add(client);
}
onclose(tcpclient client);
{
 client.remove(client);
}
DataReceived(obj,arg)
{
  var data=xxx.read();
  clients.foreach(item=>{
      item.send(data);
  });
}
其实LZ的问题 本来就很简单..是你们想的太复杂了. 那么回到问题上. 收数据的客户端 拔网线了 咋办? 那是客户端的事 在服务端会执行onclose事件. 自动移除.下次不在发送. 至于客户端.拔掉网线的瞬间你会捕获一个异常,至于你重新连接 无非就是一个tcpclient的一个函数而已. 高级点 就整个定时器 所以有的时候 我们在纠结一个问题的时候.并不是因为问题有多难,可能是走进了一个错误的方向. 用错误来验证错误.得到的就是无尽的错误 无尽的讨论....
github_36000833 2019-11-13
  • 打赏
  • 举报
回复
当然谁都不用认同任何结论,最有用的是掌握独立思考和验证的方法。
开发者可以用常用的工具,WireShark(用来探嗅网络数据),来自己做试验和验证。

至于异常抛出的时机。要这样解释,异常是基于调用栈的,简单的说异常只能在调用方法的时候才抛出。该抛出时机可以是
socket.Receive
socket.Send
socket.Poll
socket.Shutdown
或其他变种,以及包裹调用等比如
NetworkStream.Read
NetworkStream.Write

如果TCP协议栈发现经过各种重发后,超时期后还是没有受到TCP确认帧,TCP协议栈就可以将该连接状态设为断开。
在0楼代码第二次调用NetworkStream.Write的时候,TCP协议栈根据连接状态就可以抛出异常。

要试验Windows默认2小时的KeepAlive,可以
1、检查HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\KeepAliveTime是否被更改过。
2、建立一个到远程服务器的连接后,进入接收,然后拔远程服务器的网线(远程服务器可以用一个虚拟机,断开虚拟机的网卡即可)
3、观察什么时候接收抛出异常。

试验代码很简单的:
static void Main(string[] args)
{
using (var tcpClient = new TcpClient("服务器地址写这里", 12345))
using (var networkStream = tcpClient.GetStream())
{
try
{
// 在这个时候,拔掉远程服务器的网线
networkStream.ReadByte();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
static void 服务器代码()
{
var tcpListener = new TcpListener(IPAddress.Any, 12345);
tcpListener.Start();
while(true)
{
var socket = tcpListener.AcceptSocket();
Console.WriteLine("客户连接:" + socket.RemoteEndPoint);
Task.Run(() => socket.Receive(new byte[16]));
}
}

wanghui0380 2019-11-13
  • 打赏
  • 举报
回复
无论是server还是client,其实都是第一时间得到断线异常得 比如XX园们最得意的高级技术IOCP
 private void ProcessAccept(SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                Socket s = e.AcceptSocket;//和客户端关联的socket
                if (s.Connected)
                {
                    try
                    {

                        Interlocked.Increment(ref _clientCount);//原子操作加1
                        SocketAsyncEventArgs asyniar = _objectPool.Pop();
                        asyniar.UserToken = s;

                        Log4Debug(String.Format("客户 {0} 连入, 共有 {1} 个连接。", s.RemoteEndPoint.ToString(), _clientCount));

                        if (!s.ReceiveAsync(asyniar))//投递接收请求
                        {
                            ProcessReceive(asyniar);
                        }
                    }
                    catch (SocketException ex)
                    {
                        Log4Debug(String.Format("接收客户 {0} 数据出错, 异常信息: {1} 。", s.RemoteEndPoint, ex.ToString()));
                        //TODO 异常处理
                    }
                    //投递下一个接受请求
                    StartAccept(e);
                }
            }
        }

        #endregion

        #region 发送数据

        /// <summary>
        /// 异步的发送数据
        /// </summary>
        /// <param name="e"></param>
        /// <param name="data"></param>
        public void Send(SocketAsyncEventArgs e, byte[] data)
        {
            if (e.SocketError == SocketError.Success)
            {
                Socket s = e.AcceptSocket;//和客户端关联的socket
                if (s.Connected)
                {
                    Array.Copy(data, 0, e.Buffer, 0, data.Length);//设置发送数据

                    //e.SetBuffer(data, 0, data.Length); //设置发送数据
                    if (!s.SendAsync(e))//投递发送请求,这个函数有可能同步发送出去,这时返回false,并且不会引发SocketAsyncEventArgs.Completed事件
                    {
                        // 同步发送时处理发送完成事件
                        ProcessSend(e);
                    }
                    else
                    {
                        CloseClientSocket(e);
                    }
                }
            }
        }
看见没有,但凡不是SocketError.Success一律立刻关闭归还入池(就是reset都不认,因为野数据会让数据包解析混乱,还不如请强制重联呢),强制client重新连接。如果这玩意想上面那位老兄说的,windows2小时以后才知道是不是断线了,额,我表示就是阿里都抗不住,因为我不知道你断线了,我就server就保留你,然后你发不出,重试。我就是给个long.MaxValue的池都不够用的,因为你们2小时1w连接都不知道重试多少次了。 对于tcpclient其实也是一样,send有异常,read异常,BeginRead,全都会有异常。
巴士上的邂逅 2019-11-13
  • 打赏
  • 举报
回复
引用 9 楼 qq_29741071 的回复:
楼主,为什么不考虑RJ45转232或者485,然后在232或者485转串口呢?这样就可以不用考虑使用PC机,只是增加了两根线而已
设备上是USB口,只能接到电脑上,用线转接不行啊
加载更多回复(23)

111,089

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • AIGC Browser
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧