[.net] Socket arraylist empty index question

Started by
10 comments, last by furin591 15 years, 8 months ago
Hopefully this is a quick and simply thing that I'm just overlooking the obvious answer on. My problem is that I have an arraylist that stores sockets for a tcp server. This is all good and no issues. But, I don't want to keep adding new sockets to the end. What I want to do is traverse the arraylist to find the first empty index. How can I do this? I was thinking of setting that index value to 'null' but really don't want to mix 'null' and socket objects in the same array list in case that causes errors trying to sort or search. Is there a way to check each index to see if it contains a socket object? And this has to be quick, very quick. If it takes 2 seconds per index to figure it out, hell even 1 sec is way to long, then I will have to find a new solution. Any thoughts or ideas?
Advertisement
Here's a little snippit of code that I haven't tested yet so don't know if it works or not. Thoughts?

        private int GetSocketListIndex()        {            for (int i = 0; i < m_SocketList.Capacity; i++)            {                if (m_SocketList.GetType() != System.Net.Sockets.Socket)                {                    return i;                }            }            // if here return -1 since the array list is full            return -1;        }
Well that snippit didn't work. Get this error:

Error 1 'System.Net.Sockets.Socket' is a 'type', which is not valid in the given context
well this gets rid of the error. Still haven't tested yet (need a lot more code done before I can test) so still looking for any suggestions.

        private int GetSocketListIndex()        {            for (int i = 0; i < m_SocketList.Capacity; i++)            {                if (m_SocketList.GetType() != typeof(System.Net.Sockets.Socket))                {                    return i;                }            }            // if here return -1 since the array list is full            return -1;        }
I think a LinkedList may be more appropriate to your needs, as you can insert and remove elements in constant (O(1)) time.

[Website] [+++ Divide By Cucumber Error. Please Reinstall Universe And Reboot +++]

I had given some thought to using a linked list but there was a problem with that idea. This was a week or so ago and I can not remember exactly what the issue was. I'll look back into it to see if I can remember what the issue was and I'll post here if I remember. Thanks for the suggestion though.

Here is the entire class for this feature in case anyone has more suggestions and / or needs / wants to see the code:

// ConnectionManager.cs class file// ConnectionManager class keeps track of all connections and their states// Each connection should contain one(1) socket// Should have one(1) socket that acts as the main connection that passes the new stream off to a new socket which handles the work// After the new socket has been created and passed the connection, the main socket needs to be set back to listening for connectionsusing System;using System.Collections.Generic;using System.Text;namespace Software_Configuration_Server{    class ConnectionManager    {        private System.Collections.ArrayList m_SocketList = new System.Collections.ArrayList(); // array list to hold our sockets        private System.Net.Sockets.Socket m_MainListeningSocket; // main socket that listens for new connections        private System.Net.IPHostEntry m_HostInfo; // iphost info to use for listener        private System.Net.IPAddress m_IPAddress; // ip address         private System.Net.IPEndPoint m_IPEndPoint; // end point for the main listening socket        // properties and accessor methods        public System.Collections.ArrayList SocketList        {            get { return m_SocketList; }            set { m_SocketList = value; }        }        // get first avail spot in array list        private int GetSocketListIndex()        {            // loop through the socket array list and check each item to see if it's a socket or not            for (int i = 0; i < m_SocketList.Capacity; i++)            {                if (m_SocketList.GetType() != typeof(System.Net.Sockets.Socket))                {                    // if no socket at the current index, return i                    return i;                }            }            // if here return -1 since the array list is full            return -1;        }        // callback for client connection        public void OnClientConnect(IAsyncResult asyn)        {            int InsertIndex = GetSocketListIndex();                        if (InsertIndex == -1)            {                // create new socket manager object to handle this socket connection                SocketManager m_ActiveSocket = new SocketManager(m_SocketList.Count, m_MainListeningSocket.EndAccept(asyn));                // add SM to array list                m_SocketList.Insert(m_SocketList.Count, m_ActiveSocket);            }            else            {                // create new socket manager object to handle this socket connection                SocketManager m_ActiveSocket = new SocketManager(InsertIndex, m_MainListeningSocket.EndAccept(asyn));                // add SM to array list                m_SocketList.Insert(InsertIndex, m_ActiveSocket);            }                        // finally, start waiting for connection again            m_MainListeningSocket.BeginAccept(OnClientConnect, null);        }        // constructor        public ConnectionManager()        {            m_HostInfo = System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName()); // iphost info to use for listener            m_IPAddress = m_HostInfo.AddressList[0]; // get the ip address from the iphost info            m_IPEndPoint = new System.Net.IPEndPoint(m_IPAddress, 10000); // end point for the main listening socket            m_MainListeningSocket = new System.Net.Sockets.Socket(System.Net.Sockets.AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp); // init the socket            // now bind the listening socket to the local endpoint so that we can listen            m_MainListeningSocket.Bind(m_IPEndPoint);            // now start to listen            m_MainListeningSocket.Listen(512);            // accept connections            m_MainListeningSocket.BeginAccept(OnClientConnect, null);        } // end constructor        // destructor        ~ConnectionManager()        {        } // end destructor    } // end ConnectionManager class}


here is the socket manager class.. just used to keep track of specific sockets

// SocketManager.cs class file// SocketManager class keeps track of the socketusing System;using System.Collections.Generic;using System.Text;namespace Software_Configuration_Server{    class SocketManager    {        private int m_SocketNumber; // current socket number        private System.Net.Sockets.Socket m_Socket; // current socket        private byte[] m_DataReceivedBuffer = new byte[4096]; // buffer to hold incomming data        // properties and accessor methods        public int SocketNumber        {            get { return m_SocketNumber; }            //set { m_SocketNumber = value; } // socket number should only be set by the constructor. uncomment only for testing purposes        }        public System.Net.Sockets.Socket SocketRef        {            get { return m_Socket; }            //set { m_Socket = value; } // socket reference should only be set by the constructor. uncomment only for testing purposes        }        public byte[] DataReceivedBuffer        {            get { return m_DataReceivedBuffer; }            set { m_DataReceivedBuffer = value; }        }        // end properties        // constructor        public SocketManager(int socketNumber, System.Net.Sockets.Socket currentSocket)        {            m_SocketNumber = socketNumber;            m_Socket = currentSocket;        }// end constructor    }// end SocketManager class}


and don't worry about the lack of error checking. That will all be added in due time. :)
I think that you aren't really getting the concept behind the ArrayList, or .Net data structures in general, your problem is a non-issue, as these classes are designed to handle insertion, removal, etc.
ArrayLists don't work as as object[], and they don't hace "empty indexes" as you call them, they don't contain null values unless you explicitly add or insert them. When you call ArrayList.Remove(), the element is removed and the gap is automatically closed.

A little problem with your solution is that you add SocketManagers to the ArrayList, but check if the elements are Sockets, so GetSocketListIndex always returns -1.

As benryves mentioned, you should really use a LinkedList for what you want.

The code should look something like this (of course, if you want to save SocketManagers instead of Sockets, you should make some changes
// ConnectionManager.cs class fileusing System;using System.Collections.Generic;using System.Text;using System.Net.Sockets;using System.Net;namespace Software_Configuration_Server{    class ConnectionManager    {        private LinkedList<Socket> m_SocketList = new LinkedList<Socket>(); // array list to hold our sockets        private Socket m_MainListeningSocket; // main socket that listens for new connections        private IPHostEntry m_HostInfo; // iphost info to use for listener        private IPAddress m_IPAddress; // ip address         private IPEndPoint m_IPEndPoint; // end point for the main listening socket        // properties and accessor methods        public LinkedList<Socket> SocketList        {            get { return m_SocketList; }            set { m_SocketList = value; }        }        // callback for client connection        public void OnClientConnect(IAsyncResult asyn)        {			//Add the socket to the client to the lost of sockets			Socket activeSocket =  m_MainListeningSocket.EndAccept(asyn);			m_SocketList.Add(activeSocket);			            // finally, start waiting for connection again            m_MainListeningSocket.BeginAccept(OnClientConnect, null);        }        // constructor        public ConnectionManager()        {            m_HostInfo = System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName()); // iphost info to use for listener            m_IPAddress = m_HostInfo.AddressList[0]; // get the ip address from the iphost info            m_IPEndPoint = new System.Net.IPEndPoint(m_IPAddress, 10000); // end point for the main listening socket            m_MainListeningSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // init the socket            // now bind the listening socket to the local endpoint so that we can listen            m_MainListeningSocket.Bind(m_IPEndPoint);            // now start to listen            m_MainListeningSocket.Listen(512);            // accept connections            m_MainListeningSocket.BeginAccept(OnClientConnect, null);        } // end constructor        // destructor        ~ConnectionManager()        {        } // end destructor    } // end ConnectionManager class}
Ok, here's a question then: using a linked list, how would I find the specific node in the list that sent the data so I know where to send the response? I know I can iterate over the list to find it, but with potentially thousands of nodes and each sending data almost constantly, iterating through the list of nodes for each data transmission would slow down the system and start to backlog the data (causing what I would assume lots of data being lost, connections being dropped, and potentially crashing the software).
Assuming you're doing asynchronous requests, since you're wondering what socket to reply back to...

When you set up the asynchronous request you submitted a callback delegate and a state object, which are called and returned to you once the receive has finished. (Specifically, the IAsyncResult object will have a member named AsyncState. This can be converted back to the state object you passed to BeginReceive.) This object should hold identifying information that you then use to index into the socket collection. (This identifying information can be whatever you want it to be, including an index into a collection.)

From MSDN:
private static void Receive(Socket client) {    try {        // Create the state object.        StateObject state = new StateObject();        state.workSocket = client;        // Begin receiving the data from the remote device.        client.BeginReceive( state.buffer, 0, StateObject.BufferSize, 0,            new AsyncCallback(ReceiveCallback), state);    } catch (Exception e) {        Console.WriteLine(e.ToString());    }}

In the example above the StateObject() can be any object you designate or desire.

Upon read finishing:
private static void ReceiveCallback( IAsyncResult ar ) {    try {        // Retrieve the state object and the client socket         // from the asynchronous state object.        StateObject state = (StateObject) ar.AsyncState;        Socket client = state.workSocket;        // Read data from the remote device.        int bytesRead = client.EndReceive(ar);        if (bytesRead > 0) {            // There might be more data, so store the data received so far.            state.sb.Append(Encoding.ASCII.GetString(state.buffer,0,bytesRead));                //  Get the rest of the data.            client.BeginReceive(state.buffer,0,StateObject.BufferSize,0,                new AsyncCallback(ReceiveCallback), state);        } else {            // All the data has arrived; put it in response.            if (state.sb.Length > 1) {                response = state.sb.ToString();            }            // Signal that all bytes have been received.            receiveDone.Set();        }    } catch (Exception e) {        Console.WriteLine(e.ToString());    }}

Code was taken from the MSDN page for "Using An Asynchronous Client Socket".
..what we do will echo throughout eternity..
Ok, I took your suggestions and implemented my connections in a linked list and everything is cool. I can open several connections, keep track of where data came from and went, and keep track of who is connected. But now I've run into another problem that maybe you guys can help with.

The issue is with receiving data. Right now my data buffer is 4096 bytes so it can receive 99% of the messages that will be sent with just the one call. But, if I go over that and send something like a 5k message it 'wraps around and truncates'. Here's a small example using a buffer of 8 bytes:

first message sent: Hello
first message recd: Hello -- everything is good

first message sent: Hello World
first message recd: Hello
second message recd: Hello Wo -- see the issue?

I'm thinking I need some way to know if there is more data for that particular message. Here is the code for my data received event:
 private void DataReceivedEvent(IAsyncResult asyn)        {            // get a ref to the calling socket            SocketManager currentSM = (SocketManager)asyn.AsyncState;            try            {                // see how many bytes were read                int bytesRead = currentSM.SocketRef.EndReceive(asyn);                if (bytesRead &gt; 0)                {                    // store what we have so far                    currentSM.ReceivedDataString.Append(Encoding.ASCII.GetString(currentSM.DataReceivedBuffer, 0, bytesRead));                }                WaitForData(currentSM);                messageBox.AppendText("Message received: " + currentSM.ReceivedDataString + " || Bytes read: " + bytesRead.ToString() + " || Client GUID: " + currentSM.SocketID.ToString() + "\r\n");            }            catch (System.Net.Sockets.SocketException ex)            {                // handle the expected client disconnect (errorcode of 10054)                if (ex.ErrorCode == 10054)                {                    // client closed connection                    messageBox.AppendText("Client disconnected. Client GUID: " + currentSM.SocketID.ToString() + "\r\n");                }                else // otherwise note that something bad happened!                {                    messageBox.AppendText("Unknown error on client GUID: " + currentSM.SocketID.ToString() + "\r\n");                }            }        }

This topic is closed to new replies.

Advertisement