端口重叠情况下Socket的创建与释放

就一个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;
          }
        }
      }
    }
  }




  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值