I don't quite understand exactly how a few of the features are shared when a TcpListener
and TcpClient
communicate.
Let's say the following code is run (for now ignore synchronisation):
Server:
Dim server As New TcpListener(localAddr, port)
server.Start()
Dim client As TcpClient = server.AcceptTcpClient()
Client:
Dim client As New TcpClient
client.Connect(hostAddr, port)
And the connection is successfully established. Now there are two TcpClient
instances — one on server side and one on client side. However, they share the same network stream through TcpClient.GetStream()
.
I'm slightly confused — does the client pass itself and all of its properties to the server when server.AcceptTcpClient()
is called?
What about any changes to either of the TcpClient
instances after this? When the connection shuts down I call this on both sides:
client.GetStream.Close()
client.Close()
But I get an exception with TcpClient.GetStream.Close()
on the client which executes this code the latest because it tells me that the client is already closed (this happens when the above code isn't perfectly synchronised on both sides).
What about the .SendBufferSize
and .ReceiveBufferSize
properties? Do I need to set this on both sides of the connection?
Hope someone can clear up my confusion with an explanation of how exactly the TcpClient/Listener
classes work during the communication — so far I haven't been able to find documentation explaining what exactly happens.
The TCP protocol does not know what a TcpClient
is. This is a .NET concept. TCP does not reference .NET concepts at all. For that reason no objects will be sent across the wire.
The only thing that is sent is the bytes you explicitly write.
Each side has it's own isolated objects. Both sides use their own TcpClient
object which acts like a handle to the TCP connection.
client.GetStream.Close()
client.Close()
This is not the proper shutdown sequence. The first line is redundant to the second and incomplete. Close should never be called. The best way to do it is to wrap the client in using
. The second best way is to call Dispose
on the client. The Close
methods in the BCL are historic accidents and should be ignored. They do the same thing that Dispose does in all cases that I ever looked at.
Don't touch the buffer sizes. They control how much memory the kernel uses to buffer data on your end of the connection. The kernel is capable of managing this by itself.
Also don't look at the buffer sizes in your code. They are meaningless. Also don't use the DataAvailable
property because if it returns false/0
this does not mean that no data can be read.
The Connected
property is not necessarily synchronized on both sides. If the network goes down there can be no synchronization. Never look at the Connected
property. If it says true
the next nanosecond it could be false
. So it's not possible to make decisions based on that property. You do not need to test anything. Just Read/Write and handle the exceptions by aborting.
Regarding packets, you are not sending packets when you Write
. TCP has a boundaryless stream of bytes. The kernel packetizes your data internally. You do not need to split data into specific sizes. Just use a fairly big buffer size such as 8K (or more on fast networks). The write size is only about saving CPU time by being less chatty (assuming nagling is enabled).