【问题标题】:Receiving data from multiple sockets at once (Multithreading)一次从多个套接字接收数据(多线程)
【发布时间】:2016-12-19 10:14:40
【问题描述】:

我是网络编程的新手,我一直在这里寻找解决问题的方法,但找不到。我想要的是有一个可以同时从多个套接字接收文件的服务器。当服务器接受新的连接套接字时,它会使用 ClientThread 类包装该套接字。代码如下:

public class Server extends Thread {
    private ServerSocket server;
    private Vector<ClientThread> clients;

    @Override
    public void run() {
        listen();
    }

    private void listen() {

    new Thread("Listening Thread") {

        @Override
        public void run() {
            while (true) {
                try {
                    Socket socket = server.accept();

                    ClientThread newClient = new ClientThread(socket);
                    newClient.start();
                    clients.addElement(newClient);

                } catch (IOException | ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }
    }.start();
}

ClientThread 是 Server 类中的私有类。它总是从 ObjectInputStream 监听一个对象,但我也希望能够在对象之后接收一个大文件。这就是为什么我认为我应该使用多线程。代码如下:

private class ClientThread extends Thread {

    public Socket socket;
    private boolean loggedIn;
    private ObjectInputStream ois;
    private BufferedInputStream bis;

    public ClientThread(Socket socket) {
        this.socket = socket;
        loggedIn = true;

        InputStream is = socket.getInputStream();
        ois = new ObjectInputStream(is);
        bis = new BufferedInputStream(is);
    }

    @Override
    public void run() {
        receive();
    }

    private void receive() {

        while (loggedIn) {
            try {
                // this method blocks i guess
                Object object = ois.readObject();

                // after the object comes the large file
                byte[] bytes = new byte[SOME_SIZE];

                int bytesRead;
                int totalRead = 0;

                // reading the large file into memory
                while ((bytesRead = bis.read(bytes, totalRead, bytes.length - totalRead)) > -1) {
                    totalRead += bytesRead;
                }

                // rest of the code for handling received bytes.......

            } catch (ClassNotFoundException | IOException e) {
                e.printStackTrace();
            }
        }
    }

}

我不确定是否可以接收这样的数据,因为所有这些客户端套接字都将数据发送到此服务器上的同一端口(我猜?)。如果客户端同时发送数据,服务器需要知道哪些数据是为哪个客户端发送的。这是已经处理好了,还是我需要完全不同的方法?

我不知道这是不是一个愚蠢的问题,但就像我说的那样,我才刚刚开始学习这些东西。我也无法测试我的程序,因为我什至还没有客户端的代码。只是想确保我一开始就不会出错。如果这是错误的,请随时发布一些想法。 :) 谢谢!

【问题讨论】:

  • 去年我添加了一个类似的请求,最终我使用了 Netty netty.io,他们有一个 wiki 有几个示例 netty.io/wiki,用于套接字检查 telnet 或 udp。
  • “我不确定是否可以接收这样的数据,因为所有这些客户端套接字都将数据发送到此服务器上的同一端口(我猜?)” - 客户端连接到一个端口, 是的。但是它们被赋予了一个唯一的 local 端口。所以是的,您实际上可以同时从连接到同一(公共服务器)端口的许多客户端接收数据。
  • 好吧,要明确一点,如果它们都在同一个客户端主机中,它们将被赋予一个唯一的本地端口,这是唯一需要唯一本地端口的时候。
  • That other answer 值得一读,如果您想了解更多关于 IP:port 唯一性的信息。
  • 感谢大家的回答! :D

标签: java multithreading sockets tcp network-programming


【解决方案1】:

一开始还不错:) 您可以稍后使用Selector 进行改进,但这是另一个主题。

尽管有一些说明:ServerSocket 侦听特定端口。当远程客户端连接到它时,会创建一个通信通道(即套接字)。如果另一个客户端连接,则创建另一个套接字。两个套接字是不同的通道,不会相互干扰,因为它们连接到不同的远程 IP 端口。

这一切都与TCP headersIP headers 的形成方式有关:发送 TCP 数据包时,其标头包含源和目标 端口,IP 标头顶部包含源和目标 IP。这些用于区分不同的套接字。


关于您想要做的“广播”(根据您在@Rajesh 的回答中的评论),您可以选择:

