套接字不能像 LAN 或 NET 那样工作,但在本地完美

Sockets don't work as excpected with LAN or NET, but perfectly locally

我正在使用 Java 编写网络软件,但我在通过 "true" 网络使用我的应用程序时遇到了实际问题。

让软件成为主机,监听客户端连接。 这是我的服务器循环:

public void run() {                                                 
    while (mServerSocket != null) {                                        
        try {                                                              
            Socket wClient = mServerSocket.accept();                       
            System.out.println("Client connecté");                         
            wClient.setSoTimeout(50);                                      
            wClient.setTcpNoDelay(false);                                  
            Client c = new Client(wClient);                                
            synchronized(this) {                                           
                mWaitingClients.add(c);                                    
                c.start();                                                 
            }                                                              
        } catch(Exception ex) {                                              
            System.out.println("Server error : " + ex.getMessage());       
        }                                                                  
    }                                                                      
}                                                                          

当客户端试图连接到服务器时,我使用这个函数:

public Client connect(InetAddress addr, int port) throws Exception {
    Socket socket = new Socket(addr, port);                        
    socket.setSoTimeout(50);                                       
    socket.setTcpNoDelay(false);                                   
    Client c = new Client(socket);                                 
    c.start();                                                     
    return c;                                                      
}                                                                  

这是客户端循环:

public void run() {                                                             
    try {                                                                       
        ObjectOutputStream out = new ObjectOutputStream(mSocket.getOutputStream());                                                                                 
        ObjectInputStream in = new ObjectInputStream(mSocket.getInputStream()); 

        while(mSocket.isConnected() && !mSocket.isClosed()) {                   
            for (int i = 0; i < mOutMessages.size(); i++) {                     
                Message msg = mOutMessages.get(i);                              
                out.writeObject(msg);                                           
            }                                                                   
            out.flush();                                                        
            mOutMessages.clear();                                               

            Thread.sleep(50);                                                   
            out.reset();                                                        

            while(true) {                                                       
                try {                                                           
                    Message m = (Message) in.readObject();                      
                    mInMessages.add(m);                                         
                } catch (Exception e) {                                         
                    break;                                                      
                }                                                               
            }                                                                   

            Thread.sleep(50);                                                   
        }                                                                       
    } catch(Exception ex) {                                                     
        try {                                                                   
            mSocket.close();                                                    
        } catch(Exception exx) {                                                
            exx.printStackTrace();                                              
        }                                                                       
        ex.printStackTrace();                                                   
    }                                                                           
}                                                                               

程序的其他部分做Message,放到Client的Output列表(mOutMessages)中。 程序的其他部分从Client的mInMessages读取Message。

但这有点不对劲。它在本地运行良好(服务器和客户端在同一台计算机上),但在使用两台计算机(使用 LAN 或通过 Internet)时会失败或有危险(某些消息已发送但从未收到)。 服务器曾经检测到来自客户端的连接,向客户端发送 "handshake" 消息,但客户端从未收到它们。

与 Java 相比,我更像是一名 C 程序员,而且我在使用 libc 套接字时从未遇到过此类问题,那么,为什么我的做法是错误的?

谢谢!

编辑: 我的服务器是使用此函数创建的:

public void open(int port) throws Exception {
    mServerSocket = new ServerSocket(port);  
    start(); // Call the run mentionned above.                                
}                                            

编辑: 这是我的解决方案,也许它并不完美,但它确实有效!

public void run() {                                                        
    try {                                                                  
        BufferedOutputStream buf_out = new BufferedOutputStream(           
            mSocket.getOutputStream()                                      
        );                                                                 
        BufferedInputStream buf_in = new BufferedInputStream(              
            mSocket.getInputStream()                                       
        );                                                                 
        ObjectOutputStream out = new ObjectOutputStream(buf_out);          
        out.flush();                                                       
        ObjectInputStream in = new ObjectInputStream(buf_in);              

        while(mSocket.isConnected() && !mSocket.isClosed()) {              
            for (int i = 0; i < mOutMessages.size(); i++) {                
                Message msg = mOutMessages.get(i);                         
                out.writeObject(msg);                                      
                out.flush();                                               
            }                                                              
            mOutMessages.clear();                                          
            out.reset();                                                   

            while(true) {                                                  
                try {                                                      
                    Message m = (Message) in.readObject();                 
                    mInMessages.add(m);                                    
                } catch (Exception e) {                                    
                    break;                                                 
                }                                                          
            }                                                              
        }                                                                  
    } catch(Exception ex) {                                                
        try {                                                              
            mSocket.close();                                               
        } catch(Exception exx) {                                           
            exx.printStackTrace();                                         
        }                                                                  
        ex.printStackTrace();                                              
    }                                                                      

如果我没理解错的话,client和server都使用了run方法。如果客户端和服务器碰巧同时写入了足够大的消息(不适合所涉及的缓冲区)那么你会得到一个 死锁 因为双方都没有继续阅读(这会耗尽整个缓冲区) .由于网络延迟,这可能只发生在非本地场景中,即可能有足够的时间在 mOutMessages 缓冲区中堆积足够多的消息。

请注意 Socket.setSoTimeout 的文档(您使用的)仅说明它会影响 read()s。 (例如,在我的JDK中,ObjectOutputStream似乎使用了一个缓冲区大小为1024字节的BlockDataOutputStream)。

我建议为 reading/writing 使用单独的线程,或者(如果您知道最大消息大小)使用足够大的缓冲区(通过将 SocketOutputStream 包装在 BufferedOutputStream ).如果您选择更大的缓冲区,您可能还希望一次写入一条消息(并尝试在每条消息之后读取消息)。