就一个Socket完成传输后重建的问题,被坑了整整两天。 在这里简单记录 一下。
为了完成NET穿透,在客户端我们用 TCPClient 使用随机端口号,设置端口重叠,创建一个到服务器的连接,然后在相同的端口上,再新建一个Socket,并在这个Socket上打开侦听,等待网络上,特别是公网上的连接。
公网上的服务器,为了支持浏览器访问,是一个long-pulling类型的信息交换服务,同时支持http协议和tcp协议。它在每一个命令完成后,都会有一个断开socket的操作。同时客户端的二进制协议环境中,由于功能需求,也会出现客户端主动断开连接的情况。
在上述情况下,如果我们只是简单的执行 Socket.ShutDown() ;Socket.Close ();Socket.Dispose();方法,那么在执行完前几次命令后,再创建socket时,就是会出现:“端口号只能使用一次”,的错误。按网络上的资料介绍,这个错误,会持续"最大网络等待时间"的两倍;大概在4-6分钟之间。对于要求处理实时数据的 long-pulling技术而言,这个时间肯定不能接受。
通过多次尝试,其解决方法如下:
方案一:【优雅的关闭】
发送数据后需要关闭时,不能直接调用close和dispose.
正确步骤:
send前保证 socket.receive函数处于等待接收状态。
send的回调函数中:调用socket.shutdown.
在socket.receive的回调函数中,发现接收到的数据长度是0时调用Socket.Close();Socket.Dispose();
方案二:
要设计一个“关闭指令“【比如命令号为4字节0】。
客户端在需要关闭时,发送该命令。
服务器端接收该命令后,执行关闭操作,调用Socket.ShutDown() ;Socket.Close ();Socket.Dispose();方法。
客户端在接收到BytesTransferred=0时,调用Socket.Close ();Socket.Dispose();方法。
如果需要在Socket.Send的执行路径中确定Socket已经完全销毁,那么可以不停地发送上述关闭指令,一直到Socket.Connected状态为false。这时调用Socket.Close ();Socket.Dispose();方法。
TCP的3次握手无法帮助处理这个问题, 另一个概念”TCP的四次挥手“,对这个处理过程有更深入的解释。
public class SocketCommandDecoder:IDisposable
{
public static int HttpGet = 12;
public static int HttpPost = 21;
public static int HttpPut = 21;
public static int HttpDelete = 21;
public DateTime ReceivedLast = DateTime.Now;
public Action<SocketCommandDecoder> OnClosed;
public Action<BufferPip> DoBinary;
public Action<BufferPip> DoHttp;
Socket tS;
System.Net.Sockets.SocketAsyncEventArgs receiv;
public BufferPip pip;
Action OnDataCall;
public void OnSocketConnect(Socket s,int bufferSize)
{
pip = new BufferPip(bufferSize);
tS = s;
OnDataCall = DispatchData;
receiv = new SocketAsyncEventArgs();
receiv.Completed += OnDataReceived;
if (!tS.ReceiveAsync(receiv))
OnDataReceived(tS, receiv);
}
void OnDataReceived(object sender, SocketAsyncEventArgs e)
{
try
{
if (e.BytesTransferred == 0)
{
this.Dispose();
return;
}
else
pip.SkipWrite(e.BytesTransferred);
ReceivedLast = DateTime.Now;
OnDataCall();
}
finally
{
if (tS.Connected)
{
if (!tS.ReceiveAsync(receiv))
OnDataReceived(tS, receiv);
}
}
}
//检查最后接收数据的时间,如果最后接收数据的时间超过deadline,那么关闭socket.
public void TimeClose(DateTime deadline)
{
if (this.ReceivedLast > deadline)
this.Dispose();
}
//读取到指定长度的数据后调用回调函数
public void NoneBlockWriteForLength(int ask, Action<SocketCommandDecoder> callback)
{
OnDataCall = () =>
{
if (pip.Count >= ask)
{
OnDataCall = DispatchData;
callback(this);
}
};
}
//接收到新数据,等待解析执行命令
protected virtual void DispatchData()
{
if (pip.Count < 4)
return;
int v = pip.FetchInt(0);
if (v == HttpGet
|| v == HttpPost
|| v == HttpPut
|| v == HttpDelete)
OnHttp();
else
OnBinary();
}
public BufferPip ExtendBufferSize(int newSize)
{
if (newSize < this.pip.Buffer.Length)
return pip;
BufferPip newPip = new BufferPip(newSize);
pip.ReadToBuffer(newPip);
this.pip = newPip;
return this.pip;
}
public virtual void OnBinary()
{
if (DoBinary != null)
DoBinary(this.pip);
this.pip.LeftZero();
}
public virtual void OnHttp()
{
if (DoHttp != null)
DoHttp(this.pip);
this.pip.LeftZero();
}
public void SendAndClose(BufferPip pip)
{
pip.FetchToSocket(this.tS, (exe) =>
{
if (exe != null)
ErrorLog.Current.LogException(10000, "SocketCommandDecoder.SendClose", exe);
this.Dispose();
});
}
public void Dispose()
{
lock (this)
{
if (tS != null)
{
if (tS.Connected)
{
tS.Shutdown(SocketShutdown.Both);//发送关闭指令,等待receive到0字节长度
return;
}
else
{
this.tS.Close();
this.tS.Dispose();
if (OnClosed != null)
OnClosed(this);
OnClosed = null;
this.tS = null;
receiv = null;
pip = null;
}
}
}
}
}