Let's assume you have the very idea of sockets, otherwise it would be hard to explain anything. The ideas of how to use sockets in .NET are simple enough, even though the seem to be hard to find -- at first.
You will need to use:
using Sockets = System.Net.Sockets;
using Threads = System.Threading;
using Streams = System.IO;
using IPAddress = System.Net.IPAddress;
Now, what kind of sockets, indeed? First, you need to choose from UDP vs. TCP. Anyway, top classes to use are:
Sockets::TcpListener
,
Sockets::TcpClient
,
Sockets::UdpClient
.
UdpClient
is more simple, but "business logic" could be much more difficult. As I cannot teach you your own business logic, let's concentrate on TCP; and UDP you can learn later by yourself. TCP is the most typical use of sockets, while UDP can be reserved for much more exotic applications.
All the design of your communication layer could be built around six simple ideas:
Idea #1: With TCP you work with sessions and channels. For each session, one host on your network plays the role of server, another -- the role of client. It does not matter which side is reading or writing data (typically, both sides, using some application-level protocol you define by your code). What does matter is who is listening for a new connection is the server. Let's assume there are just one server on one side and one client on another. Then you will basically need two separate threads on server side: one is listening for new connections, another one is reading/writing data from/to all client sockets at once. Having separate threads for each remote sockets is usually a big mistake: what if there are too many of them? -- the result unpredictable (like server crash).
Note: There is not strict exact recipe which process on each host plays the role of server or client: each can implement more then one server and more than one client. Therefore, I pick up the most simple example; we shall concentrate just on two sockets on both sides and associated threads (see above).
Idea #2: Server socket is created like this:
Server = new Sockets::TcpListener(IPAddress.Any, port);
Server.ExclusiveAddressUse = true;
Note use of
IPAddress.Any
: server does not know its own IP or remote socket IPs.
Now, it is important to understand that the same instance of
TcpListener
is used across two threads. No IPC primitive are needed to interlock this instance: initially sockets are designed to serve as IPC themselves (but you usually will need to use mutexes to interlock access to shared data, as always).
Idea #3: Server's listening thread should use the following call in an infinite loop:
Sockets::TcpClient client = Server.AcceptTcpClient();
This call is a blocking one, so the thread will be truly sleeping in waiting for a connection, not wasting a single CPU cycle.
The variable
client
here represents remote client socket on server's socket. This object should be used on communication thread of the server to write or read the data. As we don't know how many remote connection to expect, all such objects should be collected in some container (I would use
Queue<Sockets::TcpClient>
) to be used in reading/writing the data in sequence.
Idea #4: The server's communication thread should run an infinite cycle reading and/or writing to thread obtained as
NetworkStream stream = client.GetStream();
Idea #5: Client thread on client side first connects to selected remote server (selected by URL and port) then goes to infinite cycle reading and/or writing to/from remove socket in the order symmetrical to the order implemented on the server: server writes, client reads and visa versa. Pretty good policy is to confirm each chunk of data send in one direction by some confirmation code (like one special byte):
Client = new Sockets::TcpClient();
Client.ExclusiveAddressUse = false;
Client.Connect(PublisherHostName, Port);
Streams::Stream Stream = Client.GetStream();
Last idea #6:
Now the trouble is: Stream read/write operations work with arrays of byte, but you want to use some objects instead. One of the possible solutions: use System.Runtime.Serialization binary formatter -- see the description of serialization,
System.SerializableAttribute
and related topics.
Oh! Why I'm still writing? That's it, basically.
I would like some feedback later on. Does it help? Maybe, more detailed article is needed.
Thank you for attention.