Click here to Skip to main content
15,885,216 members
Please Sign up or sign in to vote.
3.00/5 (1 vote)
I've been working on this for a while (much longer than anticipated) and still can't seem to get a client-server working with C#. I think the main issue is my poor understanding of sockets in C# (I could implement this in Java no problem).

Any help finding my error(s) or helping me understand how to implement it properly would be appreciated. A code sample of how you would implement any one of my functions below would be great :)

The goal is to have multiple clients connected to a single server. Each client can send data updates/changes to the server, which then relays those updates to all other clients. This should be simple, right?

The server tracks all currently connected clients in a list along with the TcpClient object acquired from the connection.

The following code snippets are mostly entry points for threads and are supposed to do the following:

Server Side:
> Accept new clients
> Receive data from client
> Send data to clients

Client Side:
> Send data to server
> Receive data from sever

C#
//Server Thread Entry Point: Waiting for client connections
        private void acceptClientConnection()
        {
            // Create a TCP/IP listener.
            TcpListener tcpListener = new TcpListener(IPAddress.Any, 3000);
            tcpListener.Start();

            //start listening for incomming connections from clients
            while (true)
            {
                //Wait for incoming client connection
                TcpClient client = tcpListener.AcceptTcpClient();

                //Save connection (launches new thread that waits for client msgs)
                clients.AddLast(new ConnClient(client, clients.Count));

                Console.WriteLine("SERVER: client connected to server");
            }
        }

No problems so far, but I'm pretty sure I have issues here: The following code is where the server handles in-coming data from the clients. (This is run in a thread; one thread per client). The changes received are saved in a queue and are processed by another thread.

I feel this code should block, and wait for a message from the client, but I don't think that is happening.

C#
//Comm Thread Entry Point
            private void handleClientComm()
            {
                while (true)
                {
                    //handle incomming messages
                    if (conn.Connected)
                    {
                        Console.WriteLine("SERVER: still connected, reading stream");
                        //Get data from stream
                        byte[] data = ReadToEnd(conn.GetStream());

                        lock (dataChanges)
                        {
                            //add data to the change list
                            dataChanges.AddLast(new Pair<int, byte[]>(clientIndex, data));

                            //wake up any threads waiting on data
                            Monitor.PulseAll(dataChanges);
                        }
                    }
                    else Console.WriteLine("SERVER: not connected");
                }
            }


conn == the saved TcpClient object.
ReadToEnd(Stream) is a function I copied from a response to someone else question on here. I can't guarantee its correctness...

The following is the code that actually sends the updates to the clients:
I believe its working as intended.

C#
//Synch Data Thread Entry Point
        private void synchData() 
        {
            while (true)
            {
                lock (dataChanges)
                {
                    //wait for data if there isn't any
                    Console.WriteLine("SERVER: waiting for data to synch");
                    while (dataChanges.Count == 0)
                        Monitor.Wait(dataChanges);

                    //send updates to all the clients
                    LinkedList<ConnClient>.Enumerator itr = clients.GetEnumerator();
                    for (int i = 0; i < clients.Count; i++ )
                    {
                        itr.MoveNext();
                        if(i == dataChanges.First.Value.index)
                            continue; // no need to send change data to the client that made the change

                        //send data to client
                        itr.Current.sendDataUpdate(dataChanges.First.Value.data);
                    }
                    dataChanges.RemoveFirst();
                    Console.WriteLine("SERVER: data change synched");
                }
            }
        }

and
C#
//send data update to client (called from a separate thread)
            public void sendDataUpdate(byte[] data)
            {
                //handle incomming messages
                if (conn.Connected)
                {
                    //send data to the client
                    conn.GetStream().Write(data, 0, data.Length);
                    conn.GetStream().Flush();
                }
            }


The client is a little simpler, one thread for sending changes, one thread for receiving updates. If there is an issue here, I believe it will be while receiving updates - for the same reasons I think the server receiving data is wrong.

C#
//make initial contact with the server
            server = new TcpClient();
            IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Parse(hostIP), hostPort); //127.0.0.1 //3000
            server.Connect(serverEndPoint);


C#
//send changes to server
        private void sendUpdates() 
        {
            while (true)
            {
                lock (dataChanges)
                {
                    //Wait until there is data to send
                    while (dataChanges.Count == 0)
                        Monitor.Wait(dataChanges);

                    //if still connected to server
                    if (server.Connected)
                    {
                        NetworkStream nStream = server.GetStream();
                        nStream.Write(dataChanges.First.Value, 0, dataChanges.First.Value.Length);
                        nStream.Flush();

                        dataChanges.RemoveFirst();
                        Console.WriteLine("CLIENT: data change sent to server");
                    }
                    else Console.WriteLine("CLIENT: no longer connected to server");
                }
            }
        }

//recieve updates from server
        private void receiveUpdates() 
        {
            while(true)
            {
                //if still connected to server
                if (server.Connected)
                {
                    //Get data from stream
                    Console.WriteLine("CLIENT: waiting for data from server");
                    byte[] data = ReadToEnd(server.GetStream());

                    //Deserialize change
                    AbstractDataChange dChange = aData.deserialize(data);
                    Console.WriteLine("CLIENT: recieved data update from the server");

                    //update synched data
                    lock (aData)
                    {
                        aData.updateData(dChange);
                    }
                }
                else Console.WriteLine("CLIENT: no longer connected to server");
            }   
        }


.
Posted

If it were me, I would let the clients push their own updates and pull other client updates on a regular basis. That makes the server nothing more than a glorified data clearinghouse. All it does is listen for client connections, and then the clients drive the comm session - not the server.
 
Share this answer
 
Comments
ProjectXC 22-Dec-11 13:29pm    
Alright, now that I have my issue resolved, your suggestion intrigues me. I may implement it and see if it runs more efficiently for my given scenario.

I don't know if I like it yet though... The clients will all be sitting there spinning, constantly asking for updates instead of just waiting to receive them.

My solution blocks the server's "send data" thread when there is no data to send.
#realJSOP 22-Dec-11 14:27pm    
The clients don't have to poll constantly (every N milliseconds), and they would only pull down data that's been updated, and maybe even only data for other clients that are "visible" to them (or within a pre-defined visibility/audible interaction zone).
Alright, my question has been solved. I removed the
ReadToEnd(conn.GetStream());

function call, and replaced it with
conn.GetStream().Read(data, 0, buffSize); 

My client-server is functioning correctly, now I just need to figure out how to send messages of arbitrary length...

This is a good example of "Don't blindly copy-paste!!" The function I used did not work as expected!
 
Share this answer
 

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900