Maybe I did not completely understand your idea, but I feel you're confusing something. Anyway, I have a clear idea how to work with multiple clients to identify them properly.
Generally, just one IP port is quite enough. You seemingly confirm it. Then, the port is the attribute of the service side, not client's. A client only uses the known port of the service whem trying to connect. If so, what does it mean "to identify each packets from a port".
However, your problem is easy to solve. The unique identification at the service side is the remote socket of the client, that is, a socket as an object of the service code which represent the remote socket of the client.
You can use TPC, hence sessions. A remote socket represents a client's socket on the service side as long as both sides keep the connection alive.
You can use socket level, but with .NET I would prefer a bit higher level: using
System.Net.Sockets.TcpListener
on service side and
System.Net.Sockets.TcpClient
on client side.
The schema of the operation should be like this: on the service side, you need two separate threads: one accepts new client connections (let's call it listening thread), another one writes and reads data to/from
NetworkStream
following some application-level protocol you develop in cycle with all available remote (client's) sockets (let's call it communication thread). Those sockets are created in the the communication thread which calls the method
System.Net.Sockets.TcpListener.AcceptTcpClient
in infinite cycle. This method is blocking, so the thread will be in wait state most of the time. This method returns the object representing a newly created client socket on the service side (of the same type:
TcpClient
). Now, you should support collection of such
TcpClient
instances (more exactly, some data items each including a reference to such instance, see belo); this collection is used by both thread. The item type of this collection should associate some client-specific data (I don't know if you want to use authentication, but — whatever involved). This is association is the client identity you need. Don't forget to use appropriate locking primitives to make collection operation thread-safe.
In case a client breaks connection by whatever reason, your communication thread will throw exception. This exception should be caught; and the exception handler used to remove the client record from the collection. In this way, you don't need any special ceremony for graceful disconnection: everything will work, even the power off on the client machine. This is my idea: graceful connections is the invention in vane: some disconnection will be non-graceful anyway (all kind of network failures), so why bothering?
Some extra information: for threads I recommend to use a thread wrapper; the sample code and explanation you will find here:
How to pass ref parameter to the thread[
^].
You may want to have additional thread(s) for "busyness logic" and inter-thread communication; consider blocking queue and inter-thread invocation I offer in my Tips/Tricks article:
Simple Blocking Queue for Thread Communication and Inter-thread Invocation[
^].
—SA