【问题标题】:Java client throws SocketException: Connection reset when receiving data from C server. Why?Java 客户端从 C 服务器接收数据时抛出 SocketException: Connection reset。为什么?
【发布时间】:2018-11-17 04:37:07
【问题描述】:

我有一个用 C 编写的 TCP/IP 服务器程序和一个用 Java 编写的相应客户端:

C 服务器

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdbool.h>


#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <netdb.h>

#define PORT 0

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


    printf("ISV_APP: Initiating TCP/IP networking.\n");

    int server_fd, new_socket, valread;
    struct sockaddr_in address;
    int opt = 1;
    int addrlen = sizeof(address);
    char buffer[1024] = {0};



    // Creating socket file descriptor.
    if((server_fd = socket(AF_INET, SOCK_STREAM,0)) == 0)
    {
        printf("TCP/IP: Initiation of server socket file descriptor failed.\n" );
        return -1;
    }
    printf("TCP/IP: Initializing server socket.\n" );

    // Forcefully attaching socket to the connection port.
    if(setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)))
    {
        printf("TCP/IP: Error with setsockopt call occured.\n" );
        return -1;
    }
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);
    printf("TCP/IP: Socket options have been set.\n" );

    // Binding the socket.
    if(bind(server_fd, (struct sockaddr *)&address, sizeof(address)) <0){
        printf("TCP/IP: Failed to bind socket.\n" );
        return -1;
    }
    printf("TCP/IP: Socket bound to port.\n" );

    // Listen for connections
    if(listen(server_fd, 3) < 0){
        printf("TCP/IP: Failed to listen for incoming connections.\n" );
        return -1;
    }

    // Print the assigned port and address so that client knows how to connect.
    struct sockaddr_in sin;
    socklen_t len = sizeof(sin);
    if (getsockname(server_fd, (struct sockaddr *)&sin, &len) == -1){
        printf("Failed to read port number.\n" );
    }
    else{
        // Print port
        unsigned int my_port = ntohs(sin.sin_port);
        char my_port_str[6]; 
        sprintf(my_port_str, "%d", my_port);
        printf("\nINFO: Socket bound to port: " );
        printf(my_port_str);
        printf("\n");

        // Print address
        char my_ip[16];
        inet_ntop(AF_INET, &sin.sin_addr, my_ip, sizeof(my_ip));
        printf("INFO: Server IP Address: ");
        printf(my_ip);

        printf("\n");
        printf("\nServer is listening for incoming connections...\n" );

    }

    // Accept connection
    if((new_socket = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen))<0){
        printf("TCP/IP: Failed to accept incoming connection.\n" );
        return -1;
    }
    printf("TCP/IP: Connection successfully established.\n" );
    printf("\n");
    fflush(stdout);

    // Connection successful. You can send and receive data now. 


    // Get message from client.
    printf("Waiting for message from client...\n");
    valread = read(new_socket, buffer, 1024);
    printf("FROM CLIENT: %s\n", buffer);

    // Pass message to client.
    printf("Sending message to client...\n");
    char *hello = "serversayshello";
    send(new_socket, hello, strlen(hello), 0);
    printf("Hello message sent\n");


    // Close file descriptors and end program.
    close(new_socket);
    close(server_fd);
    printf("End of program.");
    exit(EXIT_SUCCESS);
}

Java 客户端

import java.io.*;
import java.net.*;

public class MobileClientPrototype {

    public static void main(String[] args) throws IOException{

        // Used to get user input. 
        BufferedReader inFromUser = new BufferedReader(new InputStreamReader(System.in));

        System.out.print("Please enter IP Address:");
        String host = inFromUser.readLine();

        System.out.print("Please enter port number:");
        int port = Integer.parseInt(inFromUser.readLine());

        // Initialize Socket
        Socket clientSocket = new Socket(host, port);

        // Streams for communication with server
        DataOutputStream outToServer = new DataOutputStream(clientSocket.getOutputStream());
        BufferedReader inFromServer = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));

        // Ask user for string message which will be sent to server
        String sentence;
        System.out.print("\nPlease enter message:\n");
        sentence = inFromUser.readLine();

        // Send message to server
        outToServer.writeBytes(sentence + '\n');
        System.out.print("Message sent!\n\n");

        // Wait for response message from server        
        String responseSentence;
        System.out.print("Awaiting response...\n");
        responseSentence = inFromServer.readLine();
        // Print response
        System.out.println("RESPONSE FROM SERVER:\n" + responseSentence);

        // Close socket and end program
        System.out.print("\nTerminating connection...");
        clientSocket.close();
        System.out.print("\nEnd of program.");
    }
}

