简体   繁体   中英

Socket communication between Java web app and C++ Server

I need to talk to a C++ application running as a server on a given port. It exposes a binary API(Protocol Buffer) for better performance. My RESTful service is developed in Spring MVC and Jersey and would like to use this new feature. I have been able to consume and produce Protocol Buffer messages successfully.

In my spring web application, I initially created a Apache Commons Pool to create a pool of socket connections. This is how I was reading/writing to the socket

Update 1: Adding PooledObjectFactory implementation

public class PooledSocketConnectionFactory extends BasePooledObjectFactory<Socket> {

    private static final Logger LOGGER = LoggerFactory.getLogger(PooledSocketConnectionFactory.class);

    final private String hostname;
    final private int port;

    private PooledSocketConnectionFactory(final String hostname, final int port) {
        this.hostname = hostname;
        this.port = port;
    }

    @Override
    public Socket create() throws Exception {
        return new Socket(hostname, port);
    }

    @Override
    public PooledObject wrap(Socket socket) {
        return new DefaultPooledObject<>(socket);
    }

    @Override
    public void destroyObject(final PooledObject<Socket> p) throws Exception {
        final Socket socket = p.getObject();
        socket.close();
    }

    @Override
    public boolean validateObject(final PooledObject<Socket> p) {
        final Socket socket = p.getObject();
        return socket != null && socket.isConnected();
    }

    @Override
    public void activateObject(final PooledObject<SocketConnection> p) throws Exception {
    }

    @Override
    public void passivateObject(final PooledObject<SocketConnection> p) throws Exception {
    }
}

@Service
@Scope("prototype")
public class Gateway {
    @Autowired
    private GenericObjectPool pool;

    public Response sendAndReceive(Request request) throws CommunicationException {
        Response response = null;
        final Socket socket = pool.borrowObject();
        try {
            request.writeDelimitedTo(socket.getOutputStream());
            response = Response.parseDelimitedFrom(socket.getInputStream());
        } catch (Exception ex) {
            LOGGER.error("Gateway error", ex);
            throw new CommunicationException("Gateway error", ex);
        } finally {
            pool.returnObject(socket);
        }
        return response;
    }
}

This works for the first request and when the pool returns any previously used socket it is found that the socket is already closed. This could be because different requests are getting connected to the same input and output streams. If I close the socket after reading the response then it beats the purpose of pooling. If I use a singleton socket and inject it, it is able to process first request and then times out.

If I create the socket on every instance then it works and the performance is around 2500 microseconds for every request. My target is to get the performance within 500 microseconds.

What should be the best approach given the requirements?

Update 2: Adding a server and client

package com.es.socket;

import com.es.protos.RequestProtos.Request;
import com.es.protos.ResponseProtos.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

public class TcpServer1 {

    final static Logger LOGGER = LoggerFactory.getLogger(TcpServer1.class.getName());

    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(Integer.parseInt(args[0]));
        Socket socket = null;
        while (true) {
            try {
                socket = serverSocket.accept();
            } catch (IOException e) {
                LOGGER.warn("Could not listen on port");
                System.exit(-1);
            }

            Thread thread = new Thread(new ServerConnection1(socket));
            thread.start();
        }
    }
}

class ServerConnection1 implements Runnable {

    static final Logger LOGGER = LoggerFactory.getLogger(ServerConnection.class.getName());

    private Socket socket = null;

    ServerConnection1(Socket socket) {
        this.socket = socket;
    }

    public void run() {
        try {
            serveRequest(socket.getInputStream(), socket.getOutputStream());
            //socket.close();
        } catch (IOException ex) {
            LOGGER.warn("Error", ex);
        }
    }

    public void serveRequest(InputStream inputStream, OutputStream outputStream) {
        try {
            read(inputStream);
            write(outputStream);
        } catch (IOException ex) {
            LOGGER.warn("ERROR", ex);
        }
    }

    private void write(OutputStream outputStream) throws IOException {
        Response.Builder builder = Response.newBuilder();
        Response response = builder.setStatus("SUCCESS").setPing("PING").build();
        response.writeDelimitedTo(outputStream);
        LOGGER.info("Server sent {}", response.toString());
    }

