为啥当未接受的套接字发送消息时,套接字不会抛出错误?

Posted

技术标签:

【中文标题】为啥当未接受的套接字发送消息时,套接字不会抛出错误?【英文标题】:Why Socket does not throw error when unaccepted socket send a message?为什么当未接受的套接字发送消息时,套接字不会抛出错误? 【发布时间】:2021-10-29 08:56:01 【问题描述】:

我的目标是创建一个接受 1 个连接的服务器客户端,然后服务器以代码 0 退出。

我的预期结果:第二个客户端应该无法发送消息,因为ServerSocket 要么已关闭,要么它不接受带有serverSocket.accept() 的第二个客户端。

我的实际结果:第二个客户端可以发送消息而不会抛出java.net.ConnectException: Connection refused: connect的错误

我的实际结果EXTRA:如果你用不同的方法创建第二个客户端,它会失败。

java/framework/Client.java

package framework;

import java.io.IOException;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

public class Client 
  private final Socket socket;

  public Client() throws IOException 
    socket = new Socket("localhost", 80);
  

  public void send(String message) throws IOException 
    socket.getOutputStream().write(message.getBytes(StandardCharsets.US_ASCII));
  


java/framework/Server.java

package framework;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

public class Server implements Runnable 
  private static final Logger logger = LoggerFactory.getLogger(Server.class);

  private final ServerSocket serverSocket;

  public Server() throws IOException 
    serverSocket = new ServerSocket(80);
  

  @Override
  public void run() 
    try (serverSocket) 
      logger.debug("Sever socket started at port " + serverSocket.getLocalPort());

      final Socket socket = serverSocket.accept();
      logger.debug("Socket accepted: " + socket.getInetAddress());

      final BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
      final String line = reader.readLine();
      logger.debug("Input received: " + line);

     catch (IOException e) 
      logger.warn("something went wrong" + e);
    

    logger.debug("Server Socket closed.");
  


test/java/ServerClientTest.java

import framework.Client;
import framework.Server;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;

public class ServerClientTest 
  private static final Logger logger = LoggerFactory.getLogger(ServerClientTest.class);

  @BeforeAll
  static void if_server_can_start_then_success() 
    logger.debug("beforeAll");
    try 
      Server server = new Server();
      Thread thread = new Thread(server);
      thread.start();
     catch (IOException ignored) 
      Assertions.fail("failed to start the server");
    
  

  @Test
  void if_1st_client_can_send_message_to_server_then_success() 
    try 
      Client client = new Client();
      client.send("Hello from 1st client\r\n");

      Client client1 = new Client();
      client1.send("Hello from 2nd client\r\n");
     catch (IOException e) 
      Assertions.fail("1st client unable to send message to server. " + e);
    
  


编辑:

    在Client类send方法中做logger.debug("Socket: " + socket.getInputStream()),神奇的是,socket会抛出java.net.SocketException. Connection reset by peer: socket write error。同时,调用 socket.isConnected() 将返回 true。是的,我知道,打电话给getInputStream 并记录它很奇怪,但它使测试按预期工作。

原代码日志:

23:37:02.418 [main] DEBUG ServerClientTest - beforeAll
23:37:02.442 [Thread-0] DEBUG framework.Server - Sever socket started at port 80

23:37:02.491 [Thread-0] DEBUG framework.Server - Socket accepted: /127.0.0.1
23:37:02.492 [Thread-0] DEBUG framework.Server - Input received: Hello from 1st client
23:37:02.493 [Thread-0] DEBUG framework.Server - Server Socket closed.


Process finished with exit code 0

在客户端类send方法中调用socket.getInputStream()的日志。

23:39:48.098 [main] DEBUG ServerClientTest - beforeAll

23:39:48.153 [Thread-0] DEBUG framework.Server - Sever socket started at port 80
23:39:48.188 [Thread-0] DEBUG framework.Server - Socket accepted: /127.0.0.1
23:39:48.188 [main] DEBUG framework.Client - Socket: java.net.SocketInputStream@6bf0219d
23:39:48.190 [Thread-0] DEBUG framework.Server - Input received: Hello from 1st client
23:39:48.191 [main] DEBUG framework.Client - Socket: java.net.SocketInputStream@36d585c
23:39:48.191 [Thread-0] DEBUG framework.Server - Server Socket closed.


org.opentest4j.AssertionFailedError: clients failed to send message.

    at org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:43)
    at org.junit.jupiter.api.Assertions.fail(Assertions.java:129)
    at ServerClientTest.if_clients_can_send_message_to_server_then_success(ServerClientTest.java:35)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:688)
    at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
    at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
    at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:210)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:206)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:131)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:65)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:96)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:75)
    at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:71)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:220)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:53)
Caused by: java.net.SocketException: Connection reset by peer: socket write error
    at java.base/java.net.SocketOutputStream.socketWrite0(Native Method)
    at java.base/java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:110)
    at java.base/java.net.SocketOutputStream.write(SocketOutputStream.java:138)
    at framework.Client.send(Client.java:20)
    at ServerClientTest.if_clients_can_send_message_to_server_then_success(ServerClientTest.java:33)
    ... 65 more



Process finished with exit code -1

【问题讨论】:

为什么不 1) 为 backlog 参数指定 1,即 serverSocket = new ServerSocket(80, 1);,以及 2) 在第一次 accept() 调用后关闭服务器套接字? @PresidentJamesK.Polk 1)我试图将积压工作设置为 1,正如你所说,它什么都不做 2)try-with-resources 将立即关闭它,检查 isClosed() 也返回 true。 , closing 接受后的服务器会杀死我想要理解的测试。 @user16320675 带来了一个很好的讨论点。 @user16320675 你能澄清一下standard options是什么意思吗? @user16320675 你的意思是backlog 来自ServerSocket 吗?我尝试使用 backlog 并将其设置为 1。我无法从 Socket 找到 backlog。 docs 确实暗示backlog 参数是一个建议而不是硬限制。我很惊讶服务器套接字关闭后第二个连接成功。我仍然会在接受后立即关闭服务器套接字。 【参考方案1】:

问题在于您的客户端创建。创建两个客户端之间没有延迟,这会导致排除连接错误。添加延迟总是会导致连接错误。

static void if_1st_client_can_send_message_to_server_then_success() 
    try 
        Client client = new Client();
        client.send("Hello from 1st client\r\n");

        Thread.sleep(1000);

        Client client1 = new Client();
        client1.send("Hello from 2nd client\r\n");
     catch (Exception e) 
        Assertions.fail("1st client unable to send message to server. " + e);
    

【讨论】:

以上是关于为啥当未接受的套接字发送消息时,套接字不会抛出错误?的主要内容,如果未能解决你的问题,请参考以下文章

ZeroMQ 套接字 Recv() 抛出“上下文已终止”异常 - 为啥以及如何恢复?

为啥当客户端断开连接时这个简单的 websocket 代码会抛出?

无法接受服务器上的套接字连接

socket 错误之:OSError: [WinError 10057] 由于套接字没有连接并且(当使用一个 sendto 调用发送数据报套接字时)没有提供地址,发送或接收数据的请求没有被接受。

套接字上的 BrokenPipeError

Java 客户端/服务器套接字损坏的管道