【问题标题】:Creating a monitor of messages between applications with Java使用 Java 创建应用程序之间的消息监视器
【发布时间】:2015-01-07 22:33:30
【问题描述】:

我正在尝试创建两个应用程序之间的消息监视器。这个想法是这个监视器在简单的客户端/服务器应用程序中间工作,并将消息记录到标准输出。该程序必须防止客户端/服务器出现故障(断开连接、超时等)。在代码中,我将客户端称为“起源”,将服务器称为“命运”。当前的问题是当服务器死机时,我的程序不知道,当客户端收到新消息时,出现此错误“软件导致连接中止:套接字写入错误”。当服务器重新启动时,此错误继续存在。我想当我在代码中询问“if (!socketToDestiny.isConnected())”时,它并没有真正连接。我确信问题在于我如何管理流中的“关闭”。

这是程序的代码,希望你能帮助我。

package interceptorprocess;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;


public class GenericInterceptorProcess implements Runnable
{
private final String prefix_log_messages = "[CONNECTOR]";

//COMMUNICATION'S ORIGIN'S VARIABLES
ServerSocket serverSocketLocal;
Socket socketForLocal;
DataInputStream streamFromOrigin;
DataOutputStream streamToOrigen;
int len_message_from_origen;
byte[] buffer_msg_origin = new byte[4096];
byte[] message_origin = null;

//COMMUNICATION'S DESTINY'S VARIABLES
Socket socketToDestiny;
DataInputStream streamFromDestiny;
DataOutputStream streamToDestiny;
int len_message_from_destiny;
byte[] buffer_msg_destiny = new byte[4096];
byte[] message_destiny;

@Override
public void run() 
{
    //OCCASIONAL USE
    String aux;

    try
    {
        logger("STARTING SERVER --- PORT NUMBER: " + "1234");

        //CREATING THE LOCAL SERVER SOCKET
        serverSocketLocal = new ServerSocket(1234);

        //CREATING THE DESTINITY CONNECTION WITH 15 TIMEOUT'S SECONDS
        socketToDestiny = new Socket();
        socketToDestiny.setSoTimeout(15000);

        //THIS LOOP MAINTAINS THE CONNECTIVITY WITH ONE CLIENT AT TIME
        while ( true )
        {
            logger("WAITING FOR A CONNECTION OF A CLIENT...");
            socketForLocal = serverSocketLocal.accept();

            //CREATING THE ORIGIN'S STREAMS
            streamFromOrigin = new DataInputStream(socketForLocal.getInputStream());
            streamToOrigen = new DataOutputStream(socketForLocal.getOutputStream());

            logger("CONNECTED CLIENT: " + socketForLocal.getRemoteSocketAddress() );

            //THIS LOOP MAINTAINS THE MESSAGES'S CHANGES
            while ( true )
            {
                logger("WAITING FOR A MESSAGE..");
                len_message_from_origen = streamFromOrigin.read(buffer_msg_origin);

                if ( len_message_from_origen < 0 )
                {
                    closeOriginStream();
                    break;
                }

                message_origin = new byte[len_message_from_origen];

                //SAVE THE ORIGIN'S MESSAGE INTO AN ARRAY WHO HAS THE EXACT SIZE OF THIS MESSAGE
                System.arraycopy(buffer_msg_origin, 0, message_origin, 0, len_message_from_origen);

                aux = new String(message_origin);
                logger("RECEIVED MESSAGE FROM ORIGIN: " + aux);

                //TRY TO CONNECT TO DESTINY
                try
                {
                    if ( !socketToDestiny.isConnected() )
                        socketToDestiny.connect(new InetSocketAddress("10.10.200.200",1234),5000);
                }
                catch(IOException ex)
                {
                    logger("CONNECTION REJECTED BY DESTINY: " + ex.getMessage());
                    continue;
                }

                //CREATING THE DESTINY'S STREAMS
                streamFromDestiny = new DataInputStream(socketToDestiny.getInputStream());
                streamToDestiny = new DataOutputStream(socketToDestiny.getOutputStream());

                logger("SENDING MESSAGE TO DESTINY: " + aux);

                //I HAD TO PUT THIS BLOCK BECAUSE IF THE DESTINY APPLICATIONS FAILS
                //OR NOT ANSWER, THE PROGRAM MUST KEEP LISTENING THE FOLLOWING MESSAGES
                try
                {
                    //SENDING MESSAGE TO DESTINY
                    streamToDestiny.write(message_origin);

                    //READING THE ANSWER MESSAGE
                    logger("READING MESSAGE FROM DESTINY...");

                    len_message_from_destiny = streamFromDestiny.read(buffer_msg_destiny);
                }

                //IN ONE OF THE FOLLOWINGS TWO CATCHS I GET THE ERROR 
                catch (SocketTimeoutException ex)
                {
                    logger("IT DIDN'T COULD RETRIEVE A MESSAGE FROM DESTINY: " + ex.getMessage());
                    continue;
                }
                catch (SocketException ex)
                {
                    //THE "socketToDestiny.isConnected()" ALWAYS RETURNS TRUE SINCE THE FIRST SUCCESSFULLY 
                    //CONNECTION, AFTER THAT, IF THE SOCKET IS DISCONNECTED, IT REMAINS RETURNING "true".
                    //THUS, I HAD TO MAKE THE NEXT CODE BLOCK
                    streamFromDestiny.close();
                    streamToDestiny.close();
                    socketToDestiny.close();

                    socketToDestiny = new Socket();
                    socketToDestiny.setSoTimeout(confs.timeout_destiny);
                    socketToDestiny.connect(new InetSocketAddress(confs.destiny_ip,confs.destiny_port),confs.timeout_connections);

                    streamFromDestiny = new DataInputStream(socketToDestiny.getInputStream());
                    streamToDestiny = new DataOutputStream(socketToDestiny.getOutputStream());

                    logger("TRYING TO RECONNECT WITH DESTINY AND SEND THE MESSAGE... ");
                    logger("READING MESSAGE FROM DESTINY AFTER ERROR...");
                    len_message_from_destiny = streamFromDestiny.read(buffer_msg_destiny);
                }

                message_destiny = new byte[len_message_from_destiny];

                //SAVE THE DESTINY'S MESSAGE INTO AN ARRAY WHO HAS THE EXACT SIZE OF THIS MESSAGE
                System.arraycopy(buffer_msg_destiny, 0, message_destiny, 0, len_message_from_destiny);
                aux = new String(message_destiny);

                logger("RECEIVED MESSAGE FROM DESTINY " + aux);

                //SENDING THE ANSWER BACK TO THE ORIGIN
                logger("SENDING BACK THE MESSAGE TO ORIGIN...");
                streamToOrigen.write(message_destiny);

                logger("MESSAGE DELIVERED SUCCESSFULLY!");
            } //INTERNAL LOOP OF MESSAGES

        } //INTERNAL LOOP OF CLIENTS
    } //TRY
    catch(IOException ex ) 
    {
        logger("THE SERVICE DIED: " +  ex.getMessage() );
        ex.printStackTrace();
    } //CATCH

} //RUN

private void closeDestinyStream() throws IOException
{
    streamFromDestiny.close();
    streamToDestiny.close();
}

private void closeOriginStream() throws IOException
{
    streamFromOrigin.close();
    streamToOrigen.close();
}

private void closeAll() throws IOException
{
    closeDestinyStream();
    closeOriginStream();
}

private void logger(String message)
{
    System.out.println(Utilidades.date() + " " + prefix_log_messages + " " + message);
}
}

