【问题标题】:Permanent and persistent Socket connection in javajava中的永久和持久的Socket连接
【发布时间】:2016-12-02 20:09:04
【问题描述】:

我创建了一个客户端-服务器连接,类似于聊天系统。以前我在客户端使用while 循环,它每次都在等待从控制台读取消息(当然服务器也有一个while 循环以永久服务)。但是现在,我尝试在会话开始时先创建一个连接,然后在会话期间偶尔发送一条消息,从而保持一个永久持久的连接。

目前,如果没有while 循环,客户端会关闭连接,我不知道如何找到解决方法。

这是客户端代码:

import java.net.*;
import java.io.*;
public class ControlClientTest {
    private Socket socket = null;
//  private BufferedReader console = null;  
    private DataOutputStream streamOut = null;

    public static void main(String args[]) throws InterruptedException {
        ControlClientTest client = null;

        String IP="127.0.0.1";
        client = new ControlClientTest(IP, 5555);
    }

    public ControlClientTest(String serverName, int serverPort) throws InterruptedException {
        System.out.println("Establishing connection. Please wait ...");
        try {
            socket = new Socket(serverName, serverPort);
            System.out.println("Connected: " + socket);
            start();
        } catch (UnknownHostException uhe) {
            System.out.println("Host unknown: " + uhe.getMessage());
        } catch (IOException ioe) {
            System.out.println("Unexpected exception: " + ioe.getMessage());
        }

        String line = "";
//      while (!line.equals(".bye")) {
            try {
                Thread.sleep(1000);
                //TODO get data from input
//              line = console.readLine();
                line="1";
                if(line.equals("1"))
                    line="1,123";
                streamOut.writeUTF(line);
                streamOut.flush();
            } catch (IOException ioe) {
                System.out.println("Sending error: " + ioe.getMessage());
            }
//      }
    }

    public void start() throws IOException {
//      console = new BufferedReader(new InputStreamReader(System.in));
        streamOut = new DataOutputStream(socket.getOutputStream());
    }

}

这是服务器代码:

import java.awt.*;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class ControlServer {
    private Socket socket = null;
    private ServerSocket server = null;
    private DataInputStream streamIn = null;

    public static void main(String args[]) {

        ControlServer server = null;
        server = new ControlServer(5555);
    }

    public ControlServer(int port) {
        try {
            System.out
            .println("Binding to port " + port + ", please wait  ...");
            server = new ServerSocket(port);
            System.out.println("Server started: " + server);
            System.out.println("Waiting for a client ...");
            socket = server.accept();
            System.out.println("Client accepted: " + socket);
            open();
            boolean done = false;
            while (!done) {
                try {
                    String line = streamIn.readUTF();
                    // TODO get the data and do something
                    System.out.println(line);

                    done = line.equals(".bye");
                } catch (IOException ioe) {
                    done = true;
                }
            }
            close();
        } catch (IOException ioe) {
            System.out.println(ioe);
        }

    }


    public void open() throws IOException {
        streamIn = new DataInputStream(new BufferedInputStream(
                socket.getInputStream()));
    }
public void close() throws IOException {
    if (socket != null)
        socket.close();
    if (streamIn != null)
        streamIn.close();
}

}

【问题讨论】:

  • 你为什么不想要一个循环呢?

标签: java sockets


【解决方案1】:

我想总结一些我每天应用的关于 TCP/IP 连接稳定性的良好实践。

良好做法 1:内置 Keep-Alive

socket.setKeepAlive(true);

它会在一段时间不活动后自动发送信号并检查回复。但是,保持活动间隔取决于操作系统,并且有一些缺点。但总而言之,它可以提高您的连接稳定性。

良好做法 2:SoTimeout

每当您执行read(或您的情况下为readUTF)时,您的线程实际上将永远阻塞。根据我的经验,这是不好的做法,原因如下: 关闭应用程序很困难。只是打电话给socket.close() 很脏。

一个干净的解决方案是简单的读取超时(例如 200 毫秒)。您可以使用setSoTimeout方法来做到这一点。当read() 方法超时时,它会抛出SocketTimeoutException。 (它是IOException 的子类)。

socket.setSoTimeout(timeoutInterval);

这是一个实现循环的示例。请注意shutdown 条件。只需将其设置为 true,您的线程就会平静地死去。

while (!shutdown)
{
  try
  {
    // some method that calls your read and parses the message.
    code = readData();
    if (code == null) continue; 
  }
  catch (SocketTimeoutException ste)
  {
    // A SocketTimeoutExc. is a simple read timeout, just ignore it.
    // other IOExceptions will not be stopped here.
  }
}

良好做法 3:Tcp 无延迟

当您经常连接需要快速处理的小命令时,请使用以下设置。

try
{
  socket.setTcpNoDelay(true);
}
catch (SocketException e)
{
}

好习惯 4:心跳

