How can I handle multiple clients connected to a server using sockets?
Asked Answered
R

2

7

I have an Android app that needs to let multiple devices connect. One device should act as the group owner and issue instructions to all of the clients to do specific things. I suppose it's comparable to a wireless handheld game where one player is the host.

I have a couple of questions so I will try to keep them concise. Even an answer to just the first would help.

First, I've successfully paired a single server and single client phone using sockets. I did this using Android's Wi-Fi Direct technology (Described here). The tutorial is helpful but unfortunately not very thorough, particularly in describing one-to-many connections. Once a peer list is found, socket connections can be opened. I was able to connect two devices using a thread for the server (Using this example), like so:

public class ServerThread implements Runnable {

        public void run() {
             (Code...)
             client = serverSocket.accept();
             (...other code here.)
        }
}

A client is created following a button press (I think, still trying to get my head around my modified code):

public class MusicClientThread implements Runnable {
     Socket socket = new Socket(serverAddr, port);
     while (connected) {
          //(Other code here.)
          while ((line = in.readLine()) != null) {
          //While there are still messages coming
          }
     }
     socket.close();
     //(Other code here too.)
}

So I suppose my first question is: How would I allow more clients to be connected? My ServerThread refers to a single client variable above so I do not see how I would allow a varying number (my application is aiming for anything from 2 to 10 users) nor do I know the correct way to differentiate between all my different clients. My only guess is that I would use the unique IP address of each phone.

My 2nd question is, once I've established a connection with multiple clients/peers, how would I then send and receive instructions to them correctly? Currently my single server awaits an instruction, and upon receiving it, issues a response instruction. I need it so that the server can send instructions from the start, using button presses, and the result of these is visible on the client devices.

I hope I have made everything clear!

Rutter answered 4/3, 2013 at 19:39 Comment(4)
For each new client that requests a socket from the serversocket, why not dispatch a new thread, with that client passed into it. That way any data that comes in from that client's inputStream is sent to the server.Knife
You must start a new thread everytime you want to execute a request from your client, that way, it does not matter if you've already sent one request, when you send another, a new thread will start and they will work in simulateneous.Concentrated
So from the groupowner devices perspective, does this mean multiple server threads are running on it at a time? How would I manipulate each one?Rutter
@Chucky you can keep a list of the running ServerThreads where you can go through them all and call a method on ServerThread.Erlindaerline
E
7

You have to start a new thread for each client, as the sockets need a thread for themselves to run (as it waits for input most of the time). This can for example be done (in this simplified manner):

public class ThreadHandler extends Thread {
      private ArrayList<ServerThread> serverThreads = new ArrayList<ServerThread>();
      public void run() {
            while (!isInterrupted()) { //You should handle interrupts in some way, so that the thread won't keep on forever if you exit the app.
                  (Code...)
                  ServerThread thread = new ServerThread(serverSocket.accept());
                  serverThreads.add(thread);
                  thread.start();
            }
      }
      public void doSomethingOnAllThreads() {
            for (ServerThread serverThread : serverThreads) {
                  serverThread.otherMethod();
            }
      }
}

Where the ServerThread looks something like this:

public class ServerThread extends Thread {
    private Socket socket;
    public ServerThread(Socket socket) {
          this.socket = socket;
    }
    public void run() {
         (...other code here.)
    }
    public void otherMethod() {
          //Signal to the thread that it needs to do something (which should then be handled in the run method)
    }
}
Erlindaerline answered 4/3, 2013 at 19:57 Comment(8)
I'm afraid I already have this code in place though (it was the code I didn't show =P). What I need is something that permits me to operate on this list of threads that people seem to be suggesting.Rutter
@Chucky check my edit - you will have to make sure otherMethod() and doSomethingOnAllThreads() are thread-safe though, for example by using locks in suitable locations or thread-safe collections.Erlindaerline
Bah, I'm getting all sorts of connection failure methods all of a sudden. I think there's some sort of socket open some where and I can't seem to close it.Rutter
Are you using the same port for all sockets?Erlindaerline
Yes. Crap. My apologies, very new to all of this, even sockets!Rutter
No need to apologize! Everyone has been a novice at some point and we're here to help.Erlindaerline
Back on this project, Pescis. Very close to accepting your answer but I need to know how to change ports for every new client. I've tried using port number 0 because I heard that it dynamically uses any available port but now this is causing me errors when the client tries to connect.Rutter
@Chucky Hmm it looks as if I was wrong, a ServerThread's port is only the port where it listens to connections - the actual sockets are going to another port number automatically - you do not need more than one port. Sorry for the confusion. If the same failure comes back it would be helpful if you post the stack trace.Erlindaerline
I
2

I have a Google TV Box. And i have two mobile phones.

On the GoogleTV Box i run a server. The Server has one ServerSocket on port 6001. Also the Server has two sockets for two clients

The first device connects to server and using socket number one the second the other....

I can simultanous post diffrent messages from 2 devices to the google tv box socket and show them on TV.

I using the following solution:

FOR THE MOBILE CLIENT (2 devices)

create a new android project with a blank activity and copy this code into. create a layout for the client containing a edittext and a button. MAKE SURE TO SET INTERNET AND ACCESS NETWORK PERMISSIONS IN ANDROIDMANIFEST.XML!!! And edit serverIpAddress in this file to your servers accessible IP.

package de.android.googletv.gameclient;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Random;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

/**
 * 
 *  
 *
 */
public class FullscreenActivity extends Activity {

    private Socket socket;
    private String serverIpAddress = "192.168.22.253"; // GOOGLE TV IP
    private static final int PLAYER_1_SERVERPORT = 6001; 

    private Button bt;
    private TextView tv;

    String DEVICE_NAME;


