Java 多线程客户端/服务器 - java.net.SocketException:套接字已关闭

2024-01-07

我必须使用 Java 的套接字 api 编写多线程客户端和服务器。客户端和服务器都是多线程的,因此服务器可以处理多个连接,客户端可以测试服务器处理连接的能力。

我的代码在这里:https://github.com/sandyw/Simple-Java-Client-Server https://github.com/sandyw/Simple-Java-Client-Server

我有几个可能相关的问题。一、偶尔一个客户端线程会抛出异常

java.net.SocketException: Socket closed
    at java.net.SocketInputStream.socketRead0(Native Method)
    at java.net.SocketInputStream.read(SocketInputStream.java:129)
    at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:264)
    at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:306)
    at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:158)
    at java.io.InputStreamReader.read(InputStreamReader.java:167)
    at java.io.BufferedReader.fill(BufferedReader.java:136)
    at java.io.BufferedReader.readLine(BufferedReader.java:299)
    at java.io.BufferedReader.readLine(BufferedReader.java:362)
    at ClientThread.run(ClientThread.java:45)
    at java.lang.Thread.run(Thread.java:680)

从它的位置来看,这意味着服务器在客户端完成读取之前正在关闭其套接字。我怎样才能防止这种情况发生?

另外,甚至计算抛出异常的线程Socket closed例外,似乎并非所有服务器线程都在发送其输出。例如,30 个服务器线程中的 23 个将发送其输出,但只有 22 个客户端线程将接收任何内容。通信显然在某个地方丢失了,我该如何防止这种情况发生?


您的问题是在 ClientThread 中:

private static Socket socket = null;

这意味着所有线程都为 ClientThread 的所有实例共享同一个套接字实例。这意味着您的套接字将与会话状态不同步。你们的对话是:

客户声明:

  1. 客户端连接
  2. 客户端发送请求
  3. 客户端等待响应
  4. 客户端收到响应
  5. 客户端关闭套接字(应由每个客户端完成)。

服务器状态:

  1. 服务器等待客户端
  2. 服务器读取请求
  3. 服务器处理命令
  4. 服务器发送响应
  5. 服务器关闭套接字

但是,您有 30 个对话都试图同时处于不同的状态,而服务器和客户端无法跟踪它们。第一个线程创建套接字,发送请求,移动到状态 2,在等待时,另一个线程创建另一个套接字,将其写入移动到状态 2,当第一个线程唤醒时,它开始与线程 2 创建的新套接字进行通信尚未完成处理其命令。现在第三个启动并再次覆盖该引用,依此类推。第一个命令永远不会被读取,因为当线程 2 覆盖它时,它丢失了对原始套接字的引用,并且它开始读取线程 2 的命令并将其吐出。一旦线程 1 到达 close 语句,它就会关闭套接字,而其他线程正在尝试读/写它,并抛出异常。

本质上,每个 ClientThread 都应该创建自己的套接字实例,然后每个对话都可以独立于其他正在进行的对话进行。想想你是否将客户端编写为单线程。然后启动两个单独的进程(运行 Java 应用程序两次)。每个进程都会创建自己的套接字,每个对话都会独立工作。您现在拥有的是一个带有 30 个线程的套接字,通过一个扩音器向服务器发出命令。当每个人都用同一个扩音器大声喊叫时,工作就无法有序进行。

因此,总而言之,更改从 ClientThread 中的套接字成员中删除静态修饰符,它应该开始工作得很好。

顺便说一句,永远不要将此代码发布到世界上。它存在严重的安全问题,因为客户端可以在服务器进程运行的安全级别上执行任何命令。因此,任何人都可以轻松地拥有您的机器或相对轻松地获取您的帐户。从客户端执行这样的命令意味着他们可以发送:

sudo cat /etc/passwd

例如,捕获您的密码哈希值。我认为你只是在学习,但我觉得你应该警惕你正在做的事情以确保安全。

另一件事是,如果对话按预期进行,服务器只会关闭套接字。您确实应该将 close() 调用移至您拥有的 try 块上的 finally 块中。否则,如果客户端过早关闭其套接字(这种情况发生),那么您的服务器将泄漏套接字,并最终耗尽操作系统中的套接字。

public void run() {
   try {
   } catch( SomeException ex ) {
      logger.error( "Something bad happened", ex );
   } finally {
      out.close();   <<<< not a bad idea to try {} finally {} these statements too.
      in.close();    <<<< not a bad idea to try {} finally {} these statements too.
      socket.close();
   }
}

您可能想要探索的另一件事是在服务器上使用线程池,而不是为获得的每个新连接创建一个线程。在您的简单示例中,很容易使用它,并且它有效。但是,如果您正在构建真实的服务器,线程池有两个主要贡献。 1. 创建线程会产生相关的开销,因此您可以通过让线程等待服务传入请求来获得一些性能响应时间。您可以节省一些时间来回复客户。 2. 更重要的是,物理计算机不能无休止地创建线程。如果您有很多客户说超过 1000 个,您的机器将很难回答所有使用 1000 个线程的客户。线程池意味着您创建最大数量的线程,例如 50 个线程,并且每个线程将被多次使用来处理每个请求。当新连接进入时,它们会等待线程释放后再进行处理。如果您收到太多连接,客户端将超时,而不是破坏您的计算机并需要重新启动。它可以让您更快地处理更多请求,同时防止同时出现太多连接而导致死亡。

最后,由于许多合理的原因,可能会发生关闭套接字异常。通常,如果客户端在会话过程中关闭,服务器就会收到该异常。发生这种情况时,最好正确关闭并清理自己。你永远无法阻止这是我的观点。你只需要回应它。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java 多线程客户端/服务器 - java.net.SocketException:套接字已关闭 的相关文章

随机推荐