场景

我在本地机器上同时运行服务器和客户端程序。对于客户端,我输入127.0.0.1 作为 IP 和服务器打印的端口号。建立连接后,您可以向客户端输入一条消息,该消息将发送到服务器。之后,服务器以 serversayshello 响应。

它适用于一些短字符串,例如,我可以传输 hello

Screenshot 1

但是,对于一些较长的字符串,客户端一收到服务器的响应就会抛出SocketException: Connection reset

在这里,我正在尝试从客户端发送 hello 到服务器。这就是发生的事情:

Screenshot 2

同样值得注意的是,服务器显然没有收到完整的消息,因为它只打印了 hello from

错误

Exception in thread "main" java.net.SocketException: Connection reset
    at java.net.SocketInputStream.read(SocketInputStream.java:210)
    at java.net.SocketInputStream.read(SocketInputStream.java:141)
    at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
    at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
    at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
    at java.io.InputStreamReader.read(InputStreamReader.java:184)
    at java.io.BufferedReader.fill(BufferedReader.java:161)
    at java.io.BufferedReader.readLine(BufferedReader.java:324)
    at java.io.BufferedReader.readLine(BufferedReader.java:389)
    at MobileClientPrototype.main(MobileClientPrototype.java:38)

传输似乎适用于“hello”、“123456”、“foo”、“bar”、“g g”等短字符串。

一些不起作用的字符串是: “Hello”(带有大写 H)、“来自客户端的 hello”、“foobarfoobarfoobar”。

除此之外,我还没有想出一个模式。

是什么导致客户端在传输这些较长的消息时抛出异常?

【问题讨论】:

  • read() 返回 ssize_t,而不是 int。他们不一样。 read() 也不会 NUL-终止字符串值。
  • 'send(new_socket, hello, strlen(hello), 0);'.......'responseSentence = inFromServer.readLine();'。什么线路?

标签: java c sockets java-io


【解决方案1】:

“连接重置”有两个常见原因:

  1. 正在写入已被对等方关闭的连接。
  2. 关闭连接而不读取对等方已发送的所有数据。

还有其他的,但这些是最常见的。两者都是应用程序协议错误。

在您的情况下,线索是它适用于短消息,但不适用于长消息,原因是尽管 Java 代码正在发送由行终止符分隔的消息,但您只执行单个 read() in C 代码,并假设这将读取整个消息。 Posix 或 TCP/IP RFC 中的任何地方都不能保证这一点。

如果您希望收到由行终止符分隔的消息,则必须继续阅读,直到收到为止。这就是 Java 的 readLine() 方法所做的,也是您需要在 C 代码中模拟的。

【讨论】:

    【解决方案2】:

    您无法确定read() 是否在单个调用中返回所有内容(除非您查看读取的数据内容或read() 的返回值),很多时候它会返回部分数据,您将必须再次read() 才能读取剩余数据

    推荐的套接字通信方式是预先确定预期的数据,您可以决定具有固定长度的数据并继续调用read() 直到总字节数(将 read() 调用的返回值相加,当大于零表示成功读取的字节数)读取等于您预先确定的固定长度,或者您可以决定期待一个新行来指示数据结束并继续循环读取直到您收到'\n'

    【讨论】:

    • 感谢您的回答。我一定会把它添加到我的代码中。但是,它仍然没有解释异常。
    • “肯定知道”。 read() 返回传输的字节数。 DataInputStream.readFully() 已经存在,BufferedReadsr.readLine() 也是如此。
    • @EJP 我指的是他是否从客户端发送了“foobarfoobarfoobar”,当在服务器中读取返回时,他不知道当单个 read() 调用返回时它返回的是“foobarfoobarfoobar”还是只返回“foo”,所以建议循环读取,直到读取所有预期数据
    • “继续调用 read() 直到读取的总字节数等于固定长度”意味着将 read() 返回的计数相加,我将在答案中明确说明
    • 我指的是read() 返回一个计数,据此他知道传输了多少数据。我已经说了这么多。不要再重复了。
    猜你喜欢
    • 2017-12-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-05
    • 1970-01-01
    • 1970-01-01
    • 2019-02-25
    相关资源
    最近更新 更多