    private class ConnectToServer extends AsyncTask {

        @Override
        protected Object doInBackground(Object... params) {

                System.out.println("connecting to server...");

                try {

                     InetAddress serverAddr = InetAddress.getByName(serverIpAddress);
                     socket = new Socket(serverAddr, PLAYER_1_SERVERPORT);

                  } catch (UnknownHostException e1) {
                      System.out.println("ERROR REACHING SERVER! 1");
                     e1.printStackTrace();
                  } catch (IOException e1) {
                      System.out.println("ERROR REACHING SERVER! 2");
                     e1.printStackTrace();
                  }

                System.out.println("Done!");

                return params;
         }

         protected void onPostExecute() {

         }

     }


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_fullscreen);

        DEVICE_NAME = android.os.Build.MODEL;

        Button exit = (Button) findViewById(R.id.dummy_button);
        exit.setOnClickListener(new OnClickListener() {         
            @Override
            public void onClick(View v) {
                System.exit(1);             
            }
        });

        new ConnectToServer().execute("");  

        tv = (TextView) findViewById(R.id.editText1);

        bt = (Button) findViewById(R.id.button1);
        bt.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                try {

                   Random rnd = new Random();                  

                   EditText et = (EditText) findViewById(R.id.editText1);
                   String str = DEVICE_NAME + " ID" + rnd.nextInt() + " says: " + et.getText().toString();

                   PrintWriter out = new PrintWriter(
                           new BufferedWriter(
                                   new OutputStreamWriter(
                                           socket.getOutputStream())),true
                                           );
                   out.println(str);

                   Log.d("Client", "Client sent message");

                } 
                catch (UnknownHostException e) {
                   tv.setText("UnknownHostException");
                   e.printStackTrace();
                } 
                catch (IOException e) {
                   tv.setText("IOException");
                   e.printStackTrace();
                } 
                catch (Exception e) {
                   tv.setText("Exception");
                   e.printStackTrace();
                }
             }
        });
    }

}

FOR THE SERVER (google tv box)

create a new android project with blank activity and copy this code into. create a layout with only a textfield MAKE SURE TO SET INTERNET AND ACCESS NETWORK PERMISSIONS IN ANDROIDMANIFEST.XML!!!

package de.android.googletv.gameserver;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

/**
 * 
 * 
 *
 */
public class FullscreenActivity extends Activity {


    // server socket
    ServerSocket ss_plr1 = null;
    public static final int SERVERPORT_1 = 6001;

    int nr_connections = 0;

    // socket for player1
    Socket s1;

    // socket for player2
    Socket s2;

    Thread myCommsThread = null;

    protected static final int MSG_ID = 0x1337;
    String mClientMsg = "";


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_fullscreen);

        Button exit = (Button) findViewById(R.id.dummy_button);
        exit.setOnClickListener(new OnClickListener() {         
            @Override
            public void onClick(View v) {
                System.exit(1);             
            }
        });

        TextView tv = (TextView) findViewById(R.id.fullscreen_content);
        tv.setText("Nothing from client yet");

        myCommsThread = new Thread(new CommsThread());
        myCommsThread.start();
    }


    @Override
    protected void onStop() {
        super.onStop();
        try {
            // make sure you close the socket upon exiting
            ss_plr1.close();            
        } 
        catch (IOException e) {e.printStackTrace(); }
    }

    Handler myUpdateHandler = new Handler() {
        public void handleMessage(Message msg) {

            System.out.println("handleMessage("+msg+")");

            switch (msg.what) {

            case MSG_ID:
                TextView tv = (TextView) findViewById(R.id.fullscreen_content);
                tv.setText((String)msg.obj);
                break;

            default:
                break;

            }
            super.handleMessage(msg);
        }
    };

    class CommsThread implements Runnable {

        public void run() {

            System.out.println("creating new sockets...");

            try {

                ss_plr1 = new ServerSocket(SERVERPORT_1 );

                if (s1 == null)
                    s1 = ss_plr1.accept();

                if (s2 == null)
                    s2 = ss_plr1.accept();

            } 
            catch (IOException e) {e.printStackTrace();}

            new Thread(new ConnectionHandler(s1, myUpdateHandler)).start();
            new Thread(new ConnectionHandler(s2, myUpdateHandler)).start();

        }

    }


}

... and required by the server is the connection handler for threaded messaging ...

create a additional class in server project called: "ConnectionHandler.java" and copy this code into. it handles the async connections.

package de.android.googletv.gameserver;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;

import android.os.Handler;
import android.os.Message;

public class ConnectionHandler implements Runnable {

    Socket m_socket;
    Handler m_updateHandler;

    public ConnectionHandler(Socket socket, Handler updateHandler) {
        m_socket = socket;
        m_updateHandler = updateHandler;
    }

    @Override
    public void run() {

        while (!Thread.currentThread().isInterrupted()) {

            try {

                BufferedReader input = new BufferedReader(new InputStreamReader(m_socket.getInputStream()));

                String st = null;
                st = input.readLine();

                Message m = new Message();
                m.what = 0x1337;
                m.obj = st;
                m_updateHandler.sendMessage(m);

            } 
            catch (IOException e) { e.printStackTrace();}

        }

    }

}

This is not the nicest solution. Multiple "not nice" codings. System.exit(1) for example. And it is only has two devices support. But it works for more than one device, and im pretty sure you will modify it for you purposes. Its based on three web sources and additional attemps from myself to make it work. Its only my prototype....

I CANNOT LINK TO THEM :( ... to less reputation.

If you build and run everthing it should look like this:

https://plus.google.com/u/0/109268217221135029753/posts/3iz6SF1hiJa

Insincerity answered 5/4, 2013 at 15:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.