简体   繁体   中英

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

I don't really know much of the theory behind TCP socket communication, but out of practice, I have achieved the following code:

Server:

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);
        }
    }
}

Client:

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;
                }
            }
        }
    }

}

This program doesn't do much. you open up a server, and it starts a thread waiting for incoming connections on the server socket. once you open up a client that tries to connect to the server socket, it accepts the connection, and adds a new thread to my thread array list, which starts waiting for messages from that client/client socket.

This way whenever a new client tries to connect, I just add him to another thread, and manage to talk to both clients at once.

The way I have it setup at the moment, as soon as 1 client sends the server a message, the server sends that same message to every client connected, along with the number of the thread. And all of this works as intended.

My problem, however, is that as soon as I stop one of the clients from running, everything stops working. It is better for you to test the code on your own instead of me explaining what exactly I mean by "stops working", because if I knew I don't think I would be asking this xD

So, The actual question: What is causing this, and can I fix this without changing everything about my code? I have a feeling it has something to do with me re-using the server socket to create several client sockets, but I'm not sure.

EDIT : You can see below Jim Garrison's answer, and he explains what the issue was - I was trying to send the message of other clients to the clients that were offline, and that would throw an exception and stop the thread. All I did to fix this was add a "T" of "Terminated" to the name of the threads that were closed, and checked every name of a thread before sending it info. It's not perfect, but it's the solution I have right now. If you ever use the code in this thread as a base, I'm sorry for you because this code isn't that good xD but anyway, if you do, I recomend you improve on it, as I will in my original program. Here is the fixed server code:

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);
        }
    }
}

After one client terminates, the corresponding ReplyThread gets a SocketException and terminates. However you don't clean up the connection array list. As each still-connected client sends a message you still attempt to send a reply to the now-closed client. This throws an exception terminating the ReplyThread for the current sending client.

In other words, after one client terminates, the ReplyThread for each remaining client will die when a message is received for that client.

The solution is to add code to handle connection terminations and ensure the correctness and consistency of the server's view of which connections are still active.

The server socket does NOT close after client lost connection. The issue is ReplyThread intent to write data to closed socket and produce a exception. A possible solution is :

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();
                }
            }
        }
    }
}

Since ArrayList is not thread safe, I use synchronized for resource lock in the sample code. The other method call of connections should apply the same fix.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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