一尘不染

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

java

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

我的代码在这里: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个将接收任何内容。通讯显然在某处迷路了,如何防止这种情况发生?


阅读 199

收藏
2020-12-03

共1个答案

一尘不染

您的问题是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的套接字成员中删除static修饰符,它应该可以很好地开始工作。

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

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个线程,并且每个线程将被多次使用以处理每个请求。随着新连接的进入,它们在处理线程之前等待线程释放。如果连接过多,客户端将超时,而不是破坏需要重新启动的计算机。它可以让您更快地处理更多请求,同时避免一次连接太多连接而导致死亡。

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

2020-12-03