    private void read(InputStream inputStream) throws IOException {
        Request request = Request.parseDelimitedFrom(inputStream);
        LOGGER.info("Server received {}", request.toString());
    }
}

package com.es.socket;

import com.es.protos.RequestProtos.Request;
import com.es.protos.ResponseProtos.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.net.Socket;

public class TcpClient1 {

    final static Logger LOGGER = LoggerFactory.getLogger(TcpClient1.class.getName());

    private Socket openConnection(final String hostName, final int port) {
        Socket clientSocket = null;
        try {
            clientSocket = new Socket(hostName, port);
        } catch (IOException e) {
            LOGGER.warn("Exception occurred while connecting to server", e);
        }
        return clientSocket;
    }

    private void closeConnection(Socket clientSocket) {
        try {
            LOGGER.info("Closing the connection");
            clientSocket.close();
        } catch (IOException e) {
            LOGGER.warn("Exception occurred while closing the connection", e);
        }
    }

    private void write(OutputStream outputStream) throws IOException {
        Request.Builder builder = Request.newBuilder();
        Request request = builder.setPing("PING").build();
        request.writeDelimitedTo(outputStream);
        LOGGER.info("Client sent {}", request.toString());
    }

    private void read(InputStream inputStream) throws IOException {
        Response response = Response.parseDelimitedFrom(inputStream);
        LOGGER.info("Client received {}", response.toString());
    }

    public static void main(String args[]) throws Exception {
        TcpClient1 client = new TcpClient1();
        try {
            Socket clientSocket = null;

            LOGGER.info("Scenario 1 --> One socket for each call");
            for (int i = 0; i < 2; i++) {
                clientSocket = client.openConnection("localhost", Integer.parseInt(args[0]));
                OutputStream outputStream = clientSocket.getOutputStream();
                InputStream inputStream = clientSocket.getInputStream();
                LOGGER.info("REQUEST {}", i);
                client.write(outputStream);
                client.read(inputStream);
                client.closeConnection(clientSocket);
            }

            LOGGER.info("Scenario 2 --> One socket for all calls");
            clientSocket = client.openConnection("localhost", Integer.parseInt(args[0]));
            OutputStream outputStream = clientSocket.getOutputStream();
            InputStream inputStream = clientSocket.getInputStream();
            for (int i = 0; i < 2; i++) {
                LOGGER.info("REQUEST {}", i);
                client.write(outputStream);
                client.read(inputStream);
            }
            client.closeConnection(clientSocket);
        } catch (Exception e) {
            LOGGER.warn("Exception occurred", e);
            System.exit(1);
        }
    }
}

Here Request and Response are Protocol Buffer classes. In Scenario 1, it is able to able to process both calls whereas in scenario 2 it never returns from the second read. Seems Protocol Buffer API is handling the streams differently. Sample output below

17:03:10.508 [main] INFO  c.d.e.socket.TcpClient1 - Scenario 1 --> One socket for each call
17:03:10.537 [main] INFO  c.d.e.socket.TcpClient1 - REQUEST 0
17:03:10.698 [main] INFO  c.d.e.socket.TcpClient1 - Client sent ping: "PING"
17:03:10.730 [main] INFO  c.d.e.socket.TcpClient1 - Client received status: "SUCCESS"
ping: "PING"
17:03:10.730 [main] INFO  c.d.e.socket.TcpClient1 - Closing the connection
17:03:10.731 [main] INFO  c.d.e.socket.TcpClient1 - REQUEST 1
17:03:10.732 [main] INFO  c.d.e.socket.TcpClient1 - Client sent ping: "PING"
17:03:10.733 [main] INFO  c.d.e.socket.TcpClient1 - Client received status: "SUCCESS"
ping: "PING"
17:03:10.733 [main] INFO  c.d.e.socket.TcpClient1 - Closing the connection
17:03:10.733 [main] INFO  c.d.e.socket.TcpClient1 - Scenario 2 --> One socket for all calls
17:03:10.733 [main] INFO  c.d.e.socket.TcpClient1 - REQUEST 0
17:03:10.734 [main] INFO  c.d.e.socket.TcpClient1 - Client sent ping: "PING"
17:03:10.734 [main] INFO  c.d.e.socket.TcpClient1 - Client received status: "SUCCESS"
ping: "PING"
17:03:10.734 [main] INFO  c.d.e.socket.TcpClient1 - REQUEST 1
17:03:10.735 [main] INFO  c.d.e.socket.TcpClient1 - Client sent ping: "PING"

