最近工作中涉汲到一些Socket 方面应用 ,如断线重连,连接状态判断等,今天做了一些总结。
1.判断Socket 连接状态
通过 Poll 与 Connected 结合使用 ,重点关注 SelectRead 模式
方法名:
Socket.Poll (int microSeconds, System.Net.Sockets.SelectMode mode) 方法参数:
参数枚举:
SelectRead 如果已调用 Listen(Int32) 并且有挂起的连接,则为 true。 - 或 - 如果有数据可供读取,则为 true。 - 或 - 如果连接已关闭、重置或终止,则返回 true; 否则,返回 false。
SelectWrite 如果正在处理 Connect(EndPoint) 并且连接已成功,则为 true; - 或 - 如果可以发送数据,则返回 true; 否则,返回 false。
SelectError 如果正在处理不阻止的 Connect(EndPoint),并且连接已失败,则为 true; - 或 - 如果 OutOfBandInline 未设置,并且带外数据可用,则为 true; 否则,返回 false。
SelectRead 模式返回 true 的三种情况,若Socket 处挂起状态 ,同时没有数据可读取,可以确定Socket 是关闭或终止的。
1.已调用 Listen(Int32) 并且有挂起的连接,则为 true
2.有数据可供读取,则为 true
3.如果连接已关闭、重置或终止,则返回 true
如下代码 ,true 表示连接断开
s.Poll(1000, SelectMode.SelectRead) && (s.Available == 0) || !s.Connected
稍作调整,判断连接状态完整代码如下:
public static bool IsSocketConnected(Socket s)
{
#region remarks
/* As zendar wrote, it is nice to use the Socket.Poll and Socket.Available, but you need to take into consideration
* that the socket might not have been initialized in the first place.
* This is the last (I believe) piece of information and it is supplied by the Socket.Connected property.
* The revised version of the method would looks something like this:
* from:http://stackoverflow.com/questions/2661764/how-to-check-if-a-socket-is-connected-disconnected-in-c */
#endregion
#region 过程
if (s == null)
return false;
return !((s.Poll(1000, SelectMode.SelectRead) && (s.Available == 0)) || !s.Connected);
/* The long, but simpler-to-understand version:
bool part1 = s.Poll(1000, SelectMode.SelectRead);
bool part2 = (s.Available == 0);
if ((part1 && part2 ) || !s.Connected)
return false;
else
return true;
*/
#endregion
}
2. Socket Blocking 属性
默认情况 Blocking 为 true ,即阻塞状态, Read 时 若没收到数据 , Socket 会挂起当前线程,直到收到数据,当前线程才被唤醒。适用于接收到数据完成后再进行下一步操作场景 。 反之Blocking 为 false ,调用 Recevied 后,无论是否收到数据,都立即返回结果,当前线程可以继续执行。
MSDN 强调, Blocking 属性值与异步操作 如 (Begin 开始API) BeginReceive 等没有相关性 , 比如通过 BeginReceive 接收数据,同时希望当前线程挂起,看起来,似乎可将 Blocking设为 true 实现 。其实不然,需要通过信号量 ManualResetEvent 实现挂起操作。
The Blocking property has no effect on asynchronous methods. If you are sending and receiving data asynchronously and want to block execution, use the ManualResetEvent class
3. Socket 发送数据时,自动断线重连
如添加心跳机制。 这里另一个简单方案,在发送数据时,检查连接状态,如发现断开,重新建立连接 , 这里 Socket 操作 加了锁,防止并发操作。
public static bool SendServer(string st)//发送
{
lock (SocketLock)
{
try
{
if (SocketClient == null || !SocketClient.Connected) {
//断开连接
if (SocketClient != null)
CloseSocket(SocketClient);
//重新连接
if (!TcpServer.TcpIpConnect(SystemConfig.TcpIP, SystemConfig.Port)) {
return false;
}
}
System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
watch.Start();
byte[] Msg = SendEncoding.GetBytes(st);
SocketClient.Send(Msg);
//接收相机返回结果
byte[] bytResult;
Receive(SocketClient, socketBuffer, 0, EndMathFormat, SocketClient.ReceiveTimeout, out bytResult);
watch.Stop();
Logger.Info("获取相机坐标信息耗时(ms):" + watch.ElapsedMilliseconds);
string ReciveMsg = ReceiveEncoding.GetString(bytResult).TrimEnd(new char[] { '\n' , '\0'});
CameraBuding.ReciveMsg = ReciveMsg;
CameraPosition.BuDing = ReciveMsg;
Task.Factory.StartNew((s) => { MyEvent(s.ToString()); } , ReciveMsg);
return true;
}
catch (Exception ex)
{
Logger.Error(ex, " 发送消息给相机用来切换功能异常");
if (!IsSocketConnected(SocketClient))
{
//断开连接
CloseSocket(SocketClient);
//重新连接
TcpServer.TcpIpConnect(SystemConfig.TcpIP, SystemConfig.Port);
}
}
return false;
}
}
4. Socket 接收数据场景
通常两种场景,如:
a.接收指定长度数据包。
b.接收不定长数据包,根据终止符结束。(不定长数据包以 末尾 \n \0 做为结束标记 , 本示例可能返回包含多条终止符数据 ,需对结果二次分隔,如 abc\n\0def\n\0)
=>定长数据包接收:
/// <summary>
/// socket 接收定长数据
/// </summary>
/// <param name="socket"></param>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <param name="size"></param>
/// <param name="timeout"></param>
public static void Receive(Socket socket, byte[] buffer, int offset, int size, int timeout)
{
int endTickCount = Environment.TickCount+ timeout;
int received = 0; // how many bytes is already received
int errTimes = 0;
do
{
if (Environment.TickCount > endTickCount)
throw new Exception("Receive Timeout.");
try
{
received += socket.Receive(buffer, offset + received, size - received, SocketFlags.None);
}
catch (SocketException ex)
{
if (ex.SocketErrorCode == SocketError.WouldBlock ||
ex.SocketErrorCode == SocketError.IOPending ||
ex.SocketErrorCode == SocketError.NoBufferSpaceAvailable)
{
if (++errTimes > 3)
throw new Exception("Receive errTimes 引发异常");
// socket buffer is probably empty, wait and try again
Thread.Sleep(30);
}
else
throw ex; // any serious error occurr
}
} while (received < size);
}
=>不定长数据包接收:
public static void Receive(Socket socket, byte[] buffer, int offset, byte [] endMath , int timeout , out byte [] bytResult)
{
int endTickCount = Environment.TickCount + timeout ;
int received = 0; // how many bytes is already received
int size = buffer.Length;
bool isMatch = false;
int errTimes = 0;
int j = 0;
do
{
if (Environment.TickCount > endTickCount)
throw new Exception("Receive Timeout.");
try
{
received += socket.Receive(buffer, offset + received, size - received, SocketFlags.None);
isMatch = true;
j = 0;
for (int i = received - endMath.Length; i < received && i >= 0; i++)
{
if (buffer[i] != endMath[j++])
{
isMatch = false;
break;
}
}
if (isMatch)
{
break;
}
if (received >= size)
{
throw new Exception("Receive 结束符非预期!");
}
}
catch (SocketException ex)
{
if (ex.SocketErrorCode == SocketError.WouldBlock ||
ex.SocketErrorCode == SocketError.IOPending ||
ex.SocketErrorCode == SocketError.NoBufferSpaceAvailable)
{
if (++errTimes > 3)
throw new Exception("Receive errTimes 引发异常");
// socket buffer is probably empty, wait and try again
Thread.Sleep(30);
}
else
throw ex; // any serious error occurr
}
} while (true);
bytResult = new byte[received];
Array.Copy(buffer, 0, bytResult, 0, received);
}
5. 关闭 Socket
private static void CloseSocket(Socket socket)
{
try
{
if (socket != null)
{
socket.Shutdown(SocketShutdown.Both);
socket.Close();
}
}
catch (Exception ex)
{
Logger.Error(ex, "关闭 socket 时 发生异常:" + ex.Message);
}
finally {
socket = null;
}
}