让我来详细讲解C# Socket心跳超时检测的思路和实现方法。
什么是心跳超时检测?
在Socket编程中,心跳超时检测就是指客户端和服务端之间保持网络连接的一种机制。当客户端和服务端之间的网络连接闲置一段时间后,为了避免网络连接被认为已经中断,我们需要在一定时间间隔内发送心跳数据包来维持网络连接。如果在规定的时间内没有收到心跳数据包,就意味着网络连接已经中断,就需要重新建立连接。
实现思路
- 在服务器端,创建一个容器,存储所有心跳正常的客户端连接对象;
- 在客户端和服务端之间,定期交换心跳数据包,以维持网络连接;
- 在服务端,开启一个定时器,定期检测容器中的客户端连接是否已经超时,如果超时,就将这些连接从容器中剔除并关闭连接。
代码实现
在服务器端
public class Server
{
private List<Socket> _clients = new List<Socket>(); // 存储所有可用的客户端连接
private Timer _timer; // 定时器,用于定期检测心跳是否超时
public Server()
{
// 在构造函数中初始化定时器
_timer = new Timer(OnTimerTick, null, 1000, 1000);
}
private void OnTimerTick(object state)
{
lock (_clients)
{
// 检测每个客户端是否已经超时,如果超时,则关闭连接
foreach (var client in _clients)
{
UserData userData = client.Tag as UserData; // 获取客户端连接对象的自定义数据
if (userData != null && DateTime.Now.Subtract(userData.LastActiveTime).TotalSeconds > 30)
{
Console.WriteLine($"{client.RemoteEndPoint}已经超时,强制关闭连接");
client.Close();
}
}
// 从容器中删除已经关闭的连接
_clients.RemoveAll(c => !c.Connected);
}
}
private void OnClientConnected(IAsyncResult ar)
{
Socket client = _server.EndAccept(ar);
lock (_clients)
{
_clients.Add(client);
}
Console.WriteLine($"{client.RemoteEndPoint}已经连接");
StartReceive(client); // 开始接收客户端发送的数据
StartSend(client); // 开始向客户端发送数据
}
private void StartReceive(Socket client)
{
// 在Socket连接中设置自定义数据
UserData userData = new UserData();
userData.Buffer = new byte[1024];
userData.Socket = client;
userData.LastActiveTime = DateTime.Now;
client.Tag = userData;
try
{
client.BeginReceive(userData.Buffer, 0, userData.Buffer.Length, SocketFlags.None, OnDataReceived, client);
}
catch (Exception ex)
{
Console.WriteLine($"读取数据出错:{ex.Message}");
client.Close(); // 出现异常关闭连接
}
}
private void OnDataReceived(IAsyncResult ar)
{
UserData userData = ar.AsyncState as UserData;
if (userData == null) return;
try
{
int length = userData.Socket.EndReceive(ar);
// 如果接收到的数据长度为0表示连接已经断开
if (length <= 0)
{
Console.WriteLine($"{userData.Socket.RemoteEndPoint}已经断开连接");
userData.Socket.Close();
return;
}
userData.LastActiveTime = DateTime.Now; // 更新客户端连接的最近活动时间
string data = Encoding.UTF8.GetString(userData.Buffer, 0, length);
Console.WriteLine($"接收到客户端({userData.Socket.RemoteEndPoint})的数据:{data}");
// 处理接收到的数据...
// 继续从客户端接收数据
userData.Socket.BeginReceive(userData.Buffer, 0, userData.Buffer.Length, SocketFlags.None, OnDataReceived, userData);
}
catch (Exception ex)
{
Console.WriteLine($"读取数据出错:{ex.Message}");
userData.Socket.Close(); // 出现异常关闭连接
}
}
private void StartSend(Socket client)
{
// 给客户端发送心跳数据包
byte[] buffer = Encoding.UTF8.GetBytes("Heartbeat");
while (client.Connected)
{
if (client.Poll(0, SelectMode.SelectWrite))
{
try
{
client.Send(buffer);
}
catch (Exception ex)
{
Console.WriteLine($"发送数据出错:{ex.Message}");
client.Close(); // 出现异常关闭连接
return;
}
}
Thread.Sleep(3000); // 每隔3秒发送一次心跳数据包
}
}
public void Start()
{
// 在本机的8080端口监听客户端连接
_server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_server.Bind(new IPEndPoint(IPAddress.Any, 8080));
_server.Listen(10);
Console.WriteLine("服务器已经启动,等待客户端连接...");
// 开始接受客户端连接
_server.BeginAccept(OnClientConnected, null);
}
}
public class UserData
{
public Socket Socket { get; set; } // 当前连接的Socket对象
public byte[] Buffer { get; set; } // 保存从客户端接收到的数据
public DateTime LastActiveTime { get; set; } // 记录最近一次活动时间
}
在客户端
public class Client
{
private Socket _client; // 客户端Socket对象
private Timer _timer; // 定时器,用于定期发送心跳数据包
public Client()
{
_timer = new Timer(OnTimerTick, null, 1000, 1000);
}
private void OnTimerTick(object state)
{
try
{
// 给服务端发送心跳数据包
byte[] buffer = Encoding.UTF8.GetBytes("Heartbeat");
_client.Send(buffer);
}
catch (Exception ex)
{
Console.WriteLine($"发送心跳数据出错:{ex.Message}");
_client.Close(); // 出现异常关闭连接
}
}
public void Connect(string ip, int port)
{
// 连接到指定的服务器端口
_client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
_client.Connect(ip, port);
Console.WriteLine($"连接到服务端({ip}:{port})成功");
}
catch (Exception ex)
{
Console.WriteLine($"连接到服务端({ip}:{port})失败:{ex.Message}");
return;
}
StartReceive(); // 开始接受服务端发送的数据
StartSend(); // 开始向服务端发送数据
}
private void StartReceive()
{
// 创建自定义数据对象,用于保存和处理客户端接收到的数据
UserData userData = new UserData();
userData.Buffer = new byte[1024];
userData.Socket = _client;
try
{
// 开始接收服务端发送的数据
_client.BeginReceive(userData.Buffer, 0, userData.Buffer.Length, SocketFlags.None, OnDataReceived, userData);
}
catch (Exception ex)
{
Console.WriteLine($"读取数据出错:{ex.Message}");
_client.Close(); // 出现异常关闭连接
}
}
private void OnDataReceived(IAsyncResult ar)
{
UserData userData = ar.AsyncState as UserData;
if (userData == null) return;
try
{
int length = userData.Socket.EndReceive(ar);
// 如果数据的长度为0表示服务端已经断开连接
if (length <= 0)
{
Console.WriteLine($"服务端({userData.Socket.RemoteEndPoint})已经断开连接");
userData.Socket.Close();
return;
}
userData.LastActiveTime = DateTime.Now; // 更新客户端连接的最近活动时间
string data = Encoding.UTF8.GetString(userData.Buffer, 0, length);
Console.WriteLine($"接收到服务端数据:{data}");
// 处理接收到的数据...
// 继续从服务端接收数据
userData.Socket.BeginReceive(userData.Buffer, 0, userData.Buffer.Length, SocketFlags.None, OnDataReceived,
userData);
}
catch (Exception ex)
{
Console.WriteLine($"读取数据出错:{ex.Message}");
userData.Socket.Close(); // 出现异常关闭连接
}
}
private void StartSend()
{
// 向服务端发送数据
string data = "Hello World";
byte[] buffer = Encoding.UTF8.GetBytes(data);
while (_client.Connected)
{
if (_client.Poll(0, SelectMode.SelectWrite))
{
try
{
_client.Send(buffer);
}
catch (Exception ex)
{
Console.WriteLine($"发送数据出错:{ex.Message}");
_client.Close(); // 出现异常关闭连接
return;
}
}
Thread.Sleep(2000); // 每隔2秒向服务端发送一次数据
}
}
}
public class UserData
{
public Socket Socket { get; set; } // 当前连接的Socket对象
public byte[] Buffer { get; set; } // 保存从客户端接收到的数据
public DateTime LastActiveTime { get; set; } // 记录最近一次活动时间
}
在上述代码中,客户端和服务端都在定时器中定期发送心跳数据包来维持连接,而服务端在定时器中还要检测是否有连接超时的情况。对于客户端,可以在定时器中定期发送心跳数据包,如果发送数据出错,说明连接已经中断,就可以关闭客户端连接。对于服务端,在定时器中定期检测客户端连接的最近活动时间,如果超过指定的时间,就认为该客户端已经超时,就可以从服务器容器中移除该客户端并关闭连接,以释放服务器资源。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:c# socket心跳超时检测的思路(适用于超大量TCP连接情况下) - Python技术站