.NET异步TcpListener/TcpClient问题

3

我有些困惑如何正确地在.NET中实现TcpListner和TcpClient的异步方法。我已经阅读了很多关于此问题的帖子,并且相信我已经解决了服务器接受新客户端的代码。以下是接受新连接的代码:

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框架正在使用线程池。 - mlindegarde
1
使用BeginRead()方法将自动将您放入线程池中,因此您已经准备就绪。 - Jon B
2个回答

2

一旦你在OnAcceptClient回调函数中提取了对TcpClient的引用,你将想通过GetStream获取NetworkStream,然后立即调用BeginRead(或BeginWrite)以开始异步操作。就像你正在使用TcpListener一样,在回调函数中从EndRead调用之后链接下一个BeginRead


谢谢,我已经弄清楚了。当连接数量增加时,这个系统会如何扩展?比如说用户不是100个而是500个。 - mlindegarde

1

你可以有100个线程,每个线程处理一个客户端,这样也是可以的。你也可以有一个线程循环遍历所有客户端,并异步处理I/O。

通过异步处理,你不必担心单线程处理每个连接所需时间过长。

如果你要扩展到比你现在讨论的规模更大,那么每个线程处理多个客户端将是必要的。

伪代码:

foreach TcpClient
    if DataAvailable
        BeginRead()

我已经有所进展,假设TcpClient.GetStream().BeginRead()是正确的方法(在调用EndRead()后再次调用它以保持输入)。假设将会有500个连接,最好的处理方式是什么?我只是想讨论:不需要代码。 - mlindegarde
@mlindegarde:嗯,那确实是处理所有这些连接的最佳方式。使用BeginRead/EndRead将使所有内容保持在ThreadPool上,因此您实际上不会专用任何一个线程,这显然不会很好地扩展。 - Brian Gideon
@Jon B: 在 OnAcceptClient 中进行初始的 BeginRead,然后开始将其链接到 EndRead 回调函数,以便保持数据流动。虽然我已经有几年没有这样做了,但是我记得是这样做的。也许有些细节我忘记了吗? - Brian Gideon
@Jon B:好吧,你可能永远也不会将其最大化。需要大量连接传输大量数据才能将其最大化。我认为混淆可能在于BeginRead在调用时实际上并未在ThreadPool中排队工作项。这一切都由IO完成端口处理。唯一排队的是回调,而且只有在数据到达后才会发生。至少我是这样理解的。 - Brian Gideon
@Jon B:我非常准备接受这一点是错误的,所以要持怀疑态度。那只是我认为它是如何工作的 :) - Brian Gideon
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接