繁体   English   中英

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

[英]Why does my TCP server socket close after one client loses connection?

我真的不太了解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);
        }
    }
}

客户:

package server;

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.URL;
import java.util.Scanner;
import java.util.logging.Level;
import java.util.logging.Logger;
/*
 * @author RaKXeR
 */
public class Client {

    public static Socket sock;

    public static void main(String[] args) {
        try {
            sock = new Socket("localhost", 3074);
            new writeThread().start();
            new readThread().start();

        } catch (IOException ex) {
            Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public static class readThread extends Thread {
        @Override
        public void run() {
            boolean loop = true;
            while (loop) {
                try {

                    DataInputStream in = new DataInputStream(sock.getInputStream());
                    //BufferedReader in = new BufferedReader(new InputStreamReader(sock.getInputStream()));
                    String msg = in.readUTF();
                    //String msg = in.readLine();
                    System.out.println(msg);

                } catch (IOException ex) {
                    System.out.println("read error");
                    Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
                    loop = false;
                }
            }
        }
    }

    public static class writeThread extends Thread {
        @Override
        public void run() {
            boolean loop = true;
            while(loop) {
                try {
                    DataOutputStream out = new DataOutputStream(sock.getOutputStream());
                    System.out.println("Type your message to the server: ");
                    Scanner scan = new Scanner(System.in);
                    out.writeUTF(scan.nextLine());

                } catch (IOException ex) {
                    System.out.println("write error");
                    Logger.getLogger(Server.class.getName()).log(Level.SEVERE, null, ex);
                    loop = false;
                }
            }
        }
    }

}

这个程序做得不多。 你打开一个服务器,它启动一个线程等待服务器套接字上的传入连接。 一旦你打开一个试图连接到服务器套接字的客户端,它接受连接,并在我的线程数组列表中添加一个新线程,该线程开始等待来自该客户端/客户端套接字的消息。

这样,每当新客户端尝试连接时,我只需将他添加到另一个线程,并设法立即与两个客户端通信。

我现在设置的方式是,只要1个客户端向服务器发送消息,服务器就会向连接的每个客户端发送相同的消息以及线程的编号。 所有这些都按预期工作。

然而,我的问题是,一旦我阻止其中一个客户端运行,一切都会停止工作。 你最好自己测试代码,而不是我解释“停止工作”到底是什么意思,因为如果我知道我不认为我会问这个xD

所以,实际的问题: 是什么导致了这个问题,我可以在不改变代码的所有内容的情况下解决这个问题吗? 我觉得它与我重新使用服务器套接字创建几个客户端套接字有关,但我不确定。

编辑 :您可以在下面看到Jim Garrison的答案,他解释了问题所在 - 我试图将其他客户端的消息发送给离线的客户端,这会抛出异常并停止线程。 我所做的就是修复这个问题,将“终止”的“T”添加到已关闭的线程的名称中,并在发送信息之前检查线程的每个名称。 它并不完美,但它是我现在拥有的解决方案。 如果您使用此线程中的代码作为基础,我很抱歉,因为此代码不是那么好的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);
        }
    }
}

在一个客户端终止后,相应的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 for resource lock。 connections的另一个方法调用应该应用相同的修复。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM