简体   繁体   English

Java:如何在单个线程中与多个客户端进行通信

[英]Java: How to communicate with multiple clients in a single thread

I have a list of sockets that belongs to a single thread. 我有一个属于单个线程的套接字列表。 But I wonder is there a feasible way to communicate (read/write from/to) with those those clients? 但我想知道是否有一种可行的方式与那些客户进行通信(读/写)? I don't want to create one thread for each client because there may be too much users and creating one thread for each of them may be too costly. 我不想为每个客户端创建一个线程,因为可能有太多用户并且为每个客户端创建一个线程可能成本太高。

I'd say NIO is your best bet here. 我会说NIO是你最好的选择。 Check out one of the many good tutorials for NIO communications over a socket (well, SocketChannel)! 查看通过套接字(好吧,SocketChannel)进行NIO通信的众多优秀教程之一!

I believe this is the tutorial that I used when I had to learn NIO: http://rox-xmlrpc.sourceforge.net/niotut/ 我相信这是我在学习NIO时使用的教程: http//rox-xmlrpc.sourceforge.net/niotut/

Netty - Java NIO客户端服务器套接字框架http://www.jboss.org/netty

Just use Standard Java NIO The best documentation is written on Java main page http://docs.oracle.com/javase/6/docs/technotes/guides/io/index.html . 只需使用标准Java NIO最好的文档是在Java主页http://docs.oracle.com/javase/6/docs/technotes/guides/io/index.html上编写的。 There are API documentation, samples, tutorial. 有API文档,示例,教程。 Everything. 一切。 I promise you it is enougth - I have experience writing software where 10k clients were connected to one client (a few threads). 我向你保证这很有意义 - 我有编写软件的经验,其中10k个客户端连接到一个客户端(几个线程)。 You must only remember about OS limitation to change it in configuration. 您必须记住操作系统限制才能在配置中更改它。

I had to connect multiple server IP:PORTs and do request-response messaging. 我不得不连接多个服务器IP:PORT并执行请求 - 响应消息传递。 After implementing a traditional IO with multiple threads and a watchdog killing blocked sockets gave up. 在实现了具有多个线程的传统IO并且看门狗查杀被阻止的套接字后放弃了。 I made NIO implementation and this is my test application for future reference. 我做了NIO实现,这是我的测试应用程序,供将来参考。

I can open N connections with timeout, read reply with timeout, write command with timeout everything in a simple single threaded "game loop". 我可以用超时打开N个连接,用超时读取回复,在一个简单的单线程“游戏循环”中用超时写入命令。 If I needed concurrency I could spawn worker threads but not mandatory if application logic does not need it. 如果我需要并发,我可以生成工作线程,但如果应用程序逻辑不需要它,则不是必需的。

Server is a custom telnet app, clients write a command and read text lines until a terminator line prompt is found. 服务器是一个自定义的telnet应用程序,客户端编写命令并读取文本行,直到找到终结符行提示符。 Terminator marks the end_of_response_packet. 终结符标记end_of_response_packet。

import java.util.*;
import java.io.*;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;

public class ClientSocketNIO {
    private String host;
    private int port;
    private String charset;
    private ByteArrayOutputStream inBuffer;
    private ByteBuffer buf;
    private Selector selector;
    private SocketChannel channel;

    public ClientSocketNIO(String host, int port, String charset) {
        this.charset = charset==null || charset.equals("") ? "UTF-8" : charset;
        this.host = host;
        this.port = port;
    }

    public void open(long timeout) throws IOException {
        selector = Selector.open();
        channel = SocketChannel.open();
        channel.configureBlocking(false);
        channel.register(selector, SelectionKey.OP_CONNECT);
        channel.connect(new InetSocketAddress(host, port));
        inBuffer = new ByteArrayOutputStream(1024);
        buf = ByteBuffer.allocate(1*1024);
        long sleep = Math.min(timeout, 1000);
        while(timeout > 0) {
            if (selector.select(sleep) < 1) {
                timeout-=sleep;
                continue;
            }
            Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
            while(keys.hasNext()) {
                SelectionKey key = keys.next();
                keys.remove();
                if (!key.isValid() || !key.isConnectable()) continue;
                SocketChannel channel = (SocketChannel)key.channel();
                if (channel.isConnectionPending()) {
                    channel.finishConnect();
                    channel.configureBlocking(false);
                    return; // we are ready to receive bytes
                }
            }
        }
        throw new IOException("Connection timed out");
    }

    public void close() {
        try { channel.close(); } catch(Exception ex) { }
        try { selector.close(); } catch(Exception ex) { }
        inBuffer=null;
        buf=null;
    }   

    public List<String> readUntil(String terminator, long timeout, boolean trimLines) throws IOException {
        return readUntil(new String[]{terminator}, timeout, trimLines);
    }