  • 在纯 TCP 中使用 ServerSocketSocket 自己动手,就像你开始的那样
  • 切换到 UDP 并使用 MulticastSocket,它具有发出单次发送的优势,但您必须在客户端代码中处理丢失/无序的数据报(UDP 不保证交付或排序,就像 TCP 一样)
  • SelectorSocketChannel检查NIO
  • 调查像 jGroupsNetty 这样为您执行 I/O 工作的框架

在您学习的过程中,我建议您按照上述顺序进行操作。使用框架很好,但自己编写代码会学到更多。

【讨论】:

  • 感谢您的回答!现在我读到这一切都说得通了。很高兴这可以工作。 :)
  • @pavlee,网络编程现在将为您提供一个全新的可能性世界,尽情享受吧! :)
  • 谢谢。我暂时不会使用框架,因为我想详细了解它的工作原理。我想我应该在实施之前进一步了解它。
  • @pavlee 我认为你是对的。一旦您了解了基础知识,NIO 也值得一试。另外,如果您喜欢答案,请不要犹豫,为他们投票;)
  • @pavlee UDP 比 TCP 更容易,因为它是“即发即弃”。这取决于您拥有多少客户端、数据的重要性和网络类型(LAN 或 Internet,“更长”的网络(如 Internet)更有可能丢弃 UDP 数据报)。困难的部分是处理无序或丢失的数据报。
【解决方案2】:

这将正常工作。每个线程都从连接到不同客户端(地址+端口)的单独套接字读取。它们是独立的流,因此像这样读取它们没有问题。

但是使用异步套接字会好得多。

在当前实现中可以注意的几件事:

1) 作为一种好的做法,在传输完成后关闭流/套接字。

2) 对于每个新连接,都会创建一个新线程。那将无法扩展。甚至有人可以发送许多请求并关闭您的应用程序。最好使用线程池。 “ClientThread”可以只实现“Runnable”,当接收到新的连接时,只需将新的“ClientThread”提交到线程池。 (在这种情况下,最好将其命名为 ClientTask 而不是 ClientThread)

如前所述,使用异步套接字会更有效率和可扩展性,但掌握它需要一些时间。这样,您可以仅使用一个线程并行读取所有套接字,并且根据负载,可以使用同一线程或线程池来处理从所有套接字接收的数据。请注意,即使使用池,您也不需要单独的线程来处理每个套接字...只是为了充分利用多个 CPU 核心,可以使用多个线程来处理数据。

您可以尝试 java nio (Selector + SocketChannels) 或 netty 库。与 nio 相比,Netty 更易于使用。

【讨论】:

  • 使用线程池只会限制连接数。缩放会更糟。解决方案是使用Selector 和线程池来处理“atomic”事件(连接、读取、写入...)。
  • 好吧,我认为吞吐量和可扩展性是两个不同的东西......考虑一下 - 服务一次获得 1000 个连接,它将产生 1000 个线程。直接的 jvm 将因堆栈溢出而停机。核心大小为平均请求数的线程池将是稳定的,尽管吞吐量将在其设计的水平上受到限制。
  • 任何系统都会有它可以处理的最大值,具体取决于可用资源。除此之外,它必须优雅地处理。如果系统中的一个组件不可预测地消耗更多资源,其他组件将饿死或系统崩溃。毫无疑问,异步套接字会做得更好,但是当使用同步套接字时,最好进行节流,以便以可预测的方式消耗资源。 U 可以通过分配更多资源并根据预期的平均/最大并行请求数允许更多线程来扩大/缩小规模;超出计划/设计的范围,在系统不停机的情况下优雅地处理。
  • @pavlee 是的,这是使用异步套接字的合适案例。从每个客户端读取的单独线程不会扩展。在上面的代码中......是的,只要你需要沟通,你就需要保持 skt 处于打开状态。但在某个时候它会出来吗?异常或客户端优雅地离开。此时您需要关闭,否则可能导致资源泄漏。
  • @pavlee 其他几点 - 第一点 - 在上面的代码中,当另一端关闭时,读取流套接字的输入流通常会返回 (
猜你喜欢
  • 1970-01-01
  • 2012-05-17
  • 1970-01-01
  • 1970-01-01
  • 2017-06-14
  • 2020-11-12
  • 2013-03-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多