Java套接字客户端无法从C服务器接收响应

时间:2017-01-11 19:09:20

标签: java c sockets

我想用java连接到c socket服务器,发送将在服务器上执行的linux命令,并在java客户端上接收结果。

我在C中有套接字客户端,如下所示:

#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");

}

和JavaFX客户端

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();
        }
    }

}

服务器工作正常,接收并重新发送linux命令结果,但是只有当我关闭服务器时,java客户端才会收到答案并填写textarea。 否则客户端会挂起...... 我在Oracle VM和Java 1.8上使用带有gcc的Ubuntu虚拟机。 我做错了什么?

1 个答案:

答案 0 :(得分:1)

原始代码有以下行检查是否继续接收:

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

问题是readLine() method仅在对等方关闭连接后才返回null。只有在方法返回后才会更新用户界面。因此,只有在连接关闭时才会更新UI。

必须有某种结束标记来表示没有更多的数据从服务器传递到最后一个请求(在这种情况下为命令),因此while循环可以结束。

如果Java端像这样实现

,代码应该有效
while (!"".equals(response = reader.readLine())){

和C面这样

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);        

通常这是由于使用原始字节流作为通信通道这一事实引起的问题。即使是最简单的东西,比如这里的代码,也需要有某种协议。在这种情况下,代码会尝试严重依赖换行符(&#39; \ n&#39;,字节0x0A)。

对于请求方,这种方法是可以的。代码假定除了0x0A(换行符,&#39; \ n&#39;)之外的所有字节都是正在传输的命令,0x0A结束命令。有时在通信中有关于控制信道和数据信道的讨论。在这里,他们通过使用不同的&#34;字母&#34;分开。混合没有变化。

不幸的是,服务器的回复,即命令生成的内容,甚至可以包含换行符。这意味着控件和数据不能有不同的字母表。固定代码假定空行(一行中有两个换行符,0x0A 0x0A)结束回复。不幸的是,这对许多命令来说都是完全有效这种方法会引起问题。控制和数据通道应以不同方式分开。

一种方法是首先读取命令的所有输出,然后发送它的长度,最后发送数据。现在,字节序列分隔控制和数据通道。另一种选择可能是选择一些结束字符并将其转义为普通数据。

ZeroMQ可能值得。它可以轻松解决这类问题,即使您决定不使用它,该指南也很有趣。 :)

希望这有帮助。