views:

54

answers:

2

I'm at a bit of a loss as to how to properly implement the asynchronous methods for TcpListner and TcpClient int .NET. I've read over quite a few posts on hear and I believe I have the code to accept new clients on the server taken care of. Here is the code for acception new connections:

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

What I'm not sure about is what to do with the client once the connection is established. From what I've seen, usually people create a new thread for each client connected to prevent IO reads from blocking the application.

My application will have up to 100 clients connected at any given time. If I spin off a new thread for each client then I'll have 100 or so threads. Is that correct? I think I'm just missing something with the async methods in the .NET framework.

Most of the example I see have the server accept the connection, read in a short message (e.g. "hello server") and then close the client and shutdown the server. This doesn't help me understand the proper way to maintain a large number of active clients over a large period of time.

Thanks for any help in advance.

+1  A: 

You could have 100 threads all handling one client each and that would be fine. You can also have one thread that loops through all of your clients and asynchronously handles I/O.

By making this asynchronous you don't have to worry about your single threading taking too long to process each connection.

If you are going to scale much higher than what you are talking about, handling multiple clients per thread will be necessary.

Pseudo code:

foreach TcpClient
    if DataAvailable
        BeginRead()
Jon B
I've made headway assuming that TcpClient.GetStream().BeginRead() is the way to go (just call it again after calling EndRead() to keep the input coming).Let's say there were going to be 500 connections, what is the best way to handle this? Discussion is all I'm really looking for: code not needed.
mlindegarde
@mlindegarde: Well, that *is* the best way to handle all of those connections. Using `BeginRead`/`EndRead` will keep everything on the `ThreadPool` so you do not actually dedicate any one thread which obvious will not scale well.
Brian Gideon
@mlindegarde - You would have one `Thread` that continuously loops through a collection of `TcpClient` objects (like the pseudo code I posted). By using `BeginRead()` it won't take a long at all to get through every client.
Jon B
@Brian - he still needs something that calls `BeginRead()`, which is where an I/O thread comes in.
Jon B
@Jon B: Do the initial `BeginRead` in `OnAcceptClient` and then starting chaining it to the `EndRead` callback that way you keep the pump going. It has been years since I have done this, but that is how I remember doing it. Maybe there is something I have forgotten?
Brian Gideon
@Brian - If you keep launching BeginRead() from callback functions you will exhaust the thread pool at some point (it's something like 1024 threads per processor, but it's certainly not infinite). A single I/O thread can launch BeginRead() only when needed, better supporting large numbers of clients. If you're not going to support large numbers of clients, you could get away with the approach you're talking about.
Jon B
@Jon B: Well, it would only exhaust the `ThreadPool` if you did not let the callback finish right? So if you call `EndRead` in the callback, do something with the buffer (ideally as fast as possible) and then call `BeginRead` just before the callback completes then you are basically letting work item die in exhange for allowing allowing a new one to be queued. Once the `ThreadPool` gets exhausted the work items will just stack up in the queue, but as long as you do not hang up a `ThreadPool` thread those work items will be dequeued eventually.
Brian Gideon
@Brian - that's true. I'm not sure I would want to do that, though, as you're basically always maxing out the thread pool.
Jon B
@Jon B: Well, no, you probably would never max it out. It would take an awful lot of connections pumping a lot of data to max it out. I think the confusion might be that `BeginRead` does not actually queue a work item in the `ThreadPool` when it is called. That is all handled by IO completion ports. The only thing that gets queued is the callback and that would only occur after data has arrived. At least that's the way I understand it.
Brian Gideon
@Brian - AH! I hadn't realized that `NetworkStream` works differently from most of the other Begin/End functions (including the base `Stream.BeginRead()` function!). So .NET won't block a pooled thread waiting for data, and you can safely call BeginRead() without actually tying up resources. I'm going to have to tinker with this later on :)
Jon B
@Jon B: I am very prepared to be wrong about that so take it with a grain of salt. That's just how I *think* it works :)
Brian Gideon
@Brian - the MSDN docs back you up on this: http://msdn.microsoft.com/en-us/library/system.net.sockets.networkstream.beginread.aspx
Jon B
Thanks for the discussion, this is exactly the kind of information I was hoping to get.
mlindegarde
+1  A: 

Once you have extracted the reference to a TcpClient in your OnAcceptClient callback you will want to get the NetworkStream via GetStream and then call BeginRead (or BeginWrite) immediately to start the asynchronous operation. You will chain the next BeginRead after you have called EndRead from the callback just like you are doing with the TcpListener.

Brian Gideon
Thanks, I got that figured out. How will this scale as the number of connections increases? Let's say its 500 instead of 100 users.
mlindegarde