心跳包和通讯的问题

西江 2012-09-22 11:20:19
以下是服务器端的,使用异步回调,关于Socket连接、接收数据和发送数据的一个简单demo,代码如下:


class test
{
public static ManualResetEvent ClientConn = new ManualResetEvent(false);
public static int Online = 0;

static void Main(string[] args)
{
IPAddress IP = Dns.GetHostAddresses(Dns.GetHostName())[0];
int Port = 21062;
TcpListener listener = new TcpListener(IP, Port);
Console.WriteLine("the server ip is {0},and port is {1}", IP, Port);
try
{
listener.Start();
while (true)
{
ClientConn.Reset();
listener.BeginAcceptSocket(AsyncAcceptSocketCallback, listener);
ClientConn.WaitOne();
}
}
catch (Exception)
{
Console.WriteLine("服务启动失败");
ClientConn.Set();
}
}
static void AsyncAcceptSocketCallback(IAsyncResult ar)
{
StateObject StateObject = new StateObject();
try
{
ClientConn.Set();
TcpListener listener = (TcpListener)ar.AsyncState;
Socket Socket = listener.EndAcceptSocket(ar);
IPEndPoint ip = (IPEndPoint)Socket.RemoteEndPoint;
string UserIP = ip.Address.ToString().TrimEnd() + ":" + ip.Port.ToString().TrimEnd();
StateObject.Socket = Socket;
StateObject.UserIP = UserIP;
Online = Online + 1;
Console.WriteLine("[连]来自{0}在{1}连接,当前在线人数:{2}", StateObject.UserIP, DateTime.Now.ToString("HH:mm:ms"), Online);
StateObject.Socket.BeginReceive(
StateObject.Buffer, 0, StateObject.BufferSize, SocketFlags.None, AsyncReceiveCallback, StateObject);
}
catch
{
Exception(StateObject);
}
}

static void AsyncReceiveCallback(IAsyncResult ar)
{
StateObject StateObject = ar.AsyncState as StateObject;
try
{
StateObject.ReceiveSize = StateObject.Socket.EndReceive(ar);
Console.WriteLine("[收]收到来自{0}发送的数据,时间{1},正在验证中...", StateObject.UserIP, DateTime.Now.ToString("HH:mm:ms"));
if (StateObject.ReceiveSize > 0)
{
ThreadPool.QueueUserWorkItem(UnPackAndSendBuffer, StateObject);
StateObject.Socket.BeginReceive(StateObject.Buffer, 0, StateObject.BufferSize, SocketFlags.None, AsyncReceiveCallback, StateObject);
}
else
{
Exception(StateObject);
}
}
catch //(SocketException se)
{
Exception(StateObject);
}
}

static void UnPackAndSendBuffer(object o)
{
Thread.CurrentThread.Priority = ThreadPriority.Highest;
byte[] Result = new byte[] { };
StateObject StateObject = o as StateObject;
UnPack UnPack = new UnPack();
try
{
//这里是解析数据包,并组合一个byte[],发送Result给客户端
Result = UnPack.UnPackAndGetData(StateObject.Buffer, StateObject.ReceiveSize, StateObject.UserIP);
if (Result.Length > 0)
{
StateObject.Socket.BeginSend(Result, 0, Result.Length, SocketFlags.None, AsyncSendCallback, StateObject);
Console.WriteLine("[发]已发送数据到{0},时间{1}\n", StateObject.UserIP, DateTime.Now.ToString("HH:mm:ms"));
}
else
{
Console.WriteLine("[错]解包失败01,来自{0},时间{1}\n", StateObject.UserIP, DateTime.Now.ToString("HH:mm:ms"));
Exception(StateObject);
}
}
catch //(Exception e)
{
Console.WriteLine("[错]解包失败,来自{0},时间{1}", StateObject.UserIP, DateTime.Now.ToString("HH:mm:ms"));
Exception(StateObject);
}
}

static void AsyncSendCallback(IAsyncResult ar)
{
StateObject StateObject = ar.AsyncState as StateObject;
try
{
StateObject.Socket.EndSend(ar);
}
catch //(SocketException se)
{
Exception(StateObject);
}
}

public class StateObject
{
public Socket Socket = null;
public const int BufferSize = 128;
public byte[] Buffer = new byte[BufferSize];
public int ReceiveSize = 0;
public string UserIP = string.Empty;
}

static void Exception(StateObject o)
{
if (o.Socket != null)
{
o.Socket.Close();
o.Socket = null;
Online = Online - 1;
}
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("[断]来自{0}已断开,时间{1},当前在线人数:{2}\n", o.UserIP, DateTime.Now.ToString("HH:mm:ms"), Online);
Console.ForegroundColor = ConsoleColor.Green;
}
}


对于这个demo,有一个问题就是Socket连接会越连越多,因为没有心跳机制,当客户端意外断开服务器收不到断开请求时,Socket会一直连接并不会断开,浪费了资源。
1、请帮忙加上心跳机制,或者给出其他更好的方案
2、请贴出详细代码
谢谢。
...全文
914 29 打赏 收藏 转发到动态 举报
写回复
用AI写文章
29 条回复
切换为时间正序
请发表友善的回复…
发表回复
tzz8080 2013-07-25
  • 打赏
  • 举报
回复
心跳包原理其实很简单,就是客户端周期向发服务器发送一个字节,比如30秒,表示客户端还在活动。这样服务端就知道这个客户端还在线。同时心跳包还可以保证中间网关不会关掉端口。 服务端在定时向客户端队列检测心跳累计数,服务端有一个总的检测轮数,当总轮数与客户端的累计数相差不大的时候,表示客户端还在线。
暖暖的太阳 2013-02-26
  • 打赏
  • 举报
回复
看来还是没人会啊,更没有人有更好的方法,悲剧
西江 2012-10-08
  • 打赏
  • 举报
回复
自己顶起来。。。。
yang2948443 2012-10-08
  • 打赏
  • 举报
回复
还没有解决吗?坐等接分!
西江 2012-09-29
  • 打赏
  • 举报
回复
[Quote=引用 23 楼 的回复:]

做一个计时管理,只要客户端有数据发过来(不管任何类型数据),都去重置计时。
当计时值超过指定值,服务端就发送状态询问包给客户端,
如果客户端没有反馈,就表示已经断开了,为了保险,可以连续发三次。

这是最简单的方法,而且可以保证通信量尽可能少,避免占用过多带宽和cpu处理
[/Quote]
这个方法可行性比较强,能否给一些示例代码。。。
fenlin1985 2012-09-28
  • 打赏
  • 举报
回复
//设置KeepAlive,TCP15秒空闲就开始发送KeepAlive包
uint dummy = 0;
byte[] inOptionValues = new byte[Marshal.SizeOf(dummy) * 3];
BitConverter.GetBytes((uint)1).CopyTo(inOptionValues, 0);
BitConverter.GetBytes((uint)15000).CopyTo(inOptionValues, Marshal.SizeOf(dummy));
BitConverter.GetBytes((uint)15000).CopyTo(inOptionValues, Marshal.SizeOf(dummy) * 2);
//byte[] inOptionValues = new byte[] { 1, 0, 0, 0, 0x20, 0x4e, 0, 0, 0xd0, 0x07, 0, 0 };//另一种设置方法,首次探测时间20秒, 间隔侦测时间2秒
socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null);
林g 2012-09-28
  • 打赏
  • 举报
回复
学习!等待答案,
ggewt2td 2012-09-28
  • 打赏
  • 举报
回复
就是达到超时值,才发送状态询问,
而不是一直发, 一直发会占用带宽和服务端cpu,影响正常工作

比如象网站,默认超过20分钟才断开,
ggewt2td 2012-09-28
  • 打赏
  • 举报
回复
做一个计时管理,只要客户端有数据发过来(不管任何类型数据),都去重置计时。
当计时值超过指定值,服务端就发送状态询问包给客户端,
如果客户端没有反馈,就表示已经断开了,为了保险,可以连续发三次。

这是最简单的方法,而且可以保证通信量尽可能少,避免占用过多带宽和cpu处理
西江 2012-09-28
  • 打赏
  • 举报
回复
[Quote=引用 13 楼 的回复:]

既然客户端和服务端的通信你都完成了,让客户端定时向服务端报个到,还难住你了?
[/Quote]

14楼和16楼的方法,得通过定时扫描Socket实现,这种实现起来比较困难,请问14楼的,怎么触发这个检测,何时触发?
西江 2012-09-28
  • 打赏
  • 举报
回复
[Quote=引用 13 楼 的回复:]

既然客户端和服务端的通信你都完成了,让客户端定时向服务端报个到,还难住你了?
[/Quote]
这个我还真不会.....
西江 2012-09-28
  • 打赏
  • 举报
回复
[Quote=引用 19 楼 的回复:]

//设置KeepAlive,TCP15秒空闲就开始发送KeepAlive包
uint dummy = 0;
byte[] inOptionValues = new byte[Marshal.SizeOf(dummy) * 3];
BitConverter.GetBytes((uint)1).CopyTo(inOptio……
[/Quote]

可否再说明下,通过定时器设置KeepAlive,这段代码怎么加,加到什么地方?
jamesgardon 2012-09-27
  • 打赏
  • 举报
回复
以前我写过 找了半天代码 没找到 估计有年把了


给你点提示
在StateObject里加入最后接收时间,最后发送时间,心跳时间等属性,同时加入
needHeartBeating()判断是否需要心跳
isAlive()判断是否需要心跳
超过心跳周期0.95未发送给client就发送一个心跳
超过心跳周期的1.5倍未收到消息或者心跳就断开连接

以上工作时在扫描进程中完成的

同时要定义消息头,消息头是用来区分消息类型的标志
ycg_893 2012-09-27
  • 打赏
  • 举报
回复
采用会话超时,超时则断开
mabaolin 2012-09-27
  • 打赏
  • 举报
回复
网上的例子:

我写了一个socket的客户端和服务器端,请问服务器端如何判断客户端已经断开连接了
如果客户端结束进程,会发出FIN,但是如果是网线拨了,服务器就不知道了。借楼主的贴讨论一下:
1.如果拨掉网线的时候服务哭正在阻塞读,且没有设置超时选项,它会阻塞多久才能知道不可达?
2.如果。。。。。。。。。。正在阻塞写,。。。。。。。。。。。。。。。。。。。。。。。?
(也没有设置KEEP_ALIVE)
我用心跳机制来防止拔网线这中情况
client每间隔M秒发一个心跳包给server
若连续N秒server端没有收到client的心跳包,则认为client已经去见马克思了
一般应该这样:


1、心跳。
2、非阻塞方式。阻塞方式是不方便判断诸如:网线断开这样的异常情况的。说“不方便”,就因为还是依赖于程序的实现是怎么样的。
QUOTE:
原帖由 linuxxinetd 于 2007-8-21 20:22 发表
进程是这样可以的。但我用的是线程,客户端和服务器端都有两个收发线程,客户端发送线程退出,服务器的收线程怎么知道啊?

服务器的收线程的 recv函数会返回0

------------------------------------------------------------------------
最近在开发中遇到一个问题,就是如何判断远端服务器是否已经断开连接,如果断开那么需要重新连接。

首先想到socket类的方法isClosed()、isConnected()、isInputStreamShutdown()、isOutputStreamShutdown()等,但经过试验并查看相关文档,这些方法都是本地端的状态,无法判断远端是否已经断开连接。

然后想到是否可以通过OutputStream发送一段测试数据,如果发送失败就表示远端已经断开连接,类似ping,但是这样会影响到正常的输出数据,远端无法把正常数据和测试数据分开。

最后又回到socket类,发现有一个方法sendUrgentData,查看文档后得知它会往输出流发送一个字节的数据,只要对方Socket的SO_OOBINLINE属性没有打开,就会自动舍弃这个字节,而SO_OOBINLINE属性默认情况下就是关闭的,太好了,正是我需要的!

于是,下面一段代码就可以判断远端是否断开了连接:

try{
socket.sendUrgentData(0xFF);
}catch(Exception ex){
reconnect();
格拉 2012-09-27
  • 打赏
  • 举报
回复
既然客户端和服务端的通信你都完成了,让客户端定时向服务端报个到,还难住你了?
西江 2012-09-27
  • 打赏
  • 举报
回复
没人么。。。。
wenbin 2012-09-25
  • 打赏
  • 举报
回复
定时检查每个连接是否正常收到包(包括心跳包)
没有收到的就关闭该连接
西江 2012-09-25
  • 打赏
  • 举报
回复
[Quote=引用 6 楼 的回复:]

当客户端关闭的时候,触发shutdown()方法,会发个空白信息给服务端,这时候如果服务端不判断的话,就一直刷新。 判断收到的长度是否<=0 。
这是以前做C/S 聊天室用到的。
[/Quote]
客户端正常断开,服务器是可以收到断开请求的,这时候服务器可以关闭socket连接,但是如果客户端比如手机拔电池,或者死机,服务器这时候不会收到断开请求,服务器如何断开呢?如何去触发shutdown()方法呢
西江 2012-09-25
  • 打赏
  • 举报
回复
[Quote=引用 10 楼 的回复:]

采用心跳机制,可以通过设置Socket.EndReceive()的异步接收超时时间来达到你想要的效果
[/Quote]
可否给出详细示例代码?
加载更多回复(6)

110,567

社区成员

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

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

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