实际上还有很多侧面场景尚未涵盖。

例如,其中一个是服务器应用程序,其设计目的是一次仅与 1 个客户端通信。有时他们接受连接,甚至接受消息,但从不回复。

另一个:有时当您失去连接时,您的操作系统实际上可能需要很长时间才能注意到这一点。可能是由于良好实践 3 中描述的缺点,而且在更复杂的网络情况下(例如使用 RS232 转以太网转换器、VMware 服务器等),这种情况经常发生。

这里的解决方案是创建一个线程,每隔 x 秒发送一条消息,然后等待回复。 (例如每 15 秒)。为此,您需要创建第二个线程,每 15 秒发送一条消息。其次,你需要将good practice 2的代码稍微扩展一下。

  try
  {
    code = readData();

    if (code == null) continue; 
    lastRead = System.currentTimeMillis();

    // whenever you receive the heart beat reply, just ignore it.
    if (MSG_HEARTBEAT.equals(code)) continue;

    // todo: handle other messages
  }
  catch (SocketTimeoutException ste)
  {
    // in a typical situation the soTimeout is about 200ms
    // the heartbeat interval is usually a couple of seconds.
    // and the heartbeat timeout interval a couple of seconds more.
    if ((heartbeatTimeoutInterval > 0) &&
        ((System.currentTimeMillis() - lastRead) > heartbeatTimeoutInterval))
    {
      // no reply to heartbeat received.
      // end the loop and perform a reconnect.
      break;
    }
  }

您需要决定您的客户端或服务器是否应该发送消息。这个决定并不那么重要。但是例如如果您的客户发送消息,那么您的客户将需要一个额外的线程来发送消息。您的服务器应在收到消息时发送回复。当您的客户收到答案时,它应该只是continue(即参见上面的代码)。并且双方都应该检查:“已经多久了?”以非常相似的方式。

【讨论】:

  • 太棒了!感谢您提供详细信息。
  • 绝妙的答案。如果您能推荐任何书籍或体面的网络资源来提高我对 TCP/IP 客户端/服务器连接的了解,我将不胜感激?
  • @TheFlanman91 大约 10 年前,我确实读过“Thinking in Java”,“Effective java”的某些部分,并且确实参加了 OCJP 测试。我也喜欢这篇关于编码和字符集的文章joelonsoftware.com/articles/Unicode.html;我从未读过只涉及套接字的书。但是这里有很多关于 stackoverflow 的问答。 ;
  • 感谢@bvdb 的回复。我目前正在考虑使用 Apache MINA 构建我的第一个客户端/服务器应用程序,因此掌握核心概念非常有帮助。干杯。
【解决方案2】:

您可以在连接周围包裹一个线程,并让它定期发送一个状态以保持线路打开,比如每 30 秒或其他时间。然后,当它实际上有数据要发送时,它会将保持活动状态重置为最后一次传输后的 30 秒。该状态可能有助于查看客户端是否仍然活着,因此至少它可以是一个有用的 ping。

另外,您应该更改您的服务器代码,您目前似乎只处理一个连接。你应该循环,当一个套接字连接进来时产生一个线程来处理客户端请求并返回监听。不过,我可能正在阅读可能只是您的测试代码的内容。

【讨论】:

  • 嗯,对我来说新东西太多了。我真的不知道如何将它应用到我的代码中。每次我想发送一些东西时,我可以只创建一个新的套接字吗?这些东西应该不多。也许 3 秒内 3 个,然后延迟 1 分钟。
【解决方案3】:

使客户端套接字连接缠绕在一个线程上。使用blocking queue 等待消息。整个应用程序中应该只有一个发送方队列,因此请使用单例模式。

例如

QueueSingleton queue = QueueSingleton.getSenderQueue();
Message message = queue.take() // blocks thread
send(message); //send message to server

当需要向服务器发送消息时,可以使用阻塞队列发送消息。

QueueSingleton queue = QueueSingleton.getSenderQueue();
queue.put(message)

客户端线程将唤醒并处理消息。

为了保持连接,请使用timer task。这是一种特殊类型的线程,它在指定的时间段重复调用 run 方法。您可以使用它来发布一条消息,一条 ping 消息,每隔一段时间。

为了处理收到的消息,您可以有另一个线程,在另一个阻塞队列(接收者队列)上等待消息。客户端线程会将收到的消息放到这个队列中。

【讨论】:

  • 嗯,对我来说新东西太多了。我真的不知道如何将它应用到我的代码中。每次我想发送一些东西时,我可以只创建一个新的套接字吗?这些东西应该不多。也许 3 秒内 3 个,然后延迟 1 分钟。
  • 你可以,但这不是你要求的永久和持久的连接。
猜你喜欢
  • 1970-01-01
  • 2014-01-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-12
  • 2012-05-26
  • 2017-05-28
  • 2012-11-03
相关资源
最近更新 更多