问候!

对不起,我的英语不是母语。

【问题讨论】:

  • 使用传统的客户端/服务器模型,当有趣的事情出现时简单地附加到日志文件不是更容易吗?我不知道你为什么要听和重复你说的话,而你自己可以很容易地写下来。我认为不需要中间人。
  • 您好,是的,我同意您的观点,但我无法访问客户端/服务器的代码,而是可以访问 IP 和端口配置。该程序用于测试目的。如果我们这样做,接下来,我们可以更改消息的某些部分。
  • 尝试注释掉您对socketToDestiny.setSoTimeout()的调用
  • MMMM 我将失去“超时”功能,我需要它。
  • 只要你知道你在做什么。该行将在运行 15 秒后使您的代码崩溃。 javadoc 说“将此选项设置为非零超时,对与此 Socket 关联的 InputStream 的 read() 调用将仅阻塞这段时间。如果超时到期,则会引发 java.net.SocketTimeoutException,虽然 Socket 仍然有效。必须在进入阻塞操作之前启用该选项才能生效。超时必须> 0。超时为零被解释为无限超时。我认为这是你的问题。

标签: java sockets tcp-ip


【解决方案1】:

根据 Java API,

public boolean isConnected()

Returns the connection state of the socket.

Note: Closing a socket doesn't clear its connection state, which means this method will return true for a closed socket (see isClosed()) if it was successfuly connected prior to being closed.