    public List<String> readUntil(String[] terminators, long timeout, boolean trimLines) throws IOException {
        List<String> lines = new ArrayList<String>(12);
        inBuffer.reset();

        // End of packet terminator strings, line startsWith "aabbcc" string.
        byte[][] arrTerminators = new byte[terminators.length][];
        int[] idxTerminators = new int[terminators.length];
        for(int idx=0; idx < terminators.length; idx++) {
            arrTerminators[idx] = terminators[idx].getBytes(charset);
            idxTerminators[idx] = 0;
        }
        int idxLineByte=-1;

        channel.register(selector, SelectionKey.OP_READ);
        long sleep = Math.min(timeout, 1000);
        while(timeout>0) {
            if (selector.select(sleep) < 1) {
                timeout-=sleep;
                continue;
            }
            Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
            while(keys.hasNext()) {
                SelectionKey key = keys.next();
                keys.remove();
                if (!key.isValid() || !key.isReadable()) continue;
                SocketChannel channel = (SocketChannel)key.channel();
                buf.clear();
                int len = channel.read(buf);
                System.out.println("read " + len);
                if (len == -1) throw new IOException("Socket disconnected");
                buf.flip();
                for(int idx=0; idx<len; idx++) {
                    byte cb = buf.get(idx);
                    if (cb!='\n') {
                        idxLineByte++;
                        inBuffer.write(cb);
                        for(int idxter=0; idxter < arrTerminators.length; idxter++) {
                            byte[] arrTerminator = arrTerminators[idxter];
                            if (idxLineByte==idxTerminators[idxter]
                                    && arrTerminator[ idxTerminators[idxter] ]==cb) {
                                idxTerminators[idxter]++;
                                if (idxTerminators[idxter]==arrTerminator.length)
                                    return lines;
                            } else idxTerminators[idxter]=0;
                        }
                    } else  {
                        String line = inBuffer.toString(charset);
                        lines.add(trimLines ? line.trim() : line);
                        inBuffer.reset();
                        idxLineByte=-1;
                        for(int idxter=0; idxter<arrTerminators.length; idxter++)
                            idxTerminators[idxter]=0;
                    }
                }
            }
        }
        throw new IOException("Read timed out");
    }

    public void write(String data, long timeout) throws IOException {
        ByteBuffer outBuffer = ByteBuffer.wrap(data.getBytes(charset));
        channel.register(selector, SelectionKey.OP_WRITE);
        long sleep = Math.min(timeout, 1000);
        while(timeout > 0) {
            if (selector.select(sleep) < 1) {
                timeout-=sleep;
                continue;
            }
            Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
            while(keys.hasNext()) {
                SelectionKey key = keys.next();
                keys.remove();
                if (!key.isValid() || !key.isWritable()) continue;
                SocketChannel channel = (SocketChannel)key.channel();
                int len = channel.write(outBuffer);
                System.out.println("write " + len);
                if (outBuffer.remaining()<1)
                    return;
            }
        }
        throw new IOException("Write timed out");
    }

    public static void main(String[] args) throws Exception {
        ClientSocketNIO client = new ClientSocketNIO("11.22.33.44", 1234, "UTF-8");
        try {
            client.open(15000);

            // read prompting for username
            List<String> reply = client.readUntil("User: ", 15000, true);
            for(int idx=0; idx<reply.size(); idx++)
                System.out.println("|"+reply.get(idx)+"|");         

            // write username and read a success or failed prompt(asks username once again),
            // this one may return two different terminator prompts so listen for both
            client.write("myloginname\n", 15000);
            reply = client.readUntil(new String[]{"> ", "User: "}, 15000, true);
            for(int idx=0; idx<reply.size(); idx++)
                System.out.println("|"+reply.get(idx)+"|");
            if (!reply.get(reply.size()-1).startsWith("Welcome ")) return; // Access denied

            System.out.println("-----");
            client.write("help\n", 15000);
            reply = client.readUntil("> ", 15000, true);
            for(int idx=0; idx<reply.size(); idx++)
                System.out.println("|"+reply.get(idx)+"|");

            System.out.println("-----");
            client.write("get status\n", 15000);
            reply = client.readUntil("> ", 15000, true);
            for(int idx=0; idx<reply.size(); idx++)
                System.out.println("|"+reply.get(idx)+"|");

            System.out.println("-----");
            client.write("get list\n", 15000);
            reply = client.readUntil("> ", 15000, true);
            for(int idx=0; idx<reply.size(); idx++)
                System.out.println("|"+reply.get(idx)+"|");

            client.write("quit\n", 15000);
        } finally {
            client.close();
        }
    }

}

you can use NIO approach in JRE. 你可以在JRE中使用NIO方法。 Another solution is using Space Architecture . 另一个解决方案是使用Space Architecture In this architecture exist global spaces with Space name and any request write in this spaces, then another threads read from this spaces and process it and write process result in another space and in final step request thread read own result from specified space. 在这个体系结构中,存在具有Space名称的全局空间和在此空间中写入的任何请求,然后另一个线程从此空间读取并处理它并将处理结果写入另一个空间,并且在最后一步请求线程从指定空间读取自己的结果。

You can see following link for more information: 您可以查看以下链接以获取更多信息:

http://en.wikipedia.org/wiki/Space_architecture http://en.wikipedia.org/wiki/Space_architecture

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM