none
How operate more than one Socket in ListViewItem? RRS feed

  • Question

  • I have 2 Sockets in server and client respectively (where client connect to server), on server i have one class that contains some informations of each client like IP, Socket and ID.

    For each connection accepted i add the last client connected to this List, this way i have a list (that is a object) of clients. And also each client is added in one ListView where is assigned the object referent to this client, on Tag property of ListViewItem. This is one of way that can help later if you want send a message to a specific client.

    Like you can see this List of clients is referent only to 1 Socket that listen in a determinated port number. Now already that i have 2 Sockets in each side (server and client), how i can make if i want send a message from second Socket of server to the second Socket on client? for example, is possible assign more than 1 object to Tag property of ListViewItem (only a idea)?

    Below is my code (server, compatible only with 1 Socket and 1 object (List of client)):

    Form:

    namespace mainForm
    {
    
    public partial class frmMain : Form
    {
    Listener server;
    Thread start;
    
    public frmMain()
    {
    	InitializeComponent();
    	server = new Listener();
    }
    
    private void startToolStripMenuItem_Click(object sender, EventArgs e)
    {
    	start = new Thread(listen);
    	start.Start();
    }
    
    private void listen()
    {
    	server.BeginListen(101); // Select a port to listen
    	server.Received += new Listener.ReceivedEventHandler(server_Received);
    	server.Disconnected += new Listener.DisconnectedEventHandler(server_Disconnected);
    }
    
    private void server_Disconnected(Listener l, Info i)
    {
    	Invoke(new _Remove(Remove), i);
    }
    
    private void server_Received(Listener l, Info i, string received)
    {
    
    	string[] cmd = received.Split('|');
    	int inc;
    
    	for (inc = 0; inc < cmd.Length; inc++)
    	{
    
    		switch (cmd[inc].ToString())
    		{
    			case "CMD1":
    				
    				Invoke(new _Add(Add), i, cmd[2] + " - " + cmd[3]);
    
    				break;
    
    			case "CMD2":
    				 
    				 // Other code here
    
    				break;
    				
    		}
    	}
    }
    
    private delegate void _Add(Info i, string computer);
    	private void Add(Info i, string computer)
    	{
    		string[] splitIP = i.RemoteAddress.Split(':');
    		
    		ListViewItem item = new ListViewItem();
    		item.Text = i.ID.ToString();
    		item.SubItems.Add(splitIP[0]);
    		item.SubItems.Add(computer);
    		item.Tag = i;
    	   
    		lvConnections.Items.Add(item);
    
    	}
    
    private delegate void _Remove(Info i);
    	private void Remove(Info i)
    	{
    		foreach (ListViewItem item in lvConnections.Items)
    		{
    			if ((Info)item.Tag == i)
    			{
    				item.Remove();
    				break;
    			}
    		}
    	}
    
    private void disconnectToolStripMenuItem_Click(object sender, EventArgs e)
    {
    	foreach (ListViewItem item in lvConnections.SelectedItems)
    	{
    		Info client = (Info)item.Tag;
    		client.Send("DISCONNECT" + Environment.NewLine);
    	}
    }

    Listener:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Net;
    using System.Net.Sockets;
    
    class Listener
    {
        Socket s;
        public List<Info> clients;
    
        public delegate void ReceivedEventHandler(Listener l, Info i, string received);
        public event ReceivedEventHandler Received;
        public delegate void DisconnectedEventHandler(Listener l, Info i);
        public event DisconnectedEventHandler Disconnected;
    
        bool listening = false;
    
        public Listener()
        {
            clients = new List<Info>();
            s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        }
    
        public bool Running
        {
            get { return listening; }
        }
    
        public void BeginListen(int port)
        {
            s.Bind(new IPEndPoint(IPAddress.Any, port));
            s.Listen(100);
            s.BeginAccept(new AsyncCallback(AcceptCallback), s);
            listening = true;
        }
    
        public void StopListen()
        {
            if (listening == true)
            {
                s.Close();
                listening = false;
            }
        }
    
        void AcceptCallback(IAsyncResult ar)
        {
            Socket handler = (Socket)ar.AsyncState;
            Socket sock = handler.EndAccept(ar);
            Info i = new Info(sock);
            clients.Add(i);
    
            sock.BeginReceive(i.buffer, 0, i.buffer.Length, SocketFlags.None, new AsyncCallback(ReadCallback), i);
            handler.BeginAccept(new AsyncCallback(AcceptCallback), handler);
        }
    
        void ReadCallback(IAsyncResult ar)
        {
            Info i = (Info)ar.AsyncState;
            try
            {
                int rec = i.sock.EndReceive(ar);
                if (rec != 0)
                {
                    string data = Encoding.UTF8.GetString(i.buffer, 0, rec);
                    Received(this, i, data);
                }
                else
                {
                    Disconnected(this, i);
                    return;
                }
    
                i.sock.BeginReceive(i.buffer, 0, i.buffer.Length, SocketFlags.None, new AsyncCallback(ReadCallback), i);
            }
            catch (Exception ex)
            {
                Disconnected(this, i);
                i.sock.Close();
                clients.Remove(i);
            }
        }
    }
    

    Info:

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Net;
    using System.Net.Sockets;
    using System.Threading;
    
    public class Info
    {
        public Socket sock;
        public Guid ID;
        public string RemoteAddress;
        public byte[] buffer = new byte[8192];
        
        public Info(Socket sock)
        {
            this.sock = sock; 
            ID = Guid.NewGuid(); 
            RemoteAddress = sock.RemoteEndPoint.ToString(); 
        }
    
        public void Send(string data)
        {
            byte[] buffer = Encoding.UTF8.GetBytes(data);
            sock.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback((ar) =>
            {
                sock.EndSend(ar);
            }), buffer);
        }
    }

    Tuesday, November 6, 2018 4:30 AM

All replies

  • Seems that is a question without solution :D
    Tuesday, November 6, 2018 11:29 AM
  • You can assign a single object to the Tag property. You can use a custom type to collect any amount of data and store it in the Tag if you need that. But that is what you're already doing. You're storing your Info object in the Tag.

    I don't understand what problem you're having. It looks like you create a TCP server and then starting to listen. As clients connect you create an instance of your Info class with the socket and other information and then add an item to the ListView you have with the Tag property set to the item. From that point on you can get back to the socket via the Tag property. At that point you could send/receive data for each item in your list.

    Some of your code seems wrong to me though.

    1) You do not want to start listening in the constructor of your form. That is called before the UI is shown, if ever. It is also called when loaded in the designer. You should defer all initialization of this until your OnLoad method. 

    2) In your start click handler you actually start listening by creating a new thread but there are problems with this approach. Firstly what happens if they click it again, you're going to spawn another thread. Secondly you are hooking up the events for the listener when the menu item is clicked but that should only happen at once so it should be part of the initialization of the listener. Thirdly you don't really need to spawn another thread, just use BeginListen or equivalent. There isn't anything wrong with threading it just seems like extra code here.

    3) You're hard coding a port which is probably not a good idea. Make this configurable.

    4) Your receive method is expecting the message to already be sent. Generally the receive handler should be called and it is responsible for handling the reading of the message. This will allow the back and forth of socket communication.

    5) Because of the last point your switch statement doesn't make sense to me. The command determines whether you add it to the list or not? It seems like on connect you'd always add it and then the receive/send code would handle the deciphering of the messages.

    6) Keep in mind that a message may take multiple reads. You're using EndRead so I'd have to look at the docs to see how it handles fragmented messages.

    Overall it seems like your code should handle multiple clients connecting. You can send responses from each client by getting the currently selected item from the list, casting its Tag to your Info and then using that to send the response.


    Michael Taylor http://www.michaeltaylorp3.net

    • Marked as answer by FLASHCODER Thursday, November 8, 2018 9:52 PM
    • Unmarked as answer by FLASHCODER Friday, November 9, 2018 9:24 PM
    Tuesday, November 6, 2018 3:02 PM
    Moderator
  • CoolDadTx, thank you by answer.

    Like you saw, the code above is able to send a command to specific client (ListViewItem) using only 1 Socket.

    private void disconnectToolStripMenuItem_Click(object sender, EventArgs e)
    {
    	foreach (ListViewItem item in lvConnections.SelectedItems)
    	{
    		Info client = (Info)item.Tag;
    		client.Send("DISCONNECT" + Environment.NewLine);
    	}
    }

    My goal is know how make to this same task using a second Socket? in others words, how i can send a message to specific client of ListView using a second Socket from server, where this message will arrive on second Socket already present on client side?

    The ListView must support send mesage to specific client, using any Socket (between 2) and not using only 1 Socket like already is made.

    I hope that you had understood :D


    • Edited by FLASHCODER Tuesday, November 6, 2018 3:58 PM
    Tuesday, November 6, 2018 3:20 PM
  • If I understand you correctly it should just work. You're using the term socket here but you can really say socket and client interchangeably. Your server is using a Socket but each time a client connects you get a new socket for just that client. Hence your Info class is getting the (client specific) socket. If another client connects then that Info class will get its own socket. Hence you'll have 2 sockets plus the server socket. This should just be working. Are you running into issues with the second client connecting?

    To send a message I'd implement a context menu on the list view. When the "send" option is selected get the currently selected ListViewItem. Then cast the Tag to your info object. Then call Send on that instance and the message should only go to that client. Again, that should just be working. I haven't tried to run your code but it seems like it is correct in this regard. If there is an issue then I'll have to debug your code to figure out what is going on.

    To simplify some of the handling I'm going to recommend you switch from Socket (which is very low level) and instead use TcpListener for the server and TcpClient for the client side. These wrapper classes handle some of the low level details that you would normally have to deal with for Socket. Besides that I think it'll help make your code more clear as to what "client" and "server" sockets you have. I can set up a simple example but it'll be a while. MSDN has a solid example you could look at though.


    Michael Taylor http://www.michaeltaylorp3.net

    Tuesday, November 6, 2018 4:12 PM
    Moderator
  • If I understand you correctly it should just work. You're using the term socket here but you can really say socket and client interchangeably. Your server is using a Socket but each time a client connects you get a new socket for just that client. Hence your Info class is getting the (client specific) socket. If another client connects then that Info class will get its own socket. Hence you'll have 2 sockets plus the server socket. This should just be working. Are you running into issues with the second client connecting?

    To send a message I'd implement a context menu on the list view. When the "send" option is selected get the currently selected ListViewItem. Then cast the Tag to your info object. Then call Send on that instance and the message should only go to that client. Again, that should just be working. I haven't tried to run your code but it seems like it is correct in this regard. If there is an issue then I'll have to debug your code to figure out what is going on.

    To simplify some of the handling I'm going to recommend you switch from Socket (which is very low level) and instead use TcpListener for the server and TcpClient for the client side. These wrapper classes handle some of the low level details that you would normally have to deal with for Socket. Besides that I think it'll help make your code more clear as to what "client" and "server" sockets you have. I can set up a simple example but it'll be a while. MSDN has a solid example you could look at though.


    Michael Taylor http://www.michaeltaylorp3.net

    CoolDadTx, answering to first part of your answer:

    - The "only 1 Socket" that i'm saying is like this =>

    IPHostEntry ipHostInfo = Dns.Resolve(Dns.GetHostName());
    IPAddress ipAddress = ipHostInfo.AddressList[0];
    IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 100);
    
    Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    
    listener.Bind(localEndPoint);
    listener.Listen(100);



    • Edited by FLASHCODER Tuesday, November 6, 2018 4:29 PM
    Tuesday, November 6, 2018 4:26 PM
  • In general your app will only have 1 listener. There can be only 1 listener per IP address/port on a machine. So your code is setting up a dedicated listener that other clients can connect to via IP/port. Each client will then get its own socket when it connects.

    Are you asking how to host multiple listeners on different ports from the same application?


    Michael Taylor http://www.michaeltaylorp3.net

    Tuesday, November 6, 2018 6:40 PM
    Moderator
  • In general your app will only have 1 listener. There can be only 1 listener per IP address/port on a machine. So your code is setting up a dedicated listener that other clients can connect to via IP/port. Each client will then get its own socket when it connects.

    Are you asking how to host multiple listeners on different ports from the same application?


    Michael Taylor http://www.michaeltaylorp3.net

    I have 2 Socket listener on server and 2 Socket connectors on client.

    Between these 2 Socket (in each side respectivelly) already exists the comunication between 1 of them (in each side client=>server), where actually is the Socket responsible by send/receive (client => server) commands by strings.

    The others 2 Socket (1 in each side, client=>server), will be responsible to send/receive (client=>server) a binary data that in my case are screenshots in realtime (like Team View software makes). In this moment i have these Socket  responsible by screenshots working, happens that i'm still sending the command requesting the screenshot using the Socket that works with commands of strings (mentioned above) because i still not found a way of use the 2nd Socket (responsible by screenshots) like is used the 1st to each selected client on ListView.

    To better understand i made this fast ilustration:

    Here is what code above makes =>

     

    And here is like i wants make (also work with Socket 02 in a selected client) =>







    • Edited by FLASHCODER Tuesday, November 6, 2018 11:55 PM
    Tuesday, November 6, 2018 11:47 PM
  • I believe I understand now. So you need 2 connections to each client in order to allow commands to be send separately from screenshots. I assume the clients first connect via the "command" socket and then the "image" socket connection occurs later.  I think the implementation is going to depend quite a bit on how you want to do it but an approach that I might consider is as follows.

    Here's some assumptions I'm going to make.

    1) The server needs to track the "client" that is connected including the endpoint, command socket and image socket. This will allow the UI on the server to show the list of "clients" and you could optionally disconnect a client by disposing of the "client".

    2) The server already has a way of specifying the 2 ports you'll need (one for each socket) and the clients have access to this information somehow.

    3) You can have only one connection per client machine. This makes the remote endpoint the unique identifier of the machine. If you need to support multiple "clients" from the same machine then your initialization logic will need to require some sort of unique identifier from the client so you can distinguish multiple instances from the same machine.

    4) The very first message received on the command socket is an init message that the client uses to provide basic information to the server including stuff like unique ID as mentioned in #3.

    5) The command socket is going to follow a request/response pattern where the client sends a request and then waits for a response. This helps the client and server detect failures. The image socket may also go this route but it depends if it is one way or not.

    Have a Client type that represents a connected client. Right now I believe you call it Info but I'm going to rename it here for clarity. The Client has the connected machine information (the init data) that you need including the remote endpoint, the command socket and the image socket. The Client instance is created when the command socket is first created by the client and it is stored in a (concurrency safe) list of connected clients. It is this list that the UI will ultimately bind to.

    When the server starts up it creates 2 TcpListener instances (one on each configured port). Then they both start listening for connections. That code is basically identical to your existing startup logic but you'll do it twice with a different port in each case. However the receive handler for the command socket will use a method that handles commands while the image socket will use a method for handling images.

    Here's the general flow. All steps assume no errors are returned.

    1) The "client" app creates a TcpClient and connects to the command port.

    2) The client app sends an "init" message with basic information about the client. At a minimum I would send the client version so you can expand the options later but you should add any additional data that makes sense. The unique ID as mentioned earlier would be part of this data if needed. Use a struct so you can expand the data over time.

    3) The server accepts the initial connect attempt (on a secondary thread as your code is already doing) and then waits for the init message. When the server gets the init message it creates an instance of the "Client" object, associates the data and command socket with the instance and then adds it to the list of connected clients. The server responds with basic server information that the client can use to configure its behavior. Like the init this should be a struct and at a minimum would include the server version.

    4) The server then begins processing commands as normal on the secondary thread.

    5) With the "command" communication set up the client app now connects to the "image" socket. Again it sends an "init" message with the necessary information to identify the client. This information needs to be enough for the server to find this client's "command" socket. You may opt to have the server, in the init response, return a unique ID (like your GUID) that this init request will use.

    6) The server's image listener accepts the connection request and enters its processing thread for the image socket (on a background thread as with the command socket). The server thread is waiting for the "init" message on the image socket. When it is received it contains the information needed to find the Client instance. The server gets the Client instance from its list of connected clients and associates the image socket with it.

    7) The server starts processing requests from the image socket thread as normal.

    During all this, the UI should be "connected" to the list of clients. Depends upon the UI framework you're using but I would personally recommend that your server just have a global instance of a ConnectionManager class you create that holds the list of clients. Whenever a client connects (or disconnects) have the class raise an event or otherwise allow the UI to get notified. You were already handling the Invoke logic in your original code so the UI will still need that. Whenever the UI gets notified about a new client it adds it to its ListView and sets the Tag property to the Client instance. 

    To "interact" with the client from the UI you can get the selected item's Tag property and cast to Client. You can then send a message or do whatever you need to do on either socket of the connected client. Do note however that duplex communication on a socket can be tricky so the client and server have to agree when each side can send/receive.

    That is the long winded answer. The simple answer to your original question of associating multiple sockets to a single ListViewItem is to use your custom Client type (Info as you called it) which has both sockets contained in it.


    Michael Taylor http://www.michaeltaylorp3.net

    • Marked as answer by FLASHCODER Thursday, November 8, 2018 12:21 PM
    • Unmarked as answer by FLASHCODER Friday, November 9, 2018 9:24 PM
    Wednesday, November 7, 2018 2:01 AM
    Moderator
  • I believe I understand now. So you need 2 connections to each client in order to allow commands to be send separately from screenshots. I assume the clients first connect via the "command" socket and then the "image" socket connection occurs later.  

    Michael Taylor http://www.michaeltaylorp3.net

    Yes, you finally understood! :D Thank you by this long answer.

    Like showed on ilustration above, i want use these "Socket Screenshot" (on client and server respectivelly) to send the command (from server) requesting the screenshot and also to send the Bitmap to server (on case of client).

    In others words, these "Socket Screenshot" will be used only to things referent to screenshots (binary data), and these "Socket Command" will be used only to commands (messages in text).

    Here is the code (server) referent to "Socket Screenshot" to receive the screenshots that i already using (probably still not is multiconnections but is working fine) =>

    private Bitmap _buffer;
    
    private Thread startRecvScreen;
    private Socket socket_Screen;
    
    private bool listening_Screen = false;
    
    private AutoResetEvent ready = new AutoResetEvent(true);
    
    private class StateObject
    {
    	public Socket workSocket = null;
    	public const int BufferSize = 1024 * 10;
    	public byte[] buffer = new byte[BufferSize];
    }
    
    private void startToolStripMenuItem_Click(object sender, EventArgs e)
    {
    	startRecvScreen = new Thread(Screenlisten);
    	startRecvScreen.Start();
    }
    
    private void Screenlisten()
    {
    
    	IPHostEntry ipHostInfo = Dns.Resolve(Dns.GetHostName());
    	IPAddress ipAddress = ipHostInfo.AddressList[0];
    	IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 100);
    
    	Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    
    	try
    	{
    		listener.Bind(localEndPoint);
    		listener.Listen(100);
    
    		while (true)
    		{
    			listener.BeginAccept(new AsyncCallback(AcceptCallback), listener);
    			ready.WaitOne();
    			socket_Screen = listener;
    			listening_Screen = true;
    		}
    	}
    	catch (Exception e)
    	{
    		MessageBox.Show(e.ToString());
    	}
    
    }
    
    private void AcceptCallback(IAsyncResult ar)
    {
    	ready.Set();
    
    	Socket listener = (Socket)ar.AsyncState;
    	Socket handler = listener.EndAccept(ar);
    
    	StateObject state = new StateObject();
    	state.workSocket = handler;
    
    	Task.Run(() => { while (true) RecvScreenshot(state.workSocket); });
    
    	listener.BeginAccept(new AsyncCallback(AcceptCallback), listener);
    }
    
    private void ScreenStopListen()
    {
    	if (listening_Screen == true)
    	{
    		socket_Screen.Close();
    		listening_Screen = false;
    	}
    }
    
    private void RecvScreenshot(Socket s)
    {
    
    	var lengthData = new byte[4];
    	var lengthBytesRead = 0;
    
    	while (lengthBytesRead < lengthData.Length)
    	{
    		var read = s.Receive(lengthData, lengthBytesRead, lengthData.Length - lengthBytesRead, SocketFlags.None);
    		if (read == 0) return;
    		lengthBytesRead += read;
    	}
    
    	if (BitConverter.IsLittleEndian)
    		Array.Reverse(lengthData);
    
    	var length = BitConverter.ToInt32(lengthData, 0);
    
    	if (length > 0)
    	{
    		var imageData = new byte[length];
    		var imageBytesRead = 0;
    
    		while (imageBytesRead < imageData.Length)
    		{
    			var read = s.Receive(imageData, imageBytesRead, imageData.Length - imageBytesRead, SocketFlags.None);
    			if (read == 0) return;
    			imageBytesRead += read;
    		}
    
    		using (var stream = new MemoryStream(imageData))
    		{
    			MemoryStream msInner = new MemoryStream();
    
    			stream.Seek(2, SeekOrigin.Begin);
    
    			using (DeflateStream z = new DeflateStream(stream, CompressionMode.Decompress))
    			{
    				z.CopyTo(msInner);
    			}
    
    			var bitmap = new Bitmap(msInner);
    			Invoke(new ImageCompleteDelegate(ImageComplete), new object[] { bitmap });
    		}
    	}
    }
    
    private delegate void ImageCompleteDelegate(Bitmap bitmap);
    private void ImageComplete(Bitmap bitmap)
    {
    	if (_buffer != null)
    	{
    		_buffer.Dispose();
    	}
    	_buffer = new Bitmap(bitmap);
    	pictureBox1.Size = _buffer.Size;
    	pictureBox1.Invalidate();
    }
    
    private void pictureBox1_Paint(object sender, PaintEventArgs e)
    {
    	if (_buffer == null) return;
    	e.Graphics.DrawImage(_buffer, 0, 0);
    }





    • Edited by FLASHCODER Wednesday, November 7, 2018 3:05 AM
    Wednesday, November 7, 2018 2:50 AM
  • Ok that looks fine. You just need to have that server listen logic added to your existing listen logic you posted earlier. The remainder of the code seems like it should just work. Since you're using 2 different listeners then each will get its own callbacks called so your code should be separated. To combine the 2 clients for your ListView you should put your Info type and your StateObject into a single type (Client is what I called it). Then that becomes the object your ListView will store in the Tag property.

    You might also consider moving the command/image handling logic into helper classes (e.g. CommandClient and ImageClient) that accept the Socket as a parameter to the constructor. This would move it out of your UI code. The Client type could then contain a public property for both these helper classes if you need access to them outside the Client. For the ImageClient I would recommend that it has a GetImage method (or similar). Every time it gets a new image from the socket it replaces the existing image with the new one and raises an event. The UI hooks into this event and each time it is raised it calls the method to get the image. Then the UI handler is responsible for updating its PictureBox with the new image. This would separate the image processing from the UI.


    Michael Taylor http://www.michaeltaylorp3.net

    Wednesday, November 7, 2018 3:36 AM
    Moderator
  • Ok that looks fine. You just need to have that server listen logic added to your existing listen logic you posted earlier. The remainder of the code seems like it should just work. Since you're using 2 different listeners then each will get its own callbacks called so your code should be separated. To combine the 2 clients for your ListView you should put your Info type and your StateObject into a single type (Client is what I called it). Then that becomes the object your ListView will store in the Tag property.

    You might also consider moving the command/image handling logic into helper classes (e.g. CommandClient and ImageClient) that accept the Socket as a parameter to the constructor. This would move it out of your UI code. The Client type could then contain a public property for both these helper classes if you need access to them outside the Client. For the ImageClient I would recommend that it has a GetImage method (or similar). Every time it gets a new image from the socket it replaces the existing image with the new one and raises an event. The UI hooks into this event and each time it is raised it calls the method to get the image. Then the UI handler is responsible for updating its PictureBox with the new image. This would separate the image processing from the UI.


    Michael Taylor http://www.michaeltaylorp3.net

    @CoolDadTx, is possible, could you give a code example about this to better understand please?

    Wednesday, November 7, 2018 1:05 PM
  • I can put something together but it'll take a little while. It requires a reasonable amount of code.

    Michael Taylor http://www.michaeltaylorp3.net

    Wednesday, November 7, 2018 2:48 PM
    Moderator
  • I can put something together but it'll take a little while. It requires a reasonable amount of code.

    Michael Taylor http://www.michaeltaylorp3.net

    OK, i will wait.
    Wednesday, November 7, 2018 10:15 PM
  • Here's something along the lines I'm thinking. Firstly you move the TCP stuff to a standalone class.

    //This contains some of the logic from your original form and Listener classes
    public class TcpServer
    {
       //Configuration of port info should be done in advance
       public void Start ()
       {
          //Start a listener on each port
          _commandListener = new TcpListener(IPAddress.Any, 65000);
          _imageListener = new TcpListener(IPAddress.Any, 65001);
    
          //Start listening on ports like you originally had...
       }
    
       public void Stop ()
       {
          //Implement logic to terminate listeners
       }
    
       //Clients are accessible from here
       public IEnumerable<Info> Clients 
       {
          get { return _clients; }
       }
    
       //Provide an event that interested parties could listen for - or expose ObservableCollection<Client>
       public event EventHandler<ClientEventArgs> ClientAdded;
    
       //This method would be called to handle connects on command port
       private void OnAcceptCommand ()
       {
           //Use the code you already had for accepting a connection from a client...
    
           //Create an instance to manage this client
           var client = new CommandClient(commandSocket);
    
           //Do whatever logic is necessary to init with the client but ultimately you'll need some unique ID that the client can use later...
    
           //Client is now connected so add to the list
           lock(_clients)
           {
              var info = new Info() { Id = …, CommandClient = client };
              _clients.Add(info);
           };
    
           //Notify
           ClientAdded?.Invoke(this, new ClientEventArgs(info));
    
           //Continue listening for new accepts
       }
    
       private void OnAcceptImage ()
       {
          //Use the code you already had but for accepting
          //connections on image port...
    
          //Find the existing client by getting an "init" message that has the ID that the command client used
          var info = FindClientById(…);
          if (info == null)
             //No command connection, maybe fail request??
    
          //Create wrapper to manage the image stuff
          var imageClient = new ImageClient(imageSocket);
    
          //Add that to the info as well
          info.ImageClient = imageClient;
    
          //Continue listening for more connections
       }
    
       private TcpListener _commandListener;
       private TcpListener _imageListener;
       private readonly List<Info> _clients = new List<Info>();
    }

    There is no error handling here, it won't even compile but this is the gist of what I was talking about. The CommandClient and ImageClient types are just wrappers around the sockets so you can move the message handling logic out of your form and/or TcpServer into helper classes.

    //Same for ImageClient
    public class CommandClient 
    {
       public CommandClient ( Socket socket )
       {
          _socket = socket;
       }
    
       //Logic to sned/receive messages for commands goes here
       //...
    
    
       private readonly Socket _socket;
    }

    Your Info class is updated to store an instance of each of these instead of the original Socket. It is really your client and is just an aggregate of the sockets and any additional info you need.

    public class Info
    {
       public Info ( Guid id, CommandClient client )
       {
          Id = id;
    
          _commandClient = client;
       }
    
       public Guid Id { get; private set; }
    
       //Don't make these public if you don't have to...
       public CommandClient CommandClient { get; set; }
    
       public ImageClient ImageClient { get; set; }
    }
    

    Your form then just reduces to creating an instance of TcpServer and initializing it with port information. When your "start" item is clicked you tell the server to start listening. The server has an event (you'll need to add more) to notify the UI when a client connects. That call will occur on a non-UI thread but it gives you access to the client information.

    //ClientEventArgs derives from EventArgs and just has a Client property of type Info to provide access to the Info item
    void OnClientAdded ( object sender, ClientEventArgs e )
    {
       //Will need to use Invoke here in most cases
       //if (this.InvokeRequired) this.Invoke(...);
    
       var client = e.Client;
    
       //Create a new LVI for it
       var item = new ListViewItem() {
          Text = client.Id.ToString(),
          Tag = client
       };
       ...
    
       lvConnections.Items.Add(client);
    }
    
    //To get the "client" from the currently selected item
    var client = lvConnections.SelectedItem?.Tag as Info;



    Michael Taylor http://www.michaeltaylorp3.net

    Thursday, November 8, 2018 4:32 AM
    Moderator
  • Did you get this figured out?

    Michael Taylor http://www.michaeltaylorp3.net

    Saturday, November 10, 2018 4:22 PM
    Moderator