Java 为什么我的TCP服务器套接字在一个客户端失去连接后关闭?

Java 为什么我的TCP服务器套接字在一个客户端失去连接后关闭?,java,multithreading,sockets,tcp,Java,Multithreading,Sockets,Tcp,我对TCP套接字通信背后的理论知之甚少,但在实践中,我实现了以下代码: 服务器: public class Server { public static volatile ArrayList<ReplyThread> connections = new ArrayList<>(); public static void main(String[] args) { new AcceptThread().start(); }

我对TCP套接字通信背后的理论知之甚少,但在实践中,我实现了以下代码:

服务器:

public class Server {

    public static volatile ArrayList<ReplyThread> connections = new ArrayList<>();

    public static void main(String[] args) {
        new AcceptThread().start();
    }

    private static class AcceptThread extends Thread {
        @Override
        public void run() {
            ServerSocket inSock;

            try {
                inSock = new ServerSocket(3074);

                boolean loop = true;
                while(loop) {
                    System.out.println("waiting for next connection");
                    connections.add(new ReplyThread(inSock.accept()));
                    System.out.println("connection made");
                    connections.get(connections.size() - 1).setName(""+(connections.size() - 1));
                    connections.get(connections.size() - 1).start();
                }
                inSock.close();

            } catch (IOException ex) {
                System.out.println(ex.getMessage());
            }
        }
    }

    public static class ReplyThread extends Thread {
        private static Socket sock;
        private DataOutputStream out;

        public ReplyThread(Socket newSock) {
            sock = newSock;
        }

        @Override
        public void run() {
            try {
                DataInputStream in = new DataInputStream(sock.getInputStream());
                out = new DataOutputStream(sock.getOutputStream());

                boolean loop = true;
                while(loop) {
                    String msg = in.readUTF();
                    System.out.println(msg);
                    for (ReplyThread thread : connections) {
                        thread.output(sock, msg);
                    }
                }

                in.close();
            } catch (SocketException ex) {
                Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
                System.out.println("Connection terminated.");
                this.interrupt();
                System.out.println(this.getName() + " I was interrupted");
            } catch (IOException ex) {
                Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        public final void output(Socket sock, String message) throws IOException {
            this.out.writeUTF(this.getName() + ": " + message);
        }
    }
}
这个节目没什么用。打开一个服务器,它会启动一个线程,等待服务器套接字上的传入连接。打开尝试连接到服务器套接字的客户端后,它将接受连接,并将新线程添加到我的线程数组列表中,该列表将开始等待来自该客户端/客户端套接字的消息

这样,每当一个新客户机尝试连接时,我只需将他添加到另一个线程,并设法同时与两个客户机进行对话

按照我目前的设置方式,只要一个客户机向服务器发送一条消息,服务器就会向每个连接的客户机发送相同的消息以及线程的数量。所有这些都按预期进行

然而,我的问题是,一旦我停止其中一个客户端的运行,一切都停止工作。您最好自己测试代码,而不是我解释“停止工作”的确切含义,因为如果我知道我不会问这个xD

所以,真正的问题是:是什么导致了这种情况,我能在不改变代码的情况下修复它吗?我感觉这与我重新使用服务器套接字创建几个客户端套接字有关,但我不确定

编辑:下面是Jim Garrison的回答,他解释了问题所在-我试图将其他客户端的消息发送给脱机的客户端,这将引发异常并停止线程。我所做的一切就是在关闭的线程名称中添加一个“T”和“Terminated”,并在发送信息之前检查每个线程的名称。这并不完美,但这是我现在的解决方案。如果您曾经使用此线程中的代码作为基础,我为您感到抱歉,因为此代码不是很好的xD,但无论如何,如果您这样做,我建议您改进它,正如我在原始程序中所做的那样。以下是固定服务器代码:

package server;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Server {

    public static volatile ArrayList<ReplyThread> connections = new ArrayList<>();

    public static void main(String[] args) {
        new AcceptThread().start();
    }

    private static class AcceptThread extends Thread {
        @Override
        public void run() {
            ServerSocket inSock;

            try {
                inSock = new ServerSocket(3074);

                boolean loop = true;
                while(loop) {
                    System.out.println("waiting for next connection");
                    connections.add(new ReplyThread(inSock.accept()));
                    System.out.println("connection made");
                    connections.get(connections.size() - 1).setName(""+(connections.size() - 1));
                    connections.get(connections.size() - 1).start();
                }
                inSock.close();

            } catch (IOException ex) {
                System.out.println(ex.getMessage());
            }
        }
    }

    public static class ReplyThread extends Thread {
        private static Socket sock;
        private DataOutputStream out;

        public ReplyThread(Socket newSock) {
            sock = newSock;
        }

        @Override
        public void run() {
            try {
                DataInputStream in = new DataInputStream(sock.getInputStream());
                out = new DataOutputStream(sock.getOutputStream());

                boolean loop = true;
                while(loop) {
                    String msg = in.readUTF();
                    System.out.println(msg);
                    for (ReplyThread thread : connections) {
                        if (!thread.getName().contains("T")) thread.output(sock, msg);
                    }
                }

                in.close();
            } catch (SocketException ex) {
                //Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
                System.out.println("Connection terminated.");
                this.setName(this.getName() + "T");
                this.interrupt();
                System.out.println(this.getName() + " I was interrupted");
            } catch (IOException ex) {
                Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        public final void output(Socket sock, String message) throws IOException {
            this.out.writeUTF(this.getName() + ": " + message);
        }
    }
}
包服务器;
导入java.io.DataInputStream;
导入java.io.DataOutputStream;
导入java.io.IOException;
导入java.net.ServerSocket;
导入java.net.Socket;
导入java.net.SocketException;
导入java.util.ArrayList;
导入java.util.logging.Level;
导入java.util.logging.Logger;
公共类服务器{
public static volatile ArrayList connections=new ArrayList();
公共静态void main(字符串[]args){
新建AcceptThread().start();
}
私有静态类AcceptThread扩展线程{
@凌驾
公开募捐{
内座;
试一试{
inSock=新服务器套接字(3074);
布尔循环=真;
while(循环){
System.out.println(“等待下一个连接”);
add(新的ReplyThread(inSock.accept());
系统输出打印项次(“连接完成”);
connections.get(connections.size()-1).setName(“+(connections.size()-1));
connections.get(connections.size()-1.start();
}
inSock.close();
}捕获(IOEX异常){
System.out.println(例如getMessage());
}
}
}
公共静态类ReplyThread扩展线程{
专用静电插座;
私有数据输出流输出;
公共ReplyThread(套接字新闻组){
袜子=新袜子;
}
@凌驾
公开募捐{
试一试{
DataInputStream in=新的DataInputStream(sock.getInputStream());
out=新的DataOutputStream(sock.getOutputStream());
布尔循环=真;
while(循环){
字符串msg=in.readUTF();
System.out.println(msg);
for(ReplyThread线程:连接){
如果(!thread.getName()包含(“T”))thread.output(sock,msg);
}
}
in.close();
}捕获(SocketException例外){
//Logger.getLogger(Server.class.getName()).log(Level.SEVERE,null,ex);
System.out.println(“连接终止”);
this.setName(this.getName()+“T”);
这个。中断();
System.out.println(this.getName()+“我被中断”);
}捕获(IOEX异常){
Logger.getLogger(Server.class.getName()).log(Level.SEVERE,null,ex);
}
}
公共最终无效输出(套接字套接字、字符串消息)引发IOException{
this.out.writeUTF(this.getName()+“:”+消息);
}
}
}

在一个客户端终止后,相应的
ReplyThread
获取一个
SocketException
并终止。但是,您不会清理连接数组列表。当每个仍然连接的客户端发送消息时,您仍然尝试向现在关闭的客户端发送回复。这会引发异常,终止当前发送客户端的
ReplyThread

换句话说,在一个客户端终止后,当接收到该客户端的消息时,每个剩余客户端的
ReplyThread
将失效


解决方案是添加代码来处理连接终止,并确保服务器查看哪些连接仍然处于活动状态的正确性和一致性。

客户端断开连接后,服务器套接字不会关闭。问题在于
ReplyThread
意图将数据写入关闭的套接字并产生异常。一种可能的解决办法是:

while(loop) {
    String msg = in.readUTF();
    System.out.println(msg);
    /*for (ReplyThread thread : connections) {
        thread.output(sock, msg);
    }*/
    synchronized (connections) {
        for (Iterator iterator = connections.iterator(); iterator.hasNext();) {
            ReplyThread thread = (ReplyThread) iterator.next();
            if (thread.sock.isClosed()) {
                iterator.remove();
            } else {
                try {
                    thread.output(thread.sock, msg);
                } catch (IOException e0) {
                    iterator.remove();
                }
            }
        }
    }
}
由于
ArrayList
不是线程安全的,因此我在示例代码中使用synchronized作为资源锁。
connecti的另一个方法调用
while(loop) {
    String msg = in.readUTF();
    System.out.println(msg);
    /*for (ReplyThread thread : connections) {
        thread.output(sock, msg);
    }*/
    synchronized (connections) {
        for (Iterator iterator = connections.iterator(); iterator.hasNext();) {
            ReplyThread thread = (ReplyThread) iterator.next();
            if (thread.sock.isClosed()) {
                iterator.remove();
            } else {
                try {
                    thread.output(thread.sock, msg);
                } catch (IOException e0) {
                    iterator.remove();
                }
            }
        }
    }
}