简体   繁体   English

简单的非阻塞服务器

[英]Simple non-blocking server

For the purpose of writing an instant messenger program, I am trying to make up a simple server class which will run in its own thread. 为了编写即时消息程序,我试图组成一个简单的服务器类,它将在自己的线程中运行。

What the server should do 服务器应该做什么

  • accept connections from / connect to other instances of the server and associate the selection keys for the connections in Map<Integer, SelectionKey> keys wit an ID so the messenger thread can access the connections by ID 接受来自/连接到服务器的其他实例的连接,并将Map<Integer, SelectionKey> keys的连接的Map<Integer, SelectionKey> keys与ID相关联,以便信使线程可以通过ID访问连接
  • read from / write to connections 读/写连接
  • store incoming messages in a queue 将传入的消息存储在队列中
  • messenger thread can 信使线程可以
    • fetch incoming messages 获取传入的消息
    • queue messages to be sent : send_message(int id, String msg) 要发送的队列消息: send_message(int id, String msg)

My current approach is based mainly on this example: A simple non-blocking Echo server with Java nio . 我目前的方法主要基于这个例子: 一个带有Java nio的简单非阻塞Echo服务器
I also used Using a Selector to Manage Non-Blocking Sockets and the realted pages to learn about non-blocking sockets and selectors. 我还使用了使用选择器来管理非阻塞套接字和已编辑的页面,以了解非阻塞套接字和选择器。

Current code 目前的代码

  • Suggestions by EJP implemented EJP的建议已实施
  • small changes 微小的变化
package snserver;

/* imports */

//class SNServer (Simple non-blocking Server)

public class SNServer extends Thread {
    private int port;
    private Selector selector;
    private ConcurrentMap<Integer, SelectionKey> keys; // ID -> associated key
    private ConcurrentMap<SocketChannel,List<byte[]>> dataMap_out;
    ConcurrentLinkedQueue<String> in_msg; //incoming messages to be fetched by messenger thread

    public SNServer(int port) {
        this.port = port;
        dataMap_out = new ConcurrentHashMap<SocketChannel, List<byte[]>>();
        keys = new ConcurrentHashMap<Integer, SelectionKey>();
    }

    public void start_server() throws IOException {
        // create selector and channel
        this.selector = Selector.open();
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);

        // bind to port
        InetSocketAddress listenAddr = new InetSocketAddress((InetAddress)null, this.port);
        serverChannel.socket().bind(listenAddr);
        serverChannel.register(this.selector, SelectionKey.OP_ACCEPT);

        log("Echo server ready. Ctrl-C to stop.");

