【问题标题】:Java - ReadObject with nioJava - 带有 nio 的 ReadObject
【发布时间】:2011-05-02 22:03:04
【问题描述】:

在传统的阻塞线程服务器中,我会做这样的事情

class ServerSideThread {

    ObjectInputStream in;
    ObjectOutputStream out;
    Engine engine;

    public ServerSideThread(Socket socket, Engine engine) {
        in = new ObjectInputStream(socket.getInputStream());
        out = new ObjectOutputStream(socket.getOutputStream());
        this.engine = engine;
    }

    public void sendMessage(Message m) {
        out.writeObject(m);
    }

    public void run() {
        while(true) {
            Message m = (Message)in.readObject();
            engine.queueMessage(m,this); // give the engine a message with this as a callback
        }
    }
}

现在,可以预期该对象会非常大。在我的 nio 循环中,我不能简单地等待对象通过,我的所有其他连接(工作量小得多)都将等待我。

我怎样才能在一个连接告诉我的 nio 通道它已经准备好之前,才知道它已经拥有整个对象?

【问题讨论】:

    标签: java serialization nio


    【解决方案1】:

    您可以将对象写入 ByteArrayOutputStream 允许您在发送对象之前给出长度。在接收端,在尝试解码之前读取所需的数据量。

    但是,您可能会发现将阻塞 IO(而不是 NIO)与 Object*Stream 一起使用更简单、更高效


    编辑类似的东西

    public static void send(SocketChannel socket,  Serializable serializable) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        for(int i=0;i<4;i++) baos.write(0);
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(serializable);
        oos.close();
        final ByteBuffer wrap = ByteBuffer.wrap(baos.toByteArray());
        wrap.putInt(0, baos.size()-4);
        socket.write(wrap);
    }
    
    private final ByteBuffer lengthByteBuffer = ByteBuffer.wrap(new byte[4]);
    private ByteBuffer dataByteBuffer = null;
    private boolean readLength = true;
    
    public Serializable recv(SocketChannel socket) throws IOException, ClassNotFoundException {
        if (readLength) {
            socket.read(lengthByteBuffer);
            if (lengthByteBuffer.remaining() == 0) {
                readLength = false;
                dataByteBuffer = ByteBuffer.allocate(lengthByteBuffer.getInt(0));
                lengthByteBuffer.clear();
            }
        } else {
            socket.read(dataByteBuffer);
            if (dataByteBuffer.remaining() == 0) {
                ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(dataByteBuffer.array()));
                final Serializable ret = (Serializable) ois.readObject();
                // clean up
                dataByteBuffer = null;
                readLength = true;
                return ret;
            }
        }
        return null;
    }
    

    【讨论】:

    • 阻塞 IO 不会扩展。因为整个 JVM 的堆栈线程大小是恒定的,所以每个连接线程的堆栈大小都将与我的引擎线程的堆栈大小相同(这将需要非常大。)所以即使只有 3000 个连接,这也使我达到 384Mb只是为了连接。这不会为 1Gb 机器上的系统、堆和数据库留下太多东西。 cpu 需求足够小,我可以轻松处理超过 10000 个连接,除非内存使用。
    • 那么我在哪里先发送要读取的数据量,我怎么知道我已经排队了多少数据,直到……我读取了它?
    • 对每个对象(或对象组)使用new ObjectOutputStream(new ByteArrayOutputStream 这样您就没有排队的数据。您将字节数组的长度作为int 后跟字节数组发送。在读取方面,您需要读取最少 4 个字节,这将为您提供对象数据其余部分的长度。
    • @glowcoder,使用 NIO,您执行读取,它会告诉您读取了多少字节。然后,您获取这些字节,并将它们放在您为每个连接维护的单独缓冲区中,直到累积所需的字节数。我建议将此功能包装在您自己的一对输入和输出流中。
    • 注意:在整个会话中将new ObjectOutputStream() 与单个ObjectOutput/InputStream 对上的每个对象一起使用时,应该注意两个微妙的陷阱:首先,只需要发送一次的数据,例如标头和有关类型的各种元信息,每次发送都会重新发送。其次,发送方的常见实例在多次发送时会成为差异引用。
    【解决方案2】:

    受上面代码的启发,我创建了一个 (GoogleCode project)

    它包括一个简单的单元测试:

    SeriServer server = new SeriServer(6001, nthreads);
    final SeriClient client[] = new SeriClient[nclients];
    
    //write the data with multiple threads to flood the server
    
    for (int cnt = 0; cnt < nclients; cnt++) {
        final int counterVal = cnt;
        client[cnt] = new SeriClient("localhost", 6001);
        Thread t = new Thread(new Runnable() {
             public void run() {
                 try {
                    for (int cnt2 = 0; cnt2 < nsends; cnt2++) {
                       String msg = "[" + counterVal + "]";                       
                       client[counterVal].send(msg);
                     }
                 } catch (IOException e) {
                     e.printStackTrace();
                     fail();
                 }
             }
             });
        t.start();
     }
    
     HashMap<String, Integer> counts = new HashMap<String, Integer>();
       int nullCounts = 0;
       for (int cnt = 0; cnt < nsends * nclients;) {
           //read the data from a vector (that the server pool automatically fills
           SeriDataPackage data = server.read();  
           if (data == null) {
                  nullCounts++;
                  System.out.println("NULL");
                  continue;
           }
    
           if (counts.containsKey(data.getObject())) {
                  Integer c = counts.get(data.getObject());
                  counts.put((String) data.getObject(), c + 1);
            } else {
                  counts.put((String) data.getObject(), 1);
            }
            cnt++;
            System.out.println("Received: " + data.getObject());
       }
    
       // asserts the results
       Collection<Integer> values = counts.values();
       for (Integer value : values) {
            int ivalue = value;
            assertEquals(nsends, ivalue);
            System.out.println(value);
       }
       assertEquals(counts.size(), nclients);
       System.out.println(counts.size());
       System.out.println("Finishing");
       server.shutdown();
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-05-02
      • 1970-01-01
      • 2015-07-17
      • 2013-05-10
      • 1970-01-01
      • 2010-11-13
      • 1970-01-01
      相关资源
      最近更新 更多