为啥我的 TCP 服务器套接字在一个客户端断开连接后关闭?

Posted

技术标签:

【中文标题】为啥我的 TCP 服务器套接字在一个客户端断开连接后关闭?【英文标题】:Why does my TCP server socket close after one client loses connection?为什么我的 TCP 服务器套接字在一个客户端断开连接后关闭? 【发布时间】:2016-06-18 14:39:42 【问题描述】:

我对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;
                
            
        
    


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

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

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

然而,我的问题是,一旦我阻止其中一个客户端运行,一切都会停止工作。你最好自己测试代码,而不是我解释“停止工作”的确切含义,因为如果我知道我不认为我会问这个 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);
        
    

【问题讨论】:

崩溃后能否添加服务器日志? 您如何“阻止其中一个客户端运行”?我没有看到任何代码可以在任一端有序关闭连接。 你这里有一个主要的并发问题。 Socket 成员在您将其设置为静态的任何类中都不应该是静态的。 @JimGarrison 我没有制定“有序关闭”机制,因为我还没有实现此代码以供最终使用,只是想了解如何使其工作。此外,如果用户失去与 Internet 的连接,服务器将不会正常关闭,因此将其添加到代码中并不能帮助我解决问题。 【参考方案1】:

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

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

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

【讨论】:

谢谢,基于此,我几乎立即创建了一个解决方案。我会将您的答案标记为正确并编辑我的问题以包含我的解决方案【参考方案2】:

客户端断开连接后服务器套接字不会关闭。问题是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 不是线程安全的,我在示例代码中使用同步 来进行资源锁定。 connections 的另一个方法调用应该应用相同的修复。

【讨论】:

以上是关于为啥我的 TCP 服务器套接字在一个客户端断开连接后关闭?的主要内容,如果未能解决你的问题,请参考以下文章

使用TCP/IP传输电信号:断开连接(4次挥手)并删除套接字

为啥当客户端断开连接时这个简单的 websocket 代码会抛出?

为啥我与 Apple APNS 的 TCP 连接挂起并强行断开连接

如何在C中编写一个函数来断开客户端与服务器的连接? [复制]

Python-TCP服务端程序开发

C# 客户端 - 服务器套接字断开处理