【问题标题】:C server returns only first messageC 服务器只返回第一条消息
【发布时间】:2017-01-11 20:34:25
【问题描述】:

我有一个简单的 C 服务器和一个简单的 Java 客户端。客户端从键盘获取输入并将其发送到服务器。服务器将其打印并发送回。但它只是第一次工作,可能是什么问题?

主要:

package test;

import java.util.Scanner;

public class Main {


    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        Client cli = new Client();
        cli.connect();

        while(sc.hasNextLine()) {
            cli.send(sc.nextLine());
            System.out.println(cli.rec());
        }

    }
}

客户:

package test;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.UnknownHostException;

public class Client {

    Socket socket;
    OutputStreamWriter out;
    BufferedReader in;

    public void connect() {

        try {
            socket = new Socket("localhost", 50001);
            socket.setSoTimeout(5000);
        } catch (UnknownHostException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        try {
            out = new OutputStreamWriter(socket.getOutputStream());
        } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
        try {
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public void send(String message) {
        try {
            this.out.write(message + "\n");
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        try {
            this.out.flush();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public String rec() {
        try {
            return in.readLine();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return "";
    }
}

服务器:

#include <stdio.h>
#include <errno.h>
#include <sys/socket.h>
#include <resolv.h>
#include <arpa/inet.h>
#include <errno.h>

#define MY_PORT     50001
#define MAXBUF      1024

int main(int Count, char *Strings[])
{   int sockfd;
    struct sockaddr_in self;
    char buffer[MAXBUF];
    int clientfd;
    struct sockaddr_in client_addr;
    int addrlen=sizeof(client_addr);
    int yes = 1;

    /*---Create streaming socket---*/
    if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0 )
    {
        perror("Socket");
        exit(errno);
    }

    /*---Initialize address/port structure---*/
    bzero(&self, sizeof(self));
    self.sin_family = AF_INET;
    self.sin_port = htons(MY_PORT);
    self.sin_addr.s_addr = INADDR_ANY;

    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == 0)
        {
            printf("Socket options was set.\n"); //TODO
        }
        else
        {
            printf("Set socket options failure.\n"); //TODO
        }

    /*---Assign a port number to the socket---*/
    if ( bind(sockfd, (struct sockaddr*)&self, sizeof(self)) != 0 )
    {
        perror("socket--bind");
        exit(errno);
    }

    /*---Make it a "listening socket"---*/
    if ( listen(sockfd, 20) != 0 )
    {
        perror("socket--listen");
        exit(errno);
    }

    clientfd = accept(sockfd, (struct sockaddr*)&client_addr, &addrlen);

    /*---Forever... ---*/
    while (1)
    {
        memset(buffer, 0, MAXBUF);
        /*---accept a connection (creating a data pipe)---*/
        printf("%s:%d connected\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

        recv(clientfd, buffer, MAXBUF, 0);
        printf("Server got: %s", buffer);
        //sprintf(buffer, "%s\r\n", buffer);
        /*---Echo back anything sent---*/
        printf("sending...\n");
        int n = send(clientfd, buffer, MAXBUF, 0);
        printf("sent: %d\n", n);
    }

    /*---Close data connection---*/
            close(clientfd);

    /*---Clean up (should never get here!)---*/
    close(sockfd);
    return 0;
}

【问题讨论】:

  • 你不检查recv返回了多少字节。您假设您可以发送MAXBUFbuffer。客户端代码意味着它只读取一行。
  • 我认为 accpet 调用应该进入 while 循环。最近一直在玩socket,看看我的messy gist here

标签: java c sockets


【解决方案1】:

好了,根本原因就到这里了。 (不想要细节的可以跳过)

  1. 当您将数据发送回 Java 客户端时,您发送的数据长度为 MAXBUF (1024)。
  2. 现在,显然发生的事情是 java 收到了 1024 个字符作为输入。您可以在(C 语言)服务器输出中看到这一点。 sent: 1024
    为了填充剩余的字符,它发送 ASCII 值 0(即 NUL)
    您可以通过删除 java 代码中的 readLine() API 并添加它来验证这一点。

        StringBuilder strb = new StringBuilder();
        while ((value = inputStream.read()) != -1) {
            if(value != 0)
                strb.append((char) value);
            System.out.print(value + ":" + (char) value+",");
        }
        return strb.toString();
    
  3. 第一次请求从服务器返回到客户端,用户输入的数据后跟剩余的空值,会发生以下情况

    • 由于使用了 readLine() API。它读取到\n\r 字符。这样你就可以看到输出了。
    • 此时,bufferedReader 的缓冲区中仍存储有那些 NUL 字符。
    • 现在,当我们第二次尝试从该读取器读取时,它首先尝试清空旧缓冲区,直到(没有找到 \n\r,除了所有 NUL)。它将所有这些附加到 StringBuilder(它在内部使用),然后将新读取的字符串附加到构建器。
      如果出现此 NUL 字符,则附加到 StringBuilder 的所有字符都将在其缓冲区中消失。 (Explanation given here) 你可以用这个代码试试这个:

      StringBuilder str = new StringBuilder().append((char)0).append("kishore");
      System.out.println(str.toString()); // Output : Blank
      
    • 这就是您看不到输出的原因。所以你的客户端和服务器通信没有问题。

  4. 修复:更改您的“C”服务器代码以发送正确的长度。

        int bytes = recv(clientfd, buffer, MAXBUF, 0);
        ...
        int n = send(clientfd, buffer, bytes, 0);
    

这样它应该可以工作。

【讨论】:

  • @chris01 我用给定的更改尝试了这段代码。有效。我也尝试了循环中的accept 函数,但它不起作用。因为以前的客户端从未断开连接。在循环中写入accept导致java端java.net.SocketTimeoutException: Read timed out,CPP服务器日志卡在accept,因为没有新客户端连接,也没有响应现有客户端。
  • 所以,上面的这个解决方案是有效的。我很高兴,它把我逼疯了。第二。接受在外面,因为我希望一个客户端与服务器无休止地连接,那么为什么我应该在循环中接受?这将浪费连接。
  • 是的,accept 不应该在 while 循环中。这就是我为 chris01 的回答所做的重点。将其写入内部意味着您的客户端还需要创建许多您的场景不需要的连接。
【解决方案2】:

我不确定这是否能解决您的问题,但我认为您的 accept 函数调用应该进入 while 循环,正如我看到 Jacajack 刚刚评论的那样。

这样,每次您收到连接时,循环都会累积。它应该是这样的:

示例

/* Do what's in the loop every time that you accept a connection */

while (clientfd = accept(sockfd, (struct sockaddr*)&client_addr, &addrlen))
{
    memset(buffer, 0, MAXBUF);
    /*---accept a connection (creating a data pipe)---*/
    printf("%s:%d connected\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

    int iResult = recv(clientfd, buffer, MAXBUF, 0); 
    // iResult is the amount of data from recv

    // Handle information and echo it back 


}

此外,我认为您应该实现某种协议来验证服务器收到的消息(看看它确实以行尾或您想要的任何其他验证结束指定)。

【讨论】:

  • 我认为这不会有帮助。如果您查看 java 代码,客户端不会尝试第二次连接以发送第二条消息。他只连接了一次并且正在发送多条消息。将accept改为while循环会在java端产生java.net.SocketTimeoutException: Read timed out,因为没有新的客户端连接到CPP Server,所以卡在accept函数,也没有响应现有的客户端。
【解决方案3】:

accept 必须在你的无限循环中。 它接受那里的任何新连接。所以你只接受第一个而不接受其他。

listen 是您最后一次通话。然后你按连接进行。如果您有一些可扩展性(例如接受分叉),这也是这种方式。

【讨论】:

  • 我认为这不会有帮助。如果您查看 java 代码,客户端不会尝试第二次连接以发送第二条消息。他只连接了一次并且正在发送多条消息。将accept改成while循环会导致java端java.net.SocketTimeoutException: Read timed out,因为没有新客户端连接到CPP Server,所以卡在accept函数,也没有响应现有客户端。
  • 我想问题在于只接受一个客户端并让连接处于活动状态,以便您可以多次响应它。它类似于 HTTP 的保持活动状态。
  • @Bandi:我说的是 C 服务器,而不是 Java 客户端。
  • @chris01:当我只想与一个客户和同一个客户沟通时,接受在外面。它对我来说是这样的。
  • 如果您只有一个连接而没有其他连接,这是正确的。我不是在说只有一个连续的 - 我的意思是在服务器的生命周期中只有一个。但是通常一个服务器应该能够在他的一生中接受超过 1 个连接,然后接受就在一个循环中。也许这是代码中的第二个问题,但我读到它不适用于第二个客户。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-08-17
  • 1970-01-01
  • 1970-01-01
  • 2021-08-06
  • 1970-01-01
  • 2016-04-19
  • 1970-01-01
相关资源
最近更新 更多