amkz
amkz

Reputation: 588

Java socket client can't receive response from C server

I want to connect with java to c socket server, send linux command which will be executed on server and the receive the result on java client.

I have socket client in C which looks like this:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/wait.h>
#include <pthread.h>

#define MAXCON 500 
#define BUFFSIZE 256
#define MAXCOMMAND 500

void *newClient(void *argSocket);

int main (int argc, char *argv[]) {

    fflush(stdout);
    struct sockaddr_in addrIn;
    struct sockaddr_in clientAddr;
    int mainSocket = socket(PF_INET, SOCK_STREAM, 0);

    memset(&addrIn, '\0', sizeof(addrIn));
    addrIn.sin_family = AF_INET;
    addrIn.sin_addr.s_addr = INADDR_ANY;
    addrIn.sin_port = htons(6666);

    if(setsockopt(mainSocket, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int))<0){
        perror("SETSOCKOPT ERROR");
        exit(-1);
    }

    if(bind(mainSocket, (struct sockaddr *)&addrIn, sizeof(addrIn))<0){
        perror("PORT IS ALREADY IN USE");
        exit(-1);
    }

    listen(mainSocket, MAXCON); 

    while(1){
        int newSocket;
        int clientAddrLength = sizeof(clientAddr);

        if((newSocket = accept(mainSocket, (struct sockaddr*)&clientAddr, &clientAddrLength)) < 0){
            perror("ERROR DURING CONNECTION");
            continue;
        }

        pthread_t *newThread;
        pthread_create(&newThread, NULL, newClient, (void*)newSocket);
        pthread_detach(newThread);
    }

    return EXIT_SUCCESS;

}

void *newClient(void *argSocket){

    fflush(stdout);
    int socketId = (int)argSocket;
    while(1){
        char command[MAXCOMMAND];
        memset(command, '\0', MAXCOMMAND);

        for(int i = 0;i<MAXCOMMAND;i++){
            int answer = recv(socketId, &command[i], 1, 0);
            if(answer==0)       
                break;
            if(answer<0)
                break;
            if(command[i]=='\n')
                break;
        }

        int commandLength = strlen(command);

        if(commandLength > 0 && command[commandLength-1] == '\n'){
            command[commandLength-1] = '\0';
            if(commandLength-2 >= 0 && command[commandLength-2] == '\r')
                command[commandLength-2] = '\0';
        }

        if(commandLength == 0)
            continue;

        printf("NEW COMMAND: %s\n", command);
        fflush(stdout);

        FILE *result = popen(command, "r");

        if(result != NULL)
            while(1){
                char buffer[BUFFSIZE];
                memset(buffer, '\0', BUFFSIZE);
                char *pipeData = fgets(buffer, BUFFSIZE, result);
                if(pipeData == NULL)
                    break;
                int r = send(socketId, buffer, strlen(buffer), 0);
                r = send(socketId, '\n', 1, 0);
                if(r <= 0)
                    break;
            }
    }

    end:
    printf("END\n");

}

and JavaFX Client

package sample;

import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;

import java.io.*;
import java.net.Socket;
import java.net.URL;
import java.util.ResourceBundle;

public class Controller implements Initializable {

    @FXML
    TextField textField = new TextField();
    @FXML
    TextArea textArea = new TextArea();
    @FXML
    Button button = new Button("Accept");

    private DataOutputStream dataOutputStream;
    private BufferedReader reader;

    private void connectAndReceive() {
        try {
            textArea.setText("");
            dataOutputStream.writeBytes(textField.getText() + "\n");
            String response;
            while ((response = reader.readLine()) != '\n') {
                textArea.appendText("-" + response);
            }

        } catch (IOException e) {
            textArea.appendText("1" + e.getMessage() + e.toString());
        }
    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        textField.setId("textField");
        textArea.setId("textArea");
        button.setId("button");
        button.setOnAction(e -> connectAndReceive());
        try {
            Socket client = new Socket("127.0.0.1", 6666);
            dataOutputStream = new DataOutputStream(client.getOutputStream());
            reader = new BufferedReader(new InputStreamReader(client.getInputStream()));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

Server is working good, receive and resend linux command result, but java client receive answer and fill textarea only when I shut down the server. Otherwise client is going to hang... I am using Ubuntu virtual machine with gcc on Oracle VM and Java 1.8. What I am doing wrong with this?

Upvotes: 1

Views: 720

Answers (1)

Panu
Panu

Reputation: 362

Original code had following line to check wether receiving should continue:

while ((response = reader.readLine()) != null){

Problem is that the readLine() method returns null only after the connection has been closed by the peer. The updating of the user interface happens only after the method return. Thus the UI gets updated only when the connection is closed.

There has to be some kind of end marker to signal that there is no more data coming from server to the last request (command in this case) so the while loop can end.

Code should work if Java side is implemented like this

while (!"".equals(response = reader.readLine())){

and C side like this

if(result != NULL) {
    while(1){
        char buffer[BUFFSIZE];
        memset(buffer, '\0', BUFFSIZE);
        char *pipeData = fgets(buffer, BUFFSIZE, result);
        if(pipeData == NULL)
            break;
        int r = send(socketId, buffer, strlen(buffer), 0);
        if(r <= 0) break;           
    }
}
send(socketId, "\n", 1, 0);        

Generally this is problem caused by the fact that using raw byte stream as communication channel is a pain in the ass. Even the simplest things, like the code here, need to have some kind of protocol. In this case the code tries to lean heavily on the newline character ('\n', byte 0x0A).

For the request side this approach is ok. Code just assumes that all of the bytes except 0x0A (newline, '\n') is of a command being transmitted and 0x0A ends the command. Sometimes in communication there is discussion about control channel and data channel. Here they are separated by using different "alphabets". There is no change of mixup.

Unfortunately the reply from the server, what the command generated, can contain any characters even the newline. This means that the control and data can not have different alphabets. The fixed code assumes that empty line (two newlines in a row, 0x0A 0x0A) ends the reply. Unfortunately this is totally valid output for many commands. This approach will cause problems. Control and data channels should be separated differently.

One approach is to read all of the output of the command first, then send the length of it and finally the data. Now the sequence of bytes separates control and data channels. Another option could be to choose some end character and escape it in the normal data.

It might be worth to look ZeroMQ. It can solve this kind of problems easily and in additionally even if you decide not to use it, the guide is fun to read. :)

Hope this helps.

Upvotes: 1

Related Questions