【问题标题】:.NET Async TcpListener / TcpClient Question.NET 异步 TcpListener / TcpClient 问题
【发布时间】:2011-04-07 13:10:45
【问题描述】:

对于如何正确实现 TcpListner 和 TcpClient int .NET 的异步方法,我有点不知所措。我已经阅读了很多关于hear 的帖子,我相信我有代码可以在服务器上接受新客户端。下面是接受新连接的代码:

Public Sub Start()
    m_oListener = New TcpListener(m_oIpAddress, m_iPort)

    m_oListener.Start()
    m_oListener.BeginAcceptTcpClient(New AsyncCallback(AddressOf OnAcceptClient), m_oListener)
End Sub

Public Sub OnAcceptClient(ByVal ar As IAsyncResult)
    Dim listener As TcpListener = CType(ar.AsyncState, TcpListener)
    Dim client As TcpClient = listener.EndAcceptTcpClient(ar)

    If listener.Server.IsBound Then
        ThreadPool.QueueUserWorkItem(New WaitCallback(AddressOf OnHandleClient), client)
        listener.BeginAcceptTcpClient(New AsyncCallback(AddressOf OnAcceptClient), listener)
    End If
End Sub

我不确定的是,一旦建立连接,客户端该怎么做。据我所知,通常人们会为每个连接的客户端创建一个新线程,以防止 IO 读取阻塞应用程序。

我的应用程序将在任何给定时间连接多达 100 个客户端。如果我为每个客户分拆一个新线程,那么我将拥有 100 个左右的线程。那是对的吗?我想我只是缺少 .NET 框架中的异步方法。

我看到的大多数示例都让服务器接受连接,读入一条短消息(例如“hello server”),然后关闭客户端并关闭服务器。这并不能帮助我理解在很长一段时间内保持大量活跃客户的正确方法。

提前感谢您的帮助。

【问题讨论】:

  • 好吧,我现在看到我可能应该使用 TcpClient.GetStream().BeginRead() 从流中异步读取数据。如果我使用 BeginRead(),是否需要使用 ThreadPool.QueueUserWorkItem?我看到的几个示例使用了线程轮询,但这里似乎不需要它,因为我想当我使用 BeginXXX 和 EndXXX 方法时,.NET 框架正在使用线程池。
  • 使用BeginRead() 方法已经将你放入线程池,所以你已经准备好了。
  • 太好了,感谢您的快速回复。

标签: .net multithreading sockets tcp tcplistener


【解决方案1】:

一旦您在OnAcceptClient 回调中提取了对TcpClient 的引用,您将希望通过GetStream 获取NetworkStream,然后立即调用BeginRead(或BeginWrite)以启动异步手术。在您从回调中调用EndRead 之后,您将链接下一个BeginRead,就像您对TcpListener 所做的那样。

【讨论】:

  • 谢谢,我明白了。随着连接数量的增加,这将如何扩展?假设它有 500 个而不是 100 个用户。
【解决方案2】:

您可以有 100 个线程,每个线程都处理一个客户端,这很好。您还可以有一个线程循环遍历所有客户端并异步处理 I/O。

通过使这种异步方式,您不必担心单线程处理每个连接需要很长时间。

如果您要扩展的规模远高于您所说的,那么每个线程处理多个客户端将是必要的。

伪代码:

foreach TcpClient
    if DataAvailable
        BeginRead()

【讨论】:

  • 我已经取得了进展,假设 TcpClient.GetStream().BeginRead() 是要走的路(只需在调用 EndRead() 以保持输入后再次调用它)。假设将有 500 个连接,处理这个问题的最佳方法是什么?讨论就是我真正想要的:不需要代码。
  • @mlindegarde:嗯,是处理所有这些连接的最佳方式。使用BeginRead/EndRead 会将所有内容保留在ThreadPool 上,因此您实际上不会专门使用任何明显无法很好扩展的线程。
  • @mlindegarde - 你会有一个Thread 不断循环通过TcpClient 对象的集合(就像我发布的伪代码)。通过使用BeginRead(),它不会花费很长时间来打通每个客户。
  • @Brian - 他仍然需要调用 BeginRead() 的东西,这是 I/O 线程的来源。
  • @Jon B:在OnAcceptClient 中执行初始BeginRead,然后开始将其链接到EndRead 回调,这样您就可以保持泵的运行。自从我这样做以来已经有好几年了,但这就是我记得的做法。也许我忘记了什么?
猜你喜欢
  • 2016-08-15
  • 2011-10-06
  • 2012-09-20
  • 2011-07-20
  • 2017-08-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-02
相关资源
最近更新 更多