Returns:
    true if the socket was successfuly connected to a server
Since:
    1.4

请注意,即使您关闭了套接字,isConnect() 仍然返回 true,因此很可能您的 isConnect() 用法不正确。

根据 Sumit Singh 对这个问题的回答,

How do I check if a Socket is currently connected in Java?

一旦客户端连接(甚至在断开连接之后),socket.isConnected() 总是返回真值!!

因此,即使在断开连接后,isConnected() 也会返回 true。所以我的假设(和你的一样)是,虽然你捕获了 SocketWrite 异常,但 isConnected() 仍然返回 true。我无法对此进行测试,因为它不是一个工作示例。但是,您可以执行以下操作:

try {

    //write message to server

} catch ( SocketException e ) {

    //we lost the connection, right? then print if the socket is connected
    System.out.println( socketToDestiny.isConnected() );
}

看看输出是什么。如果输出仍然是true,那么我们就发现了问题。如果我的假设是正确的,我建议您尝试在 catch 语句中重新连接。例如:

try {

    //write message to server

} catch ( SocketException e ) {

    //we lost the connection, so let's try to reconnect
    while ( true ) {
        try {
            socketToDestiny.connect( ...ip address ... );
            break; 
        } catch ( IOException e2 ) {

            //keep trying to reconnect!
            continue;
        }
    }
}

【讨论】:

  • 您好@user2570465,感谢您的回答。但是,如果服务器再也无法启动,程序将一直在尝试连接。我更改了原始问题中的代码。看到新的catch,我认为没问题,但是当程序执行catch代码中的“读取”指令时,它说套接字已关闭。我不明白为什么,我正在创建一个“新 Socket()”:S
  • @Nico 第一个问题,“程序将始终尝试连接。”您的程序无法知道服务器是否会再次恢复运行。因此,我们需要更具体地重新连接。你必须决定,“我要不要再给它 5 分钟,然后假设服务器永远不会恢复生命?”或“我是否只想继续尝试外部 while 循环的每次迭代?”这将是我认为您需要选择的实现细节
  • @Nico 对于第二个问题,“当程序执行 catch 代码中的‘读取’指令时,它表示套接字已关闭。”发生这种情况的原因如下:您的服务器死了,所以您关闭了套接字socketToDestiny.close()。您尝试重新连接,但服务器仍然死机,因此您在 socketToDestiny.connect(...); 上抛出 IOException。您的 socketToDestiny 仍然没有连接到任何东西(换句话说,它已关闭)。然后,您的内部 while 循环结束,您回到开头并尝试从已关闭的套接字中读取(),这会引发异常
  • @Nico 第三个问题,“我不明白为什么,我正在创建一个 'new Socket()'”。我是这样想的:你与服务器建立一个连接(这是一个套接字)。服务器死了,所以你的连接中断了。如果您想继续通信,您需要与服务器建立 连接。我不知道你是否需要先做socketToDestiny = new Socket(),然后再做socketToDestiny.connect(...)。我确实知道您无法使用断开的旧连接进行通信。
  • 感谢您的 cmets,我会尝试修复程序。问题是当我杀死服务器并重新启动时,在下一条消息中,“读取”指令出现永远不会释放程序:S。你会对我的代码做哪些更改?我用新的指令改变了问题。
猜你喜欢
  • 1970-01-01
  • 2016-10-02
  • 2023-03-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多