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

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

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

请给具体的代码如何写并给出注释,谢谢
...全文
2862 18 打赏 收藏 转发到动态 举报
写回复
用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关注下吧~~

110,534

社区成员

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

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

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