(C#)长连接是如何实现的,心跳包又是如何运作的?

西江 2012-02-18 08:51:31
长连接的原理及心跳包的流程我知道的,但是以前没做过长连接,不知道代码如何写。

我的需求是这样的:
1、手机客户端连接服务器,从服务器获取资源,客户端连上服务器后不断开连接(保持一个Socket),不通过IIS,不走HTTP协议,通过XML协议传输数据包。服务器如何与客户端建立长连接?
2、设置心跳包(建一个心跳包Socket),每隔5秒收服务器发一次数据,用于判断是否连接正常,连续5次接收心跳包失败时,服务器断开与客户端的长连接释放资源(包括传输数据Socket和心跳包Socket),如何建立心跳包(包括心跳包的mode字段),如何收发心跳包?当通过心跳包判断出与客户端失去连接时,如何结束进程释放资源?
3、如何及时清理服务器与客户端已经断开的线程?

请给具体的代码如何写并给出注释,谢谢
...全文
2927 18 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
18 条回复
切换为时间正序
请发表友善的回复…
发表回复
craigtao 2014-03-05
  • 打赏
  • 举报
回复
没看懂,,,,
西江 2012-02-28
  • 打赏
  • 举报
回复
非常感谢nonocast大哥~
nonocast 2012-02-23
  • 打赏
  • 举报
回复
给你一个server的代码

public abstract class Server {
static readonly ILog logger = LogManager.GetLogger(typeof(Server));

public int Port { get; set; }
public event ClientEventHandler OnClientAcceptEvent;
public event ClientEventHandler OnClientOnlineEvent;
public event ClientEventHandler OnClientRemoveEvent;
private bool bStarted;
static private int NextClientId = 0;
private TcpListener _listener;
protected Dictionary<int, Client> id2client = new Dictionary<int, Client>();
private List<Client> noHeartBeatClients = new List<Client>();

public bool HasHeartBeat;
private int CheckHeartBeatInterval = 30 * 1000;// ms
protected System.Timers.Timer CheckHeartBeatTimer = new System.Timers.Timer();
private object id2clientCS = new object();
private object noHeartBeatClientsCS = new object();

private Server() {
CheckHeartBeatTimer.Elapsed += (o1, a1) => {
if(HasHeartBeat == false) return;
List<Client> kickClientList = new List<Client>();
lock(id2clientCS) {
try {
DateTime now = DateTime.Now;
foreach(KeyValuePair<int, Client> pair in id2client) {
try {
Client client = pair.Value;
TimeSpan offset = now - client.LastHeartBeat;
if(client != null && client.State == Client.ConnectionState.Connected && offset.TotalMilliseconds > CheckHeartBeatInterval) {
kickClientList.Add(pair.Value);
logger.InfoFormat("检测到心跳超时: [IP]{0}, [ID]{1}, [Time]{2}", client.ClientIpAddress, pair.Key, DateTime.Now.ToString());
}
} catch { }
}
} catch(Exception ex) {
logger.WarnFormat("心跳检测时发生异常: \n{0}", ex);
}
}
kickClientList.ForEach(p => p.Close());
lock(noHeartBeatClientsCS) {
kickClientList.ForEach(c => noHeartBeatClients.RemoveAll(p => p.Id == c.Id));
}
};
}

public Server(int port)
: this() {
this.Port = port;
}

public List<Client> Clients {
get {
List<Client> result = new List<Client>();
lock(id2clientCS) {
foreach(Client each in id2client.Values) {
result.Add(each);
}
}
return result;
}
}

public virtual void Open() {
_listener = new TcpListener(Port);
logger.InfoFormat("Server#Open port={0}", Port);
try {
_listener.Start();
if(HasHeartBeat) {
CheckHeartBeatTimer.Stop();
CheckHeartBeatTimer.Interval = CheckHeartBeatInterval;
CheckHeartBeatTimer.Start();
}
_listener.BeginAcceptTcpClient(new AsyncCallback(OnAccept), null);
bStarted = true;
} catch(SocketException ex) {
logger.WarnFormat("服务器监听发生异常:{0}\nSocket ErrorCode: {1}\n提示:请检查端口是否已被占用", ex.Message, ex.ErrorCode);
throw ex;
} catch(Exception ex) {
logger.Warn(ex);
throw ex;
}
}

public virtual void Close() {
try {
if(HasHeartBeat) {
CheckHeartBeatTimer.Stop();
}
_listener.Stop();
bStarted = false;

lock(id2clientCS) {
foreach(Client each in id2client.Values) {
try { if(each != null)each.Close(); } catch { }
}
id2client.Clear();
}
} catch(Exception ex) {
logger.Warn(ex);
throw ex;
}
}

private void OnAccept(IAsyncResult ar) {
try {
Client client = CreateClient(NextClientId++, _listener.EndAcceptTcpClient(ar), this);
client.LastHeartBeat = DateTime.Now;
client.PostOfflineEvent += (obj, args) => RemoveClient(client);
lock(id2clientCS) {
id2client.Add(client.Id, client);
}
lock(noHeartBeatClientsCS) {
noHeartBeatClients.Add(client);
}
if(OnClientAcceptEvent != null) OnClientAcceptEvent(client);
} catch(ObjectDisposedException) { } catch(Exception ex) {
logger.Warn(ex);
} finally {
try {
_listener.BeginAcceptTcpClient(new AsyncCallback(OnAccept), null);
} catch(Exception) {
// ignore
}
}
}

protected abstract Client CreateClient(int id, TcpClient tcpClient, Server server);

public void RemoveClient(Client client) {
if(bStarted == false) return;
lock(id2clientCS) {
id2client.Remove(client.Id);
}
if(OnClientRemoveEvent != null) OnClientRemoveEvent(client);
}

public void GetHeartBeat(Client client) {
if(HasHeartBeat == false) return;

client.LastHeartBeat = DateTime.Now;
lock(noHeartBeatClientsCS) {
int index = noHeartBeatClients.FindIndex(p => p.Id == client.Id);
if(index == -1) return;
try {
if(OnClientOnlineEvent != null) OnClientOnlineEvent(client);
} catch(Exception ex) {
logger.Warn(ex);
}
noHeartBeatClients.RemoveAt(index);
}
}
}
nonocast 2012-02-23
  • 打赏
  • 举报
回复
[Quote=引用 13 楼 leesony 的回复:]

nonocast放心哦,结贴的时候肯定会给你分的,我先等等看有没有人给代码。。。
[/Quote]

不是分数问题,不缺分数的...关键是帮你写了,你说不是你不能用WCF,个么,我就昏过去
西江 2012-02-22
  • 打赏
  • 举报
回复
nonocast放心哦,结贴的时候肯定会给你分的,我先等等看有没有人给代码。。。
nonocast 2012-02-22
  • 打赏
  • 举报
回复
[Quote=引用 6 楼 leesony 的回复:]

引用 5 楼 nonocast 的回复:
那天跟你说了WebSocket看来你最终没有采用,
那么再给你建议,用WCF的TcpBinding写你现在的程序
否则tcp你还要做封包和拆包协议,粘包处理

楼上的大哥,请给代码。。。
[/Quote]

直接拉黑
西江 2012-02-22
  • 打赏
  • 举报
回复
[Quote=引用 10 楼 nonocast 的回复:]

引用 6 楼 leesony 的回复:

引用 5 楼 nonocast 的回复:
那天跟你说了WebSocket看来你最终没有采用,
那么再给你建议,用WCF的TcpBinding写你现在的程序
否则tcp你还要做封包和拆包协议,粘包处理

楼上的大哥,请给代码。。。


耍我?
[/Quote]
不是啊,你给的是WCF的TcpBinding的代码,对我的项目不试用啊。。。因为客户端用WCF的TcpBinding不现实。
nonocast 2012-02-20
  • 打赏
  • 举报
回复
[Quote=引用 6 楼 leesony 的回复:]

引用 5 楼 nonocast 的回复:
那天跟你说了WebSocket看来你最终没有采用,
那么再给你建议,用WCF的TcpBinding写你现在的程序
否则tcp你还要做封包和拆包协议,粘包处理

楼上的大哥,请给代码。。。
[/Quote]

耍我?
西江 2012-02-20
  • 打赏
  • 举报
回复
to:7楼:
WCF的TcpBinding对于我的项目用在客户端上不太现实,能否给一个socket的多线程?除了收发客户端的数据,还有心跳机制检查客户端时候连通。
足球中国 2012-02-20
  • 打赏
  • 举报
回复
心跳包是为了TCP长连接的。你只要隔一段时间一般为一分钟。通过你主通信线程发送心跳包。这个包尺寸大小可以自定义。当心跳几次服务器没有返回就断掉当前的连接。
nonocast 2012-02-20
  • 打赏
  • 举报
回复

public class Server {
public static Server Instance { get { return instance; } }

public IPEndPoint Address { get; private set; }

public void Open(IPEndPoint address) {
this.Address = address;
host = new ServiceHost(typeof(AppServiceImpl));
Config(host);
host.Opened += (s1, e1) => { Console.WriteLine("running..."); };
host.Open();
}

public void Close() {
if (host != null) {
host.Close();
}
}

public void OnRequest(string arg) {
string id = OperationContext.Current.SessionId;
lock (cs) {
if (!callbacks.ContainsKey(id)) {
AppServiceCallback callback = OperationContext.Current.GetCallbackChannel<AppServiceCallback>();
callbacks.Add(id, callback);
}
}
}

public void Request(string arg) {
List<string> sessions = new List<string>();
Dictionary<string, AppServiceCallback> tmp = new Dictionary<string, AppServiceCallback>(callbacks);

foreach (var each in tmp) {
try {
each.Value.OnRequest(arg);
} catch {
sessions.Add(each.Key);
}
}

lock(cs) {
sessions.ForEach(s => callbacks.Remove(s));
}
}

private void Config(ServiceHost host) {
var binding = new NetTcpBinding();
binding.Security.Mode = SecurityMode.None;
binding.ReceiveTimeout = TimeSpan.MaxValue;
binding.MaxReceivedMessageSize = 1024 * 1024;
binding.ReaderQuotas.MaxStringContentLength = 1024 * 1024;
string url = string.Format(uriTemplate, Address.Address.ToString(), Address.Port);
host.AddServiceEndpoint(typeof(AppService), binding, url);
}

public void Register() {
string id = OperationContext.Current.SessionId;
AppServiceCallback callback = OperationContext.Current.GetCallbackChannel<AppServiceCallback>();
lock (cs) {
callbacks.Add(id, callback);
}
}

private ServiceHost host;
private const string uriTemplate = @"net.tcp://{0}:{1}/App/";
private Dictionary<string, AppServiceCallback> callbacks = new Dictionary<string, AppServiceCallback>();
private object cs = new object();

private static Server instance = new Server();
}

[ServiceContract(CallbackContract = typeof(AppServiceCallback))]
public interface AppService {
[OperationContract]
string Request(string arg);
}

[ServiceContract]
public interface AppServiceCallback {
[OperationContract]
string OnRequest(string arg);
}

public class AppServiceImpl : AppService {
public string Request(string arg) {
Console.WriteLine(arg);
Server.Instance.OnRequest(arg);
return arg;
}
}
nonocast 2012-02-19
  • 打赏
  • 举报
回复
那天跟你说了WebSocket看来你最终没有采用,
那么再给你建议,用WCF的TcpBinding写你现在的程序
否则tcp你还要做封包和拆包协议,粘包处理
西江 2012-02-19
  • 打赏
  • 举报
回复
[Quote=引用 5 楼 nonocast 的回复:]
那天跟你说了WebSocket看来你最终没有采用,
那么再给你建议,用WCF的TcpBinding写你现在的程序
否则tcp你还要做封包和拆包协议,粘包处理
[/Quote]
楼上的大哥,请给代码。。。
西江 2012-02-18
  • 打赏
  • 举报
回复
求代码
dean615 2012-02-18
  • 打赏
  • 举报
回复
长链接差不多就是这个吧
         
Socket socket = m_listener;
uint dummy = 0;
inOptionValues = new byte[Marshal.SizeOf(dummy) * 3];
BitConverter.GetBytes((uint)1).CopyTo(inOptionValues, 0);//是否启用Keep-Alive
BitConverter.GetBytes(ConfigValue.keepalive).CopyTo(inOptionValues, Marshal.SizeOf(dummy));//多长时间开始第一次探测
BitConverter.GetBytes(ConfigValue.keepalive).CopyTo(inOptionValues, Marshal.SizeOf(dummy) * 2);//探测时间间隔
socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null);

