jarkow
jarkow

Reputation: 113

How to keep TCP sockets open?

Our application has a ping-pong like conversation with many servers (each server has a corresponding thread where those connections are made). Code below works, but it opens a new connection for every new request and is used only once, which soon leads to reaching max connection cap set by server.

DataProvider.java

public static ZnResult sendTcpQuery(String xml, String url, int port) {
    List<ZnXmlResult> results = new ArrayList<>();
    String xmlString = xml != null ? new String((xml + "\n").getBytes()) : "";
    int error = ZnResult.OK;
    try (Socket clientSocket = new Socket(url, port)) {
        clientSocket.setSoTimeout(CONNECTION_TIMEOUT);
        DataOutputStream outToServer = new DataOutputStream(clientSocket.getOutputStream());
        try (BufferedReader inFromServer = new BufferedReader(new InputStreamReader(clientSocket.getInputStream(), "UTF-8"))) {
            outToServer.writeBytes(xmlString);
            try (StringWriter responseFromServer = new StringWriter()) {
                String readLine;
                while ((readLine = inFromServer.readLine()) != null) {
                    ...
                }
            }
            outToServer.close();
            clientSocket.close();
        }
    } catch (Exception ex) {
        LOG.error("Exception {}", url + ":" + port, ex);
        error = ZnResult.ERR;
    }
    return error == ZnResult.OK ? new ZnResult(results) : new ZnResult(error);
}

How can I transform it, so everything can be done within one connection? I figured I would do something like this:

SocketFactory.java

public class SocketFactory {
private static HashMap<String, Socket> socketsByAddress = new HashMap<>();
private static HashMap<Socket, DataOutputStream> outputStreamsBySocket = new HashMap<>();
private static HashMap<Socket, BufferedReader> readersBySocket = new HashMap<>();

public static Socket getSocket(String address) {
    String ip = Tools.getIpFromAddress(address);
    int port = Tools.getPortFromAddress(address);
    Socket socket = socketsByAddress.get(address);
    if (socket == null) {
        try {
            socket = new Socket(ip, port);
            socket.setSoTimeout(60000);
            socketsByAddress.put(address, socket);
        } catch (IOException ex) {
            Logger.getLogger(SocketFactory.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    return socket;
}

public static DataOutputStream getOutputStream(Socket socket) {
    DataOutputStream outputStream = outputStreamsBySocket.get(socket);
    if (outputStream == null) {
        try {
            outputStream = new DataOutputStream(socket.getOutputStream());
            outputStreamsBySocket.put(socket, outputStream);
        } catch (IOException ex) {
            Logger.getLogger(SocketFactory.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    return outputStream;
}

public static BufferedReader getReader(Socket socket) {
    BufferedReader reader = readersBySocket.get(socket);
    if (reader == null) {
        try {
            reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            readersBySocket.put(socket, reader);
        } catch (IOException ex) {
            Logger.getLogger(SocketFactory.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    return reader;
}
}

DataProvider.java

public static ZnResult sendTcpQuery(String xml, String url, int port) {
    List<ZnXmlResult> results = new ArrayList<>();
    int error = ZnResult.OK;
    try {
        String xmlString = xml != null ? new String((xml + "\n").getBytes()) : "";
        Socket clientSocket = SocketFactory.getSocket(url + ":" + port);
        DataOutputStream outToServer = SocketFactory.getOutputStream(clientSocket);
        BufferedReader inFromServer = SocketFactory.getReader(clientSocket);
        outToServer.writeBytes(xmlString);
        try (StringWriter responseFromServer = new StringWriter()) {
            String readLine;
            while ((readLine = inFromServer.readLine()) != null) {
                ...
            }
        }
    } catch (Exception ex) {
        LOG.error("Exception {}", url + ":" + port, ex);
        error = ZnResult.ERR;
    }
    return error == ZnResult.OK ? new ZnResult(results) : new ZnResult(error);
}

but it just doesn't work and only the first one go through.

Upvotes: 4

Views: 99

Answers (2)

Peter Lawrey
Peter Lawrey

Reputation: 533790

This loop reads until the end of the stream.

while ((readLine = inFromServer.readLine()) != null) {

A stream only ends once. i.e. you can't end the stream but later use it again.

What you need to do instead;

  • have a terminating line which can't occur in your data. e.g. wait for "[EOF]"
  • send the length of data first and read only that much data.

Upvotes: 1

Shahid
Shahid

Reputation: 481

Try to initiate the Socket object using URL rather than IP address as you were doing in your first code and see if it works for you.

Upvotes: 0

Related Questions