Multithreading Socket communication Client/Server
I finished writing a Client/Server Socket communication program that works fine. Now I'm trying to figure out how to make it so that I can have multiple Client connections to the Server at once. I've looked around and there seems to be more than a couple of different ways to do this. so I've come here to ask you guys for help/suggestions.
My Server:
public class Server {
private ServerSocket serverSocket = null;
private Socket clientSocket = null;
public Server() {
try {
serverSocket = new ServerSocket(7003);
} catch (IOException e) {
System.err.println("Could not listen on port: 7003");
System.exit(1);
}
try {
clientSocket = serverSocket.accept();
} catch (IOException e) {
System.err.println("Accept failed");
System.exit(1);
}
}
public void startServer() throws IOException {
PrintWriter output = new PrintWriter(clientSocket.getOutputStream(), true);
BufferedReader input = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
String inputLine, outputLine;
outputLine = "Connected to Server";
output.println(outputLine);
while ((inputLine = input.readLine()) != null) {
// This just determines users input and server ruturns output based on that
outputLine = this.getServerOutput(inputLine);
output.println(outputLine);
if (outputLine.equals("Bye"))
break;
}
output.close();
input.close();
clientSocket.close();
serverSocket.close();
}
}
Would I need to make my constructor create threads and startServer()
or would be my run method?
You should use ExecutorService
. Your client request processing would be the run()
of a Runnable
and after each accept you can call ExecutorService.submit(runnableTask)
to asynchronously service the client.
A sample using ExecutorService.
public class MyServer {
private static MyServer server;
private ServerSocket serverSocket;
/**
* This executor service has 10 threads.
* So it means your server can process max 10 concurrent requests.
*/
private ExecutorService executorService = Executors.newFixedThreadPool(10);
public static void main(String[] args) throws IOException {
server = new MyServer();
server.runServer();
}
private void runServer() {
int serverPort = 8085;
try {
System.out.println("Starting Server");
serverSocket = new ServerSocket(serverPort);
while(true) {
System.out.println("Waiting for request");
try {
Socket s = serverSocket.accept();
System.out.println("Processing request");
executorService.submit(new ServiceRequest(s));
} catch(IOException ioe) {
System.out.println("Error accepting connection");
ioe.printStackTrace();
}
}
}catch(IOException e) {
System.out.println("Error starting Server on "+serverPort);
e.printStackTrace();
}
}
//Call the method when you want to stop your server
private void stopServer() {
//Stop the executor service.
executorService.shutdownNow();
try {
//Stop accepting requests.
serverSocket.close();
} catch (IOException e) {
System.out.println("Error in server shutdown");
e.printStackTrace();
}
System.exit(0);
}
class ServiceRequest implements Runnable {
private Socket socket;
public ServiceRequest(Socket connection) {
this.socket = connection;
}
public void run() {
//Do your logic here. You have the `socket` available to read/write data.
//Make sure to close
try {
socket.close();
}catch(IOException ioe) {
System.out.println("Error closing client connection");
}
}
}
}
how to make it so that I can have multiple Client connections to the Server at once
Right now you are starting your server and immediately waiting for a single client to connect in the constructor.
clientSocket = serverSocket.accept();
Then you handle that single socket connection inside of your startServer()
method. This means that no other clients will be handled.
public void startServer() throws IOException {
PrintWriter output = new PrintWriter(clientSocket.getOutputStream(), true);
...
Typically with a server pattern like this, you would do something like the following:
- Setup your server socket in the constructor.
- Create an
acceptClients()
method which would loop waiting for a client to be accepted. This could fork a thread to accept the clients in a thread of its own in the background. - For each client, either fork a thread to handle the connection, passing the thread the clients socket. Better would be to, as @basiljames shows, use an
ExecutorService
to manage the threads for you.
Here's some sample code:
public class Server {
private ServerSocket serverSocket = null;
public Server(int portNumber) throws IOException {
serverSocket = new ServerSocket(portNumber);
}
// this could be run in a thread in the background
public void acceptClients() throws IOException {
// create an open ended thread-pool
ExecutorService threadPool = Executors.newCachedThreadPool();
try {
while (!Thread.currentThread().isInterrupted()) {
// wait for a client to connect
Socket clientSocket = serverSocket.accept();
// create a new client handler object for that socket,
// and fork it in a background thread
threadPool.submit(new ClientHandler(clientSocket));
}
} finally {
// we _have_ to shutdown the thread-pool when we are done
threadPool.shutdown();
}
}
// if server is running in background, you stop it by killing the socket
public void stop() throws IOException {
serverSocket.close();
}
// this class handles each client connection
private static class ClientHandler implements Runnable {
private final Socket clientSocket;
public ClientHandler(Socket clientSocket) {
this.clientSocket = clientSocket;
}
public void run() {
// use the client socket to handle the client connection
...
}
}
}
Using the ExecutorService
thread-pools is recommended for just about all Thread
implementations like this. If, however, you are stuck to using raw Thread
for some reason, you can do the following instead in your acceptClients()
method:
public void acceptClients() throws IOException {
while (!Thread.currentThread().isInterrupted()) {
// wait for a client to connect
Socket clientSocket = serverSocket.accept();
// fork a background client thread
new Thread(new ClientHandler(clientSocket)).start();
}
}
Change this: public void startServer() throws IOException
To this: public void startServer(Socket clientSocket) throws IOException
Then all you need to do is:
public Server()
{
try
{
serverSocket = new ServerSocket(7003);
}
catch (IOException e)
{
System.err.println("Could not listen on port: 7003");
System.exit(1);
}
try
{
while(true) {
final Socket socket = serverSocket.accept();
new Thread(new Runnable() {
public void run() {
try {
startServer(socket);
} catch(IOException e) {e.printStackTrace();}
}
}).start();
}
}
catch(IOException e)
{
System.err.println("Accept failed");
System.exit(1);
}
}
And lastly, you can remove private Socket clientSocket = null;
That should get you there. Or at least pretty close.