因为TCP是面向流通信,则会出现几个网络问题。比如断线重连,丢包粘包等情况。
断线重连:
设备会有自己的心跳检查,一般是通过心跳检查去发现是否已断线。这里不多叙述,简单的写一个自己实现的断线检查和重新连接。
private static void Connect()
{
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Connect(new IPEndPoint(IPAddress.Parse("192.168.2.1"), 5001));
// socket.Connected保留最后一次操作时的状态,不是实时的状态
Console.WriteLine(socket.Connected);
//此处最好是通过开线程去执行。这样就能随时检测是否断线
while (true)
{
返回True :连接正常 Poll:检查连接状态
bool state = !(!socket.Connected || (socket.Poll(1, SelectMode.SelectRead) && socket.Poll(1, SelectMode.SelectWrite) && (socket.Available == 0)));
if (state)
{
Console.WriteLine("连接正常");
socket.Shutdown(SocketShutdown.Both);
socket.Dispose();
}
else
{
Console.WriteLine("连接断开");
socket = new Socket(
AddressFamily.InterNetwork,
SocketType.Stream,
ProtocolType.Tcp);
#region 方法一: 同步连接
try
{
socket.Connect(new IPEndPoint(IPAddress.Parse("192.168.2.1"), port: 5001));
}
catch (Exception ex)
{
Console.WriteLine("重新连接失败");
Console.WriteLine(ex.Message);
}
#endregion
#region 方法二:异步执行
var result = socket.BeginConnect(new IPEndPoint(IPAddress.Parse("192.168.2.1"), 5001), new AsyncCallback(r =>
{
Console.WriteLine(r.AsyncState);
if (!r.IsCompleted)
{
//这里不是关闭连接,而是关闭当前委托,和BeginConnect对应。
socket.EndConnect(r);
}
Console.WriteLine("回调执行");
}), null);
bool s = result.AsyncWaitHandle.WaitOne(1000);
if (s)// 连接成功后
{
Console.WriteLine("重新连接");
}
else
{
// 连接的时候 异步,在1秒钟时间内 没有连接成功 跳出
try
{
//socket.EndConnect(result); // 销毁之前的连接请求 卡线程
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
Console.WriteLine("重连失败");
}
#endregion
}
//重连 使用 发送业务请求 执行连接判断
}
}
丢包情况一般是网络通讯不稳定,或者数据缓冲区读取的字节数量不对造成的。前一种情况可通过心跳检查,断线重连等方案解决。后一种情况可将未读的字节补充上来,这样包依然完整。
粘包情况:设备发送数据太快,导致上一个包的缓冲区还未清空,下一个包的数据就进入了缓冲区,读取缓冲区时把数据包粘连在一起了。这是TCP特有的问题,如下:
Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
server.Bind(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9009));
server.Listen();
Task.Run(() =>
{
Socket c = server.Accept();
while (true)
{
byte[] data = new byte[1024];
c.Receive(data);
Console.WriteLine(Encoding.UTF8.GetString(data));
}
});
Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
client.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9009));
for (int i = 0; i < 10; i++)
{
// 发送端
string msg = "Hello";
byte[] data = Encoding.UTF8.GetBytes(msg);
client.Send(data);
}
Console.WriteLine("发送完成");
那怎么解决呢?
方法也很简单,和服务端定义一个协议,比如发送时将包的长度,名称,id等一起传送过来,这样接收时,按照协议去解析。
Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
server.Bind(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9009));
server.Listen();
Task.Run(() =>
{
Socket c = server.Accept();
while (true)
{
byte[] data = new byte[2];
c.Receive(data);// 接收数据字节长度
short len = BitConverter.ToInt16(data, 0);
//根据字节长度去获取正文内容
data = new byte[len];
c.Receive(data);// 接收实际数据
Console.WriteLine(Encoding.UTF8.GetString(data));
}
});
Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
client.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9009));
for (int i = 0; i < 10; i++)
{
// 发送端
string msg = "Hello";
byte[] data = Encoding.UTF8.GetBytes(msg);
List<byte> bytes = new List<byte>();
short len = (short)data.Length;// 正文字节长度
bytes.AddRange(BitConverter.GetBytes(len));// 数据字节长度
bytes.AddRange(data);// 数据有效字节
client.Send(bytes.ToArray());
}
Console.WriteLine("发送完成");