当前位置: 首页 > 知识库问答 >
问题:

TcpClient dispose订单

曹育
2023-03-14

我正在TCP之上编写一个网络层,在UnitTest阶段遇到了一些麻烦。

下面是我正在做的事情(我的库由多个类组成,但我只向您展示导致我的问题的本机指令,以限制文章的大小):

private const int SERVER_PORT = 15000;
private const int CLIENT_PORT = 16000;
private const string LOCALHOST = "127.0.0.1";

private TcpClient Client { get; set; }
private TcpListener ServerListener { get; set; }
private TcpClient Server { get; set; }

[TestInitialize]
public void MyTestInitialize()
{
    this.ServerListener = new TcpListener(new IPEndPoint(IPAddress.Parse(LOCALHOST), SERVER_PORT));
    this.Client = new TcpClient(new IPEndPoint(IPAddress.Parse(LOCALHOST), CLIENT_PORT));

    this.ServerListener.Start();
}

// In this method, I just try to connect to the server
[TestMethod]
public void TestConnect1()
{
    var connectionRequest = this.ServerListener.AcceptTcpClientAsync();

    this.Client.Connect(LOCALHOST, SERVER_PORT);

    connectionRequest.Wait();

    this.Server = connectionRequest.Result;
}

// In this method, I assume there is an applicative error within the client and it is disposed
[TestMethod]
public void TestConnect2()
{
    var connectionRequest = this.ServerListener.AcceptTcpClientAsync();

    this.Client.Connect(LOCALHOST, SERVER_PORT);

    connectionRequest.Wait();

    this.Server = connectionRequest.Result;

    this.Client.Dispose();
}

[TestCleanup]
public void MyTestCleanup()
{
    this.ServerListener?.Stop();
    this.Server?.Dispose();
    this.Client?.Dispose();
}

首先,如果我想更早地从同一endpoint连接到同一端口上的服务器,我必须首先处置服务器:

如果你这样运行我的测试,它第一次就会成功运行。第二次,它将在两个测试中对connect方法抛出一个异常,认为端口已经在使用中。

我发现的避免此异常(并且能够从同一endpoint在同一侦听器上进行连接)的唯一方法是通过两次向已处置的客户端发送字节(在第一次发送时,没有问题,仅在第二次发送时抛出异常),在服务器内引发SocketException。

为什么server.dispose()没有关闭连接并释放端口???有没有比引发异常更好的方法来释放端口?

提前道谢。

(对不起我的英语,我不是母语人士)

private const int SERVER_PORT = 15000;
private const int CLIENT_PORT = 16000;
private const string LOCALHOST = "127.0.0.1";

static void Main(string[] args)
{
    var serverListener = new TcpListener(new IPEndPoint(IPAddress.Parse(LOCALHOST), SERVER_PORT));
    var client = new TcpClient(new IPEndPoint(IPAddress.Parse(LOCALHOST), CLIENT_PORT));

    serverListener.Start();

    var connectionRequest = client.ConnectAsync(LOCALHOST, SERVER_PORT);

    var server = serverListener.AcceptTcpClient();

    connectionRequest.Wait();

    // Oops, something wrong append (wrong password for exemple), the client has to be disposed (I really want this behavior)
    client.Dispose();

    // Uncomment this to see the magic happens
    //try
    //{
        //server.Client.Send(Encoding.ASCII.GetBytes("no problem"));
        //server.Client.Send(Encoding.ASCII.GetBytes("oops looks like the client is disconnected"));
    //}
    //catch (Exception)
    //{ }

    // Lets try again, with a new password for example (as I said, I really want to close the connection in the first place, and I need to keep the same client EndPoint !)
    client = new TcpClient(new IPEndPoint(IPAddress.Parse(LOCALHOST), CLIENT_PORT));

    connectionRequest = client.ConnectAsync(LOCALHOST, SERVER_PORT);

    // If the previous try/catch is commented, you will stay stuck here, 
    // because the ConnectAsync has thrown an exception that will be raised only during the Wait() instruction
    server = serverListener.AcceptTcpClient();

    connectionRequest.Wait();

    Console.WriteLine("press a key");
    Console.ReadKey();
}

共有1个答案

鄂曦之
2023-03-14

您的端口已在使用中。运行netstat并参阅。您会发现端口仍然以time_wait状态打开。

由于您没有优雅地关闭套接字,网络层必须保持这些端口打开,以防远程endpoint发送更多数据。如果不这样做,套接字可能会接收到用于其他目的的虚假数据,从而破坏数据流。

解决此问题的正确方法是优雅地关闭连接(即使用socket.shutdown()方法)。如果您希望包含一个涉及远程endpoint崩溃的测试,那么您也需要正确地处理该场景。首先,您应该设置一个可以崩溃的独立远程进程。另一方面,您的服务器应该正确地适应这种情况,在适当的时间过去之前不要再次尝试使用端口(即端口实际上已关闭,并且不再在time_wait)。

(但不要使用答案中找到的建议使用SO_reuseAddr/SocketOptionName.reuseAddress……所做的只是隐藏问题,并可能导致实际代码中的数据损坏。)

 类似资料:
  • mysql会员订阅数据表的设计应该如何设计?产品有订阅商品和非订阅的,每次都只能购买一个。 订阅有1个月 3个月的 每次到期自动扣费。如果在一个月类购买了几个订阅商品 则扣费按照最新的一个 然后延长到期时间。其实是不是每次订阅都不需要生成新订单的 翻阅了其他资料都找不到很好的设计

  • 统一下单 没错,什么 H5 支付,公众号支付,扫码支付,支付中签约,全部都是用这个接口下单。 {info} 参数 appid, mch_id, nonce_str, sign, sign_type 可不用传入 服务商模式下, 需使用 sub_openid, 并传入sub_mch_id 和sub_appid $result = $app->order->unify([ 'body' => '

  • 作用 接入方或者费控平台拉取企业支付订单或个人支付转个人垫付订单,做汇总统计之类 依赖 暂无依赖 注意 所有接口调用时需要严格遵守请求方式(GET/POST) 使用接口前需要仔细阅读每个接口的注意事项 接口报错时先阅读通用错误解决方案和当前接口文档下的接口错误解决方案

  • 3.1 下订单 3.1.1 描述 通过调用该接口为指定电话号码充值指定流量 3.1.2 请求地址 地址:https://api.bokecs.com/recharge/createOrder 3.1.3 请求方式 POST 3.1.4 请求参数 1) 请求入参 { "mobile": "18514428123", "flow":"3000", "range":"1" }

  • 4.1订单 4.1.1消费 【场景介绍】 消费支付,需要调用此接口,此接口为聚合模式,比如集成账户余额、账户绑卡、银行卡、微信支付等,具体支持情况需要双方业务确定,商户根据需要可以展示自己的收银台,也可以使用钱麦的收银台。一笔订单支付失败时允许多次进行重试。 【页面展示】 【调用流程】 【重要说明】关于1.1钱麦返回支付URL,此URL可支持自适应浏览器;即此URL在PC端浏览器打开,则展示PC收

  • 目前只有 刷卡支付 有此功能。 调用支付接口后请勿立即调用撤销订单API,建议支付后至少15s后再调用撤销订单接口。 通过内部订单号撤销订单 $app->reverse->byOutTradeNumber("商户系统内部的订单号(out_trade_no)"); 通过微信订单号撤销订单 $app->reverse->byTransactionId("微信的订单号(transaction_id)"