After great pain I was able to resolve the issue. The class which was handling the read/write to the socket was defined as prototype. So once a reference to the socket was retrieved it was not cleared up(managed by Tomcat). As such subsequent calls to the socket gets queued up, which then times out and the object is destroyed by Apache Commons Pool.

To fix this, I created class SocketConnection with a ThreadLocal of Socket. On the processing side, I created a Callback to handle read/write to the socket. Sample code snippet below:

class SocketConnection {

    final private String identity;
    private boolean alive;
    final private ThreadLocal<Socket> threadLocal;

    public SocketConnection(final String hostname, final int port) throws IOException {
        this.identity = UUID.randomUUID().toString();
        this.alive = true;
        threadLocal = ThreadLocal.withInitial(rethrowSupplier(() -> new Socket(hostname, port)));
    }

}

public class PooledSocketConnectionFactory extends BasePooledObjectFactory<SocketConnection> {

    private static final Logger LOGGER = LoggerFactory.getLogger(PooledSocketConnectionFactory.class);

    final private String hostname;
    final private int port;
    private SocketConnection connection = null;

    private PooledSocketConnectionFactory(final String hostname, final int port) {
        this.hostname = hostname;
        this.port = port;
    }

    @Override
    public SocketConnection create() throws Exception {
        LOGGER.info("Creating Socket");
        return new SocketConnection(hostname, port);
    }

    @Override
    public PooledObject wrap(SocketConnection socketConnection) {
        return new DefaultPooledObject<>(socketConnection);
    }

    @Override
    public void destroyObject(final PooledObject<SocketConnection> p) throws Exception {
        final SocketConnection socketConnection = p.getObject();
        socketConnection.setAlive(false);
        socketConnection.close();
    }

    @Override
    public boolean validateObject(final PooledObject<SocketConnection> p) {
        final SocketConnection connection = p.getObject();
        final Socket socket = connection.get();
        return connection != null && connection.isAlive() && socket.isConnected();
    }

    @Override
    public void activateObject(final PooledObject<SocketConnection> p) throws Exception {
        final SocketConnection socketConnection = p.getObject();
        socketConnection.setAlive(true);
    }

    @Override
    public void passivateObject(final PooledObject<SocketConnection> p) throws Exception {
        final SocketConnection socketConnection = p.getObject();
        socketConnection.setAlive(false);
    }

}

class SocketCallback implements Callable<Response> {

    private SocketConnection socketConnection;
    private Request request;

    public SocketCallback() {
    }

    public SocketCallback(SocketConnection socketConnection, Request request) {
        this.socketConnection = socketConnection;
        this.request = request;
    }

    public Response call() throws Exception {
        final Socket socket = socketConnection.get();
        request.writeDelimitedTo(socket.getOutputStream());
        Response response = Response.parseDelimitedFrom(socket.getInputStream());
        return response;
    }

}

@Service
@Scope("prototype")
public class SocketGateway {

    private static final Logger LOGGER = LoggerFactory.getLogger(SocketGateway.class);

    @Autowired
    private GenericObjectPool<SocketConnection> socketPool;
    @Autowired
    private ExecutorService executorService;

    public Response eligibility(Request request) throws DataException {
        EligibilityResponse response = null;
        SocketConnection connection = null;
        if (request != null) {
            try {
                connection = socketPool.borrowObject();
                Future<Response> future = executorService.submit(new SocketCallback(connection, request));
                response = future.get();
            } catch (Exception ex) {
                LOGGER.error("Gateway error {}");
                throw new DataException("Gateway error", ex);
            } finally {
                socketPool.returnObject(connection);
            }
        }

        return response;
    }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM