Creating a Socket Client Pool in Java

Advertisements

Some time ago I needed to create a pool of Socket connections for a customer. First thing I tried to do was to google it and find a library that already provided what I needed. Unfortunately I wasn´t able to find one that fits with my needs. Therefore I had to create an artifact that resolved that issue at the moment.

What do I need to create a Socket Pool ?

This Socket Pool has only 4 classes (really 5, but 1 is only an custom exception).

  • Socket Client
  • SocketPool and SocketFactory from Apache Commons Pool
  • Socket Helper class

Keep in mind that in order to create a socket pool, the server side doesn´t have close that connection when finishes a message. The goal of a socket pool is to keep a bunch of active connections waiting to be used, therefore the server must not close connections, otherwise, the socket pool doesn´t make sense.

Socket Client Class

Nothing special here! Just a class that creates a Socket and provides methods to send a message and close it.

package com.orthanc.commons.pool.socket;

import com.orthanc.commons.pool.socket.exceptions.SocketClientException;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Adam M. Gamboa G
 */
public class SocketClient {

    private static final Logger LOGGER = Logger.getLogger(SocketClient.class.getName());

    private Socket client;
    private BufferedReader input;
    private BufferedWriter output;
    
    private static final int TIMEOUT_IN_MS = 30 * 1000;   // 1 segundo;        

    public SocketClient(String host, int port) {
        this.create(host, port);
    }

    private void create(String host, int port) {
        // create a socket with a timeout
        try {
            SocketAddress socketAddress = new InetSocketAddress(host, port);
            // create a socket
            this.client = new Socket();

            // this method will block no more than timeout ms.
            this.client.connect(socketAddress, TIMEOUT_IN_MS);
            this.client.setSoTimeout(TIMEOUT_IN_MS);
            this.client.setTcpNoDelay(true);
            this.client.setKeepAlive(true);
            
            this.input = new BufferedReader(new InputStreamReader(this.client.getInputStream()));
            this.output = new BufferedWriter(new OutputStreamWriter(this.client.getOutputStream()));
            this.output.flush();
        } catch (IOException ex) {
           LOGGER.log(Level.SEVERE, ex.getMessage(), ex);
           throw new SocketClientException("The new socket connection couldnt be created. Host:"+host+", Port:"+port,
                   ex);
        }
    }

    public byte[] execute(byte[] message) {
        try{
            //Sends the message
            this.output.write(new String(message));
            this.output.newLine();
            this.output.flush();
            
            //Gets the response. Because the server doesn´t close the
            //connection (if it´s closed by the server, the pool doesnt make sense)
            //Server used to send a new line character to finish the message.
            //that´s why we are reading a line.
            String line = input.readLine();
            return line.getBytes();
        }catch(IOException ioex){
            LOGGER.log(Level.SEVERE, "Error sending the message to the socket", ioex);
            throw new SocketClientException("Error sending the message to the socket",ioex);
        }
    }

    public void close() {
        if (this.client != null) {
            try {
                this.client.close();
            } catch (IOException ioex) {
                LOGGER.log(Level.WARNING, "The socket client couldnt be close", ioex);
            } finally {
                this.client = null;
            }
        }
    }

    public boolean isValid() {
        if (this.client != null) {
            return this.client.isClosed();
        }
        return false;
    }

    public void activate() {
        LOGGER.log(Level.FINE, "... Activating socket ...");
    }

    public void desactivate() {
        LOGGER.log(Level.FINE, "... Desactivating socket ...");
    }

}

Apache Pool

Of course, I can implement something as simple as a class with a Collection of Socket Clients and provide a couple of methods to get and return connections (socket clients) in that collection.

But for applications in the real world, with a hundred/thousands of request per minute something like that wouldn´t be scalable and might have performance issues. Therefore, I decided to use Apache Commons Pool, from the Apache Commons project as the base to build it.

A Helper Class to Interact

package com.orthanc.commons.pool.socket;

import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

/**
 *
 * @author Adam M. Gamboa G
 */
public class SocketHelper {
    
    private static final Logger LOGGER = Logger.getLogger(SocketHelper.class.getName());
    private final static Map<String,SocketHelper> socketPools = new HashMap<>();
    private final SocketPool socketPool;
    
    /**
     * Hidden constructor
     */
    private SocketHelper(SocketPool socketPool){
        this.socketPool = socketPool;
    }
    
    //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>
    //<<>><<>><<>><<>><<>> SINGLETON Methods  <<>><<>><<>><<>><<>><<>><<>><<>>
    //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>
    
    public static SocketHelper getInstance(String host, int port){
        String key = host+":"+port;
        if(!socketPools.containsKey(key)){
            synchronized(SocketHelper.class){
                SocketFactory factory = new SocketFactory(host, port);
                GenericObjectPoolConfig config = getDefaultConfig();
                SocketPool newPool = new SocketPool(factory, config);
                try {
                    newPool.preparePool();
                } catch (Exception ex) {
                    LOGGER.log(Level.SEVERE, "Error while trying to init the pool", ex);
                }
                SocketHelper newInstance = new SocketHelper(newPool);
                socketPools.put(key, newInstance);
            }
        }
        return socketPools.get(key);
    }
    
    private static GenericObjectPoolConfig getDefaultConfig(){
        GenericObjectPoolConfig defaultConfig = new GenericObjectPoolConfig();
        //Put here any default configuration
        return defaultConfig;
    }
    
    //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>
    //<<>><<>><<>><<>><<>> METODOS HELPER  <<>><<>><<>><<>><<>><<>><<>><<>>
    //<<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>

    public SocketClient getSocket(){
        try {
            return this.socketPool.borrowObject();
        } catch (Exception ex) {
            Logger.getLogger(SocketHelper.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        }
    }
  
    public void returnSocket(SocketClient socketClient){
        this.socketPool.returnObject(socketClient); 
    }
  
    public void shutDown(){
        String key = ((SocketFactory)socketPool.getFactory()).getHost()+":"+
                ((SocketFactory)socketPool.getFactory()).getPort();
        socketPools.remove(key);
    }
    
    public void setConfiguration(GenericObjectPoolConfig config){
        this.socketPool.setConfig(config);
        try{
            this.socketPool.preparePool();
        }catch (Exception ex) {
            LOGGER.log(Level.SEVERE, "Error while reseting the pool", ex);
        }
    }
    
    public void logStatus(){
        StringBuilder sb = new StringBuilder();
        sb.append("\n-------------------------------------").append("\n")
            .append("[Maximum Size:").append(this.socketPool.getMaxTotal()).append("]")
            .append("[Minimun Size:").append(this.socketPool.getMinIdle()).append("]")
            .append("[Used instances:").append(this.socketPool.getNumActive()).append("]")
            .append("[Unused instances: ").append(this.socketPool.getNumIdle()).append("]")
            .append("[Total instances: ").append(this.socketPool.getNumIdle()+this.socketPool.getNumActive()).append("]")
            .append("[Requests in Queue: ").append(this.socketPool.getNumWaiters()).append("]")
            .append("\n-------------------------------------").append("\n");
        LOGGER.log(Level.INFO, sb.toString());
    }

    public int getFeature(StatusPoolFeature feature){
        switch(feature){
            case NUM_ACTIVE:
                return this.socketPool.getNumActive();
            case NUM_IDLE:
                return this.socketPool.getNumIdle();
            case NUM_WAITERS:
                return this.socketPool.getNumWaiters();
            case MAX_TOTAL:
                return this.socketPool.getMaxTotal();
        }
        return -1;
    }
    
    /**
     * Configurable features in the pool
     */
    public enum StatusPoolFeature{
        NUM_ACTIVE,
        NUM_IDLE,
        NUM_WAITERS,
        MAX_TOTAL;
    }
    
}

How to use it?

You can initialize a new pool by using:

SocketHelper helper = SocketHelper.getInstance("127.0.0.1", 800);

The above line is going to initilizate a new pool or return and existent pool. Then you can use the methods of the helper to perform actions on the pool.

Advertisements

Getting a new Socket Client from the pool.

When you want to interact with a Socket server endpoint you will use a SocketClient object from the pool. To obtaing one of them you just need to use:

SocketClient client = helper.getSocket();

byte [] messageInput = ...;
byte [] messageOutput = client.execute(messageInput);

When you finished using a Socket client instance you can return it back to the pool by using

helper.return(client);

Shutting down a Socket Pool

When you decided to shutdown a pool just use the following line:

helper.shutdown();

That line is going to shutdown the pool that was initiliazed or gotten on that instance.

Customizing Configurations

That’s done by using the GenericObjectPoolConfig class from Apache Commons Pool2.

helper.setConfiguration(GenericObjectPoolConfig config);

For example, it’s possible to customize the minimum, maximum, initial amount of instances on the pool.

Where is the code?

You can access the complete code I created at my github.
https//github.com/AdamGamboa/socket-pool

Advertisements

6 comments

  1. Hi,

    I used this code in my application.

    I found a problem after using this pool after certain time and it happens randomly. The pool doesn’t increase based on the request that comes to it. It stay stable creating one pool only and keep requests in the queue instead opening socket connection to server it.

    I used following settings
    setMinEvictableIdleDuration(Duration.ofSeconds(120))
    setTimeBetweenEvictionRuns(Duration.ofSeconds(60))
    setTestWhileIdle(true)
    setMaxTotal(30)
    setMaxIdle(5)

    Thanks
    Masuqur

Leave a Reply to ปั้มไลค์ Cancel reply

Your email address will not be published. Required fields are marked *