用抓包工具就能看到心跳的过程的。
前2个差不多就是这么写的,第三个我也MARK关注下吧~~
资源下载链接为: https://pan.quark.cn/s/9648a1f24758 在 IT 领域,心跳包是维持网络连接稳定并检测其是否中断的重要通信方式,尤其在 C# 开发中,基于 .NET 框架实现心跳包功能十分常见。本项目给出了服务端和客户端心跳包的完整实现,目的是保障长连接的稳定性和可靠性。在 C#实现心跳包涉及以下核心要点: 网络通信基础:心跳包的传输基于网络编程,主要依托 TCP/IP 协议栈。TCP 提供可靠的数据传输服务,心跳包的作用是防止 TCP 连接因长时间无数据传输而被误判为断开。 Socket 编程:C# 的 System.Net.Sockets 命名空间中的 Socket 类可用于创建服务端和客户端套接字。服务端需监听特定端口以接收客户端连接请求,客户端则需连接到服务器并发送心跳包。 心跳间隔与超时:心跳包的发送间隔应合理设置,如几秒或几十秒一次,既能避免频繁发送增加网络负担,又能及时发现连接异常。同时,服务端和客户端都需设定超时时间,若在该时间内未收到对方心跳包,则认为连接丢失。 异步编程:借助 .NET 框架的异步编程模型(如 async/await),可高效处理网络 I/O 操作,提升程序响应性和性能。服务端需异步监听新连接,客户端需异步发送和接收心跳包。 状态管理:服务端需维护一个客户端连接列表,记录每个连接的状态,如最后收到心跳包的时间;客户端也需保存服务器连接状态,以便在心跳包超时后重新建立连接。 数据序列化与反序列化:心跳包数据(如“ping”或“pong”)在发送前需进行序列化(如 JSON 或 protobuf),接收后需反序列化,以确保数据正确传输和解析。 异常处理:实现心跳包时,要充分考虑网络异常情况,如连接中断、数据丢失等,并设计合理的错误处理和恢复机制。 多线程/并发:在高并发场景下,服务端需处理多个客户端的心跳

111,111

社区成员

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

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

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