        // processing
        while (true) {
            // wait for events
            this.selector.select();

            // wakeup to work on selected keys
            Iterator keys = this.selector.selectedKeys().iterator();
            while (keys.hasNext()) {
                SelectionKey key = (SelectionKey) keys.next();

                // this is necessary to prevent the same key from coming up 
                // again the next time around.
                keys.remove();

                if (! key.isValid()) {
                    continue;
                }

                if (key.isAcceptable()) {
                    this.accept(key);
                }
                else if (key.isReadable()) {
                    this.read(key);
                }
                else if (key.isWritable()) {
                    this.write(key);
                }
                else if(key.isConnectable()) {
                    this.connect(key);
                }
            }
        }
    }

    private void accept(SelectionKey key) throws IOException {
        ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
        SocketChannel channel = serverChannel.accept();
        channel.configureBlocking(false);
        send_message(key, "Welcome."); //DEBUG

        Socket socket = channel.socket();
        SocketAddress remoteAddr = socket.getRemoteSocketAddress();
        log("Connected to: " + remoteAddr);

        // register channel with selector for further IO
        dataMap_out.put(channel, new ArrayList<byte[]>());
        channel.register(this.selector, SelectionKey.OP_READ);

        //store key in 'keys' to be accessable by ID from messenger thread //TODO first get ID
        keys.put(0, key);
    }

    //TODO verify, test
    public void init_connect(String addr, int port){
        try {
            SocketChannel channel = createSocketChannel(addr, port);
            channel.register(this.selector, channel.validOps()/*, SelectionKey.OP_?*/);
        }
        catch (IOException e) {
            //TODO handle
        }
    }

    //TODO verify, test
    private void connect(SelectionKey key) {
        SocketChannel channel = (SocketChannel) key.channel();
        try {
            channel.finishConnect(); //try to finish connection - if 'false' is returned keep 'OP_CONNECT' registered
            //store key in 'keys' to be accessable by ID from messenger thread //TODO first get ID
            keys.put(0, key);
        }
        catch (IOException e0) {
            try {
                //TODO handle ok?
                channel.close();
            }
            catch (IOException e1) {
                //TODO handle
            }
        }

    }

    private void read(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();

        ByteBuffer buffer = ByteBuffer.allocate(8192);
        int numRead = -1;
        try {
            numRead = channel.read(buffer);
        }
        catch (IOException e) {
            e.printStackTrace();
        }

        if (numRead == -1) {
            this.dataMap_out.remove(channel);
            Socket socket = channel.socket();
            SocketAddress remoteAddr = socket.getRemoteSocketAddress();
            log("Connection closed by client: " + remoteAddr); //TODO handle
            channel.close();
            return;
        }

        byte[] data = new byte[numRead];
        System.arraycopy(buffer.array(), 0, data, 0, numRead);
        in_msg.add(new String(data, "utf-8"));
    }

    private void write(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();
        List<byte[]> pendingData = this.dataMap_out.get(channel);
        Iterator<byte[]> items = pendingData.iterator();
        while (items.hasNext()) {
            byte[] item = items.next();
            items.remove();
            //TODO is this correct? -> re-doing write in loop with same buffer object
            ByteBuffer buffer = ByteBuffer.wrap(item);
            int bytes_to_write = buffer.capacity();
            while (bytes_to_write > 0) {
                bytes_to_write -= channel.write(buffer);
            }
        }
        key.interestOps(SelectionKey.OP_READ);
    }

    public void queue_data(SelectionKey key, byte[] data) {
        SocketChannel channel = (SocketChannel) key.channel();
        List<byte[]> pendingData = this.dataMap_out.get(channel);
        key.interestOps(SelectionKey.OP_WRITE);

        pendingData.add(data);
    }

    public void send_message(int id, String msg) {
        SelectionKey key = keys.get(id);
        if (key != null)
            send_message(key, msg);
        //else
            //TODO handle
    }

    public void send_message(SelectionKey key, String msg) {
        try {
            queue_data(key, msg.getBytes("utf-8"));
        }
        catch (UnsupportedEncodingException ex) {
            //is not thrown: utf-8 is always defined
        }
    }

    public String get_message() {
        return in_msg.poll();
    }

    private static void log(String s) {
        System.out.println(s);
    }

    @Override
    public void run() {
        try {
            start_server();
        }
        catch (IOException e) {
            System.out.println("IOException: " + e);
            //TODO handle exception
        }
    }    


    // Creates a non-blocking socket channel for the specified host name and port.
    // connect() is called on the new channel before it is returned.
    public static SocketChannel createSocketChannel(String hostName, int port) throws IOException {
        // Create a non-blocking socket channel
        SocketChannel sChannel = SocketChannel.open();
        sChannel.configureBlocking(false);

        // Send a connection request to the server; this method is non-blocking
        sChannel.connect(new InetSocketAddress(hostName, port));
        return sChannel;
    }
}

My question: Is the above code correct and good or what should I change? 我的问题:上面的代码是正确的还是好的,或者我应该改变什么? How do I implement the requirements I mentioned above correctly? 如何正确实现上面提到的要求? Also note my "TODO"s. 另请注意我的“TODO”。

Thank you for any help! 感谢您的任何帮助!

There are several problems here. 这里有几个问题。

  1. You aren't checking the result of write(). 您没有检查write()的结果。 It can return anything from zero up. 它可以从零开始返回任何内容。 You may have to re-do it more than once. 您可能不得不多次重做。

  2. If finishConnect() returns false it isn't an error, it just hasn't finished yet, so just leave OP_CONNECT registered and wait for it to fire (again). 如果finishConnect()返回false则不是错误,它还没有完成,所以只需保持OP_CONNECT注册并等待它(再次)触发。 The only validOps() for a SocketChannel you have just created via SocketChannel.open() is OP_CONNECT. 您刚刚通过SocketChannel.open()创建的SocketChannel的唯一validOps()是OP_CONNECT。 If finishConnect() throws an Exception, that's an error, and you should close the channel. 如果finishConnect()抛出异常,那就是错误,您应该关闭该通道。

  3. Closing a channel cancels the key, you don't have to cancel it yourself. 关闭频道取消密钥,您不必自己取消密钥。

  4. Generally you should use null as the local InetAddress when binding. 通常,绑定时应使用null作为本地InetAddress。

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

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