Hot questions for Using Transmission Control Protocol in java io

Question:

I'm coding socket client in Java. In the program, I want to get information from server. When the server receives "GET_LIGHTS" command, it sends back data in JSON format.

But in my code, bw.write() and bw.flush() doesn't work before socket.close(). So, the BufferedReader object is not ready: br.ready() returns false.

Is there any mistake in my code?

The client code is shown bellow.

package monitor;

import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.ArrayList;

public class SocketClient {
    static private int port;
    static private String hostName;
    private Socket socket;

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

    // get lights by JSON
    public void getLights() {
        try {
            // generate socket
            InetSocketAddress endpoint = new InetSocketAddress(hostName, port);
            socket = new Socket();
            socket.connect(endpoint);

            // setting
            OutputStreamWriter out = new OutputStreamWriter(socket.getOutputStream());
            BufferedWriter bw = new BufferedWriter(out);

            InputStreamReader in = new InputStreamReader(socket.getInputStream());
            BufferedReader br = new BufferedReader(in);

            // send command
            bw.write("GET_LIGHTS");
            bw.flush();

            // receive message from server
            System.out.println(br.readLine());

            socket.close();

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

    public void initLights(ArrayList<Light> lights) {
        getLights();
    }

}

Edited:

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

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

public class SocketServer extends Thread{
    static final int PORT = 44344;
    static private ILS ils;
    private ServerSocket serverSocket;
    private Socket socket;

    public SocketServer(ILS ils) {
        this.ils = ils;
    }
    @Override
    public void run() {
        serverSocket = null;
        System.out.println("Server: listening");

        try {
            serverSocket = new ServerSocket(PORT);
            while(true){
                socket = serverSocket.accept();

                BufferedReader br = new BufferedReader(
                        new InputStreamReader(socket.getInputStream()));
                ArrayList<String> cmd = new ArrayList<>();
                String in;
                while( (in = br.readLine()) != null ){
                    cmd.add(in);
                }
                command(cmd);
                if( socket != null){
                    socket.close();
                }
            }

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

        if( serverSocket != null){
            try {
                serverSocket.close();
                serverSocket = null;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    // send message to client
    private void sendMessage(String str) {
        System.out.println(str);
        try {
            OutputStream output = socket.getOutputStream();
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(output));
            bw.write(str + "¥n");
            bw.flush();
            bw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // error
    private void printError(String err) {
        String str = "ERROR; ";
        str += err;
        sendMessage(str);
    }

    public void command(ArrayList<String> cmd) {
        String mode = cmd.get(0);
        if(mode == null){

        }else switch(mode){
            case "MANUAL_SIG-ALL":
                System.out.println("全照明一括 信号値指定調光");
                manualSigAll(cmd.get(1));
                break;
            case "MANUAL_SIG-INDIVIDUAL":
                System.out.println("全照明独立 信号値指定調光");
                manualSigIndividual(cmd.get(1));
                break;
            case "MANUAL_ID-SIG":
                System.out.println("照明ID・信号値指定調光");
                manualIDSig(cmd.get(1));
                break;
            case "MANUAL_ID-RELATIVE":
                System.out.println("照明ID・相対信号値指定調光");
                break;
            case "DOWNLIGHT_ALL":
                System.out.println("Downlight: All Control");
                downlightAll(cmd.get(1));
                break;
            case "DOWNLIGHT_INDIVIDUAL":
                System.out.println("Downlight: Individual control");
                downlightIndividual(cmd.get(1));
                break;
            case "GET_LIGHTS":
                System.out.println("Sending lights via JSON");
                sendLights();
                break;
            default:
                System.out.println("Error: 不明なmode command");
        }
    }

    // 全照明一括 信号値指定調光
    private void manualSigAll(String sigs) {
        if(sigs == null) {
            System.out.println("信号値のフォーマットを確認してください");
        } else {
            ArrayList<Integer> s = new ArrayList<>();
            String[] buf = sigs.split(",");
            for(String i:buf) s.add(Integer.parseInt(i));
            for(Light l: ils.getLights()) {
                l.setLumPct((double)s.get(0)/255.0*100.0);
                l.setSignal(s.get(0), s.get(1));
            }
        }
        // 調光
        ils.downlightDimmer.send();

    }

    // 全照明独立 信号値指定調光
    private void manualSigIndividual(String sigs) {
        if(sigs == null) {
            System.out.println("信号値のフォーマットを確認してください");
        } else {
            ArrayList<Integer> s = new ArrayList<>();
            String[] buf = sigs.split(",");
            for(String i:buf) s.add(Integer.parseInt(i));
            for(int i=0; i<ils.getLights().size(); i++) {
                ils.getLights().get(i).setSignal(s.get(0), s.get(1));
                s.remove(0);
                s.remove(0);
            }
        }
        ils.downlightDimmer.send();
    }

    // 照明ID・信号値指定調光
    private void manualIDSig(String sigs) {
        if(sigs == null) {
            System.out.println("信号値のフォーマットを確認してください");
        } else {
            ArrayList<Integer> s = new ArrayList<>();
            String[] buf = sigs.split(",");
            for(String i:buf) s.add(Integer.parseInt(i));
            System.out.println(s.get(0));
            ils.getLight(s.get(0)).setSignal(s.get(1), s.get(2));
        }
        ils.downlightDimmer.send();
    }

    private void downlightAll(String cmd) {
        if(cmd == null) {
            printError("Check for data command.");
        } else {
            ArrayList<Double> data = new ArrayList<>();
            String[] buf = cmd.split(",");
            for(String i:buf) data.add(Double.parseDouble(i));
            for(Light l: ils.getLights()) {
                l.setLumPct(data.get(0));
                l.setTemperature(data.get(1));
            }
        }
        // dimming
        ils.downlightDimmer.send();
    }

    private void downlightIndividual(String cmd) {
        if(cmd == null) {
            printError("Check for data command.");
        } else {
            ArrayList<Integer> id = new ArrayList<>();
            ArrayList<Double> lumPct = new ArrayList<>();
            ArrayList<Integer> temp = new ArrayList<>();

            String[] buf = cmd.split(",");
            if(buf.length % 3 != 0) {printError("invalid number of data.");}

            for(int i=0; i<buf.length/3; i++) {
                int n = i*3;
                try {
                    id.add(Integer.parseInt(buf[n]));
                    lumPct.add(Double.parseDouble(buf[n + 1]));
                    temp.add(Integer.parseInt(buf[n + 2]));
                } catch (Exception e) {
                    printError(e.getMessage());
                    return;
                }
            }

            while (id.size() > 0) {
                // update light object
                Light light = ils.getLight(id.get(0));
                light.setLumPct(lumPct.get(0));
                light.setTemperature(temp.get(0));

                // remove data from array list
                id.remove(0);
                lumPct.remove(0);
                temp.remove(0);
            }

            // dimming
            ils.downlightDimmer.send();

        }
    }

    private void sendLights() {
        ObjectMapper mapper = new ObjectMapper();
        String json = "";
        try {
            json = mapper.writeValueAsString(ils.getLights());
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }

        // output
        sendMessage(json);
    }



}

Answer:

If your server is using readLine(), as is probable, it will block until you close the connection, because you aren't sending a line.

Add bw.newLine() before the flush().

EDIT As predicted, your server isn't sending lines, so it needs the same treatment as above. However there is an anterior problem:

while( (in = br.readLine()) != null ){
    cmd.add(in);
}

This loop in the server cannot possibly exit until the client closes the connection. You should process a line at a time in the server, or else moderate your expectations of the client's behaviour.

Question:

A TCP server (using the java.io package) should be multi-threaded so that it can support several clients concurrently. However, a UDP server need not be multi-threaded to serve several clients concurrently. Can anyone explain why it is so?


Answer:

A (java.io) TCP server needs to be multithreaded because the communication with each client happens over io streams. Communication blocks per stream every time you read/write a message.

A UDP server does not communicate via io streams. It communicates directly via single datagrams from all clients on the same channel.

Assume your server has 10 clients and it waits for any one of them to send.

  • TCP needs 10 threads, each calling the InputStream#read() method and all block. At one point 1 will be woken up. The "message" does not need to contain the senders address because that's implied by having a stream / connection.

  • UDP needs 1 thread that calls the DatagramSocket#receive() method. The packet will contain the sender address so the 1 thread can decide what to do.

Question:

My java code often stuck in a loop "while ((line = rd.readLine()) != null) {"

I guess it is because java garbage collection did not get back some memory. The variable "line" will point to new content in every iteration and the memory of old content of "line" is not collected back by java GC for some reason.

So How to optimize the code to fix the problem?

The code is as follows:

for (String url : urlList) { // a loop that process ~1000 urls
      HttpResponse response = fetchSomeUrl(url);
      // I already confirmed that response code is 200 OK, which means HttpResponse is generated successfully.

      BufferedReader rd = new BufferedReader (new InputStreamReader(response.getEntity().getContent()));
      StringBuilder raw_content = new StringBuilder();
      String line;
      int i = 0;
      System.out.println("=====start processing rd.readLine()");
      while ((line = rd.readLine()) != null) {
         // my code often stuck here when there are more than 1000 lines in rd
         System.out.println("=====processing " + i + "th line");
         raw_content.append(line);
         raw_content.append("\n");
         i++;
      }
      System.out.println("=====finished processing rd.readLine()");
  }

Answer:

My java code often stuck in a loop "while ((line = rd.readLine()) != null) {"

The word is 'blocked'. It is blocked waiting for another line to arrive from the peer, or for the peer to close the connection.

I guess it is because java garbage collection did not get back some memory.

It has nothing whatsoever to do with either garbage collection, Java, or memory. It has to do with what the peer is or isn't doing.

The variable "line" will point to new content in every iteration and the memory of old content of "line" is not collected back by java GC for some reason.

This is nonsense, and even if it was true it wouldn't account for the phenomena.

So How to optimize the code to fix the problem?

It's not a coding problem, and if it was, no amount of 'optimization' would solve it. Your program is working as coded. This loop will iterate until the peer disconnects, and it will block while there isn't a complete line or end of stream to be read.

You should be processing each line as it arrives, rather than trying to assemble them and process them all when the peer disconnects.

EDIT You could consider setting a read timeout to catch problematic cases.