简介:这是最近做socket保持长连接的一些心得,欢迎大家讨论 - .NET技术 / C#的详细页面,介绍了和c/c++,最近做socket保持长连接的一些心得,欢迎大家讨论 - .NET技术 / C#有关的知识,加入收藏请按键盘ctrl+D,谢谢大家的观看!要查看更多有关信息,请点击此处
自己写的客户端马上要发布了,忽然发现了一大堆问题,主要集中在与服务器的TCP连接经常莫名断开,客户端又检测不到,不能及时重连。一个多星期的修改,有一些心得,与大家分享。也希望大家多发表意见,您的意见也许最后就实现在我的软件中了!
主要分为两部分: 一,如何更好的检测TCP连接是否正常 二,如何提取本机TCP连接状态
一,如何更好的检测TCP连接是否正常 这方面问题,我上网查了很久,一般来说比较成熟的有两种方法: 1是在应用层制定协议,发心跳包,这也是C#,JAVA等高级语言比较常用的方法。客户端和服务端制定一个通讯协议,每隔一定时间(一般15秒左右),由一方发起,向对方发送协议包;对方收到这个包后,按指定好的通讯协议回一个。若没收到回复,则判断网络出现问题,服务器可及时的断开连接,客户端也可以及时重连。 2通过TCP协议层发送KeepAlive包。这个方法只需设置好你使用的TCP的KeepAlive项就好,其他的操作系统会帮你完成。操作系统会按时发送KeepAlive包,一发现网络异常,马上断开。我就是使用这个方法,也是重点向大家介绍的。
使用第二种方法的好处,是我们在应用层不需自己定协议,通信的两端,只要有一端设好这个值,两边都能及时检测出TCP连接情况。而且这些都是操作系统帮你自动完成的。像我们公司的服务端代码就是早写好的,很难改动。以前也没加入心跳机制,后面要改很麻烦,boss要求检测连接的工作尽量客户端单独完成.... 还有一个好处就是节省网络资源。KeepAlive包,只有很简单的一些TCP信息,无论如何也是比你自己设计的心跳包短小的。然后就是它的发送机制,在TCP空闲XXX秒后才开始发送。自己设计心跳机制的话,很难做到这一点。
这种方法也是有些缺陷的。比如某一时刻,网线松了,如果刚好被KeepAlive包检测到,它会马上断开TCP连接。但其实这时候TCP连接也算是established的,只要网线再插好,这个连接还是可以正常工作的。这种情况,大家有什么好办法处理吗?
C#中设置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);
IPEndPoint iep = new IPEndPoint(this._IPadd, xxxx); this._socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); this._socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null); this._socket.Connect(iep); 这里我设定TCP15秒钟空闲,就开始发送KeepAlive包,其实完全可是设定得长一点。
二,如何提取本机TCP连接状态 设好了KeepAlive值,又遇到麻烦了,我没找到当网络异常时,它断开连接后怎么通知我...我搜了很久都没找到,要是哪位兄弟知道的话告诉我吧。我是使用笨办法的,找到所有本地TCP连接的信息,筛选出我需要的那个TCP。 查看本机所有TCP连接信息,网上一般的方法,都是通过程序调用CMD命令里的netstat进行,然后再分析其内容。但在CMD窗口用过这个命令的都知道,悲剧的时候它显示完所有TCP信息需要15s,或者更长时间,这在我的程序中是不能忍受的。 然后我又查找了一些牛人的博客,发现有人提到用iphlpapi.dll。这是一个在win98以上操作系统目录System32都包含的库函数,功能异常强大,大家可以放心使用!但是使用起来比较麻烦,基本找不到C#现成使用的例子,就算有,也是很老版本的,完全不能用 参考C#如何使用GetTcpTable API获取TCP/IP连接信息_yulinlover的博客-CSDN博客
public class NativeFunc
{
[StructLayout(LayoutKind.Sequential)]
public class MIB_TCPROW
{
public int dwState;
public int dwLocalAddr;
public int dwLocalPort;
public int dwRemoteAddr;
public int dwRemotePort;
}
[StructLayout(LayoutKind.Sequential)]
public class MIB_TCPTABLE
{
public int dwNumEntries;
public MIB_TCPROW[] table;
}
[DllImport("Iphlpapi.dll")]
static extern int GetTcpTable(IntPtr pTcpTable, ref int pdwSize, bool bOrder);
[DllImport("Iphlpapi.dll")]
static extern int SendARP(Int32 DestIP, Int32 SrcIP, ref Int64 MacAddr, ref Int32 PhyAddrLen);
[DllImport("Ws2_32.dll")]
static extern Int32 inet_addr(string ipaddr);
[DllImport("Ws2_32.dll")]
static extern ushort ntohs(ushort netshort);
//SendArp获取MAC地址
public static string GetMacAddress(string macip)
{
StringBuilder strReturn = new StringBuilder();
try
{
Int32 remote = inet_addr(macip);
Int64 macinfo = new Int64();
Int32 length = 6;
SendARP(remote, 0, ref macinfo, ref length);
string temp = System.Convert.ToString(macinfo, 16).PadLeft(12, '0').ToUpper();
int x = 12;
for (int i = 0; i < 6; i++)
{
if (i == 5) { strReturn.Append(temp.Substring(x - 2, 2)); }
else { strReturn.Append(temp.Substring(x - 2, 2) + ":"); }
x -= 2;
}
return strReturn.ToString();
}
catch
{
return string.Empty;
}
}
public static bool IsHostAlive(string strHostIP)
{
string strHostMac = GetMacAddress(strHostIP);
return !string.IsNullOrEmpty(strHostMac);
}
public static MIB_TCPTABLE GetTcpTableInfo()
{
//声明一个指针准备接受Tcp连接信息
IntPtr hTcpTableData = IntPtr.Zero;
//声明hTcpTableData指针所指向的内存缓冲区大小
int iBufferSize = 0;
//声明MIB_TCPTABLE对象,作为返回值
MIB_TCPTABLE tcpTable = new MIB_TCPTABLE();
//声明一个List对象来临时存放MIB_TCPROW对象
List lstTcpRows = new List();
//调用API来获得真正的缓冲区大小,iBufferSize默认为0,
//这时调用API GetTcpTable会触发一个异常ERROR_INSUFFICIENT_BUFFER
//通过这个异常系统会把真正的缓冲长度返回
GetTcpTable(hTcpTableData, ref iBufferSize, false);
//为托管指针在堆上分配内存
hTcpTableData = Marshal.AllocHGlobal(iBufferSize);
//求得MIB_TCPROW对象的内存字节数
int iTcpRowLen = Marshal.SizeOf(typeof(MIB_TCPROW));
//根据上面得到的缓冲区大小来推算MIB_TCPTABLE里的MIB_TCPROW数组长度
//下面用缓冲长度-sizeof(int)也就是去掉MIB_TCPTABLE里的成员dwNumEntries所占用的内存字节数
int aryTcpRowLength = (int)Math.Ceiling((double)(iBufferSize - sizeof(int)) / iTcpRowLen);
//重新取得TcpTable的数据
GetTcpTable(hTcpTableData, ref iBufferSize, false);
//下面是关键,由于MIB_TCPTABLE里的成员有一个是数组,而这个数组长度起初我们是不能确定的
//所以这里我们只能根据分配的指针来进行一些运算来推算出我们所要的数据
for (int i = 0; i < aryTcpRowLength; i++)
{
//hTcpTableData是指向MIB_TCPTABLE缓冲区的内存起始区域,由于其成员数据在内存中是顺序排列
//所以我们可以推断hTcpTableData+4(也就是sizeof(dwNumEntries)的长度)后就是MIB_TCPROW数组的第一个元素
IntPtr hTempTableRow = new IntPtr(hTcpTableData.ToInt32() + 4 + i * iTcpRowLen);
MIB_TCPROW tcpRow = new MIB_TCPROW();
tcpRow.dwLocalAddr = 0;
tcpRow.dwLocalPort = 0;
tcpRow.dwRemoteAddr = 0;
tcpRow.dwRemotePort = 0;
tcpRow.dwState = 0;
//把指针数据拷贝到我们的结构对象里。
Marshal.PtrToStructure(hTempTableRow, tcpRow);
lstTcpRows.Add(tcpRow);
}
tcpTable.dwNumEntries = lstTcpRows.Count;
tcpTable.table = new MIB_TCPROW[lstTcpRows.Count];
lstTcpRows.CopyTo(tcpTable.table);
return tcpTable;
}
public static string GetIpAddress(long ipAddrs)
{
try
{
System.Net.IPAddress ipAddress = new System.Net.IPAddress(ipAddrs);
return ipAddress.ToString();
}
catch { return ipAddrs.ToString(); }
}
public static ushort GetTcpPort(int tcpPort)
{
return ntohs((ushort)tcpPort);
}
public static bool IsPortBusy(int port)
{
MIB_TCPTABLE tcpTableData = GetTcpTableInfo();
return false;
}
}
调用方式
private void button1_Click(object sender, EventArgs e)
{
NativeFunc.MIB_TCPTABLE tcpTableData = new NativeFunc.MIB_TCPTABLE();
tcpTableData = NativeFunc.GetTcpTableInfo();
for (int i = 0; i < tcpTableData.dwNumEntries; i++)
{
this.richTextBox1.AppendText(string.Format("{0}:{1}-->>{2}:{3}/n",
NativeFunc.GetIpAddress(tcpTableData.table[i].dwLocalAddr),
NativeFunc.GetTcpPort(tcpTableData.table[i].dwLocalPort).ToString(),
NativeFunc.GetIpAddress(tcpTableData.table[i].dwRemoteAddr),
NativeFunc.GetTcpPort(tcpTableData.table[i].dwRemotePort).ToString()));
}
}
下载了里面提到的项目,仔细结合自己体会进行修改,终于能用了。每隔一段时间,我的客户端就用这个方法扫描一遍本地TCP信息,若发现连接有问题,则断开重连。 这个方法能瞬间得到本机所有TCP连接信息(如果你有兴趣可以扩充,它的功能真的是太强大了),没有CMD命令netstat那不能忍受的延迟,相当好用。代码比较长,就不贴出来了。
这些是我不太成熟的做法,下星期项目就要提交了,不能再出啥岔子,希望大家多提意见,帮我改善一下。 本版人气很旺,但貌似用socket的人不多,不知道帖子发这是否合适。要是不合适,请前辈提点下发在哪个版比较好? 回答 1
--------------------------------------------------------------------------------
------其他回答(15分)---------
我用的下面的代码
C# code
#region 检测网络状态
[DllImport("sensapi.dll")] private extern static bool IsNetworkAlive(out int connectionDescription);
private void NetCheckThread() { while (true) { int flags;//上网方式 bool m_bOnline = true;//是否在线 m_bOnline = IsNetworkAlive(out flags); if (m_bOnline) { if (!NetAlive) { NetAlive = true; try { if (NetConnect != null) NetConnect(this, null); } catch { } } } else { if (NetAlive) { NetAlive = false; try { if (NetDisconnect != null) NetDisconnect(this, null); } catch { } } }
Thread.Sleep(1); } }
#endregion
------其他回答(40分)---------
C# code
public AsyncSocket(Socket sock,int index) { this.index = index; this.sock = sock; this.SetXinTiao(this.sock); Socket obj_Socket = sock; StateObject obj_SocketState = new StateObject(); obj_SocketState.workSocket = obj_Socket; obj_Socket.BeginReceive(obj_SocketState.buffer, 0, StateObject.BufferSize, SocketFlags.None, new AsyncCallback(ReceiveCallback), obj_SocketState); }
//设置心跳 private void SetXinTiao(Socket tmpsock) { byte[] inValue = new byte[] { 1, 0, 0, 0, 0x20, 0x4e, 0, 0, 0xd0, 0x07, 0, 0 };// 首次探测时间20 秒, 间隔侦测时间2 秒 tmpsock.IOControl(IOControlCode.KeepAliveValues, inValue, null); }
private void ReceiveCallback(IAsyncResult ar) { try { StateObject obj_SocketState = (StateObject)ar.AsyncState; Socket obj_Socket = obj_SocketState.workSocket; int BytesRead = obj_Socket.EndReceive(ar); if (BytesRead > 0) { byte[] tmp = new byte[BytesRead]; Array.ConstrainedCopy(obj_SocketState.buffer, 0, tmp, 0, BytesRead); if (socketDataArrival!=null) { socketDataArrival(this.index, tmp); } } else { if (this.sock.Connected) { if (socketDisconnected!=null) { socketDisconnected(index); } } } obj_Socket.BeginReceive(obj_SocketState.buffer, 0, StateObject.BufferSize, SocketFlags.None, new AsyncCallback(ReceiveCallback), obj_SocketState); } catch (Exception ex) { if (socketDisconnected!=null) { socketDisconnected(index); //Keepalive检测断线引发的异常在这里捕获 } } }
------其他回答(10分)---------
private void ReceiveCallBack(IAsyncResult ar) { try { lock (this) { int recvCount = _client.GetStream().EndRead(ar);
//这种情况就是断开了连接 if (recvCount < 1) { if (OnSocketError != null) { _client.Close(); OnSocketError(this, new EventArgs()); } }
------其他回答(15分)---------
引用 44 楼 cqsfd 的回复: 引用 34 楼 qldsrx 的回复: 讨论了这么多,怎么就不考虑下不保持长连接的做法呢?服务器资源毕竟宝贵,如果说长时间不使用的连接也保持的话,显然是不值得的,你必须明白,保持一个长连接的开销比创建一个新连接更大,所以一般设置一个超时时间,当在规定时间内没有动作就自动断开连接,FTP就有这种设置。
欢迎青龙白虎兄! 这个长连接是必须的,40楼的MM帮我说明了理由 很多情况下,是需要服务器给客户端发信息的 如果用短连接,就得客户端每隔个30秒连接一下服务端,看看服务端有消息要发没,没有就断开;隔30秒再这么做一次 我的软件刚好就有这个需求,所有得长连接
你说的是双工通讯的情况,显然这不是很多情况下都需要的,是个特例,不过这种特例也只需要一个长连接即可,并发的其它连接都应该使用短连接。
另外你可以学学人家腾讯公司,他们就连一个长连接都没有就可以得到服务器的通知,你知道这是什么原因吗?因为UDP根本不需要连接,客户端定时发送UDP包到服务器端确认是否有消息需要接收,这个确认包的开销远比TCP小的多,显然这种确认包是否丢失都无所谓,你可以把频率放高,一旦有消息需要接收,也可以切换到TCP连接去服务器读取消息。
最后,如果条件允许,开始用WCF来实现远程通讯吧,毕竟WCF的功能比Socket强大得多。
------其他回答(20分)---------
好像你没有回答我的问题啊! ============================== 呃,我是看这个地方热闹,凑个热闹让大家看看我的做法合理不。
我推测,网络断开,你能知道,是因为对方给你发了个TCP的FIN包,这个包是没内容的 要是客户端网线把了一会,你能检测出来吗?2个小时以上,没任何消息的TCP,还能保持吗? ==================================================================== 这个我试了下,如果客户端把无线网卡关闭,服务端用这种方法是监测不到客户端的断开动作。
到底为什么会有if (_readBytesCount == 0) ? =============================================== _readBytesCount是NetworkSream.Read方法返回值。如果客户端close。则此方法立即返回0。