简体   繁体   English

关闭套接字唤醒选择器

[英]close socket wakes-up selector

We wrote an incoming reactor that works this way: 我们写了一个以这种方式工作的传入反应器:

  1. Opens a selector 打开选择器
  2. Opens a server socket channel 打开服务器套接字通道
  3. Starts a selection loop in which: The ServerSocketChannel accepts new SocketChannels into the loop, and each SocketChannel reads data and transfers it to a worker. 启动一个选择循环,其中:ServerSocketChannel接受新的SocketChannel进入循环,每个SocketChannel读取数据并将其传输给worker。

The shutting down procedure of the reactor is iterating over the selector.keys() and for each of them closing the corresponding channel and cancelling the key. 反应器的关闭程序在selector.keys()迭代,并且每个关闭相应的通道并取消键。

We wrote the following unit test for the shutdown procedure: 我们为关机程序编写了以下单元测试:

  1. Open a reactor thread running the selction loop. 打开运行选择循环的反应器线程。
  2. Open several Sender threads. 打开几个Sender线程。 Each opens a socket to the reactor and reads. 每个都打开一个到反应器的插座并读取。
  3. The read blocks until it gets -1 (meaning the reactor closed the socket). 读取阻塞直到它变为-1(意味着反应器关闭了套接字)。
  4. After the read returns -1, the sender closes the socket and finishes. 读取返回-1后,发件人关闭套接字并完成。

The test causes ConcurrentModificationException pointing to the loop iterating over the sockets and closes them (which was in the main thread context). 该测试导致ConcurrentModificationException指向循环迭代套接字并关闭它们(在主线程上下文中)。

Our assumption is that when a Sender read method got -1, it closed the socket and somehow it woke up the selector select method, The selector then accessed its keys set which was iterated by the shutdown loop and hence the exception. 我们的假设是,当Sender读取方法得到-1时,它关闭套接字并以某种方式唤醒了选择器选择方法,然后选择器访问其由关闭循环迭代的密钥集,因此异常。

We worked around this problem by creating a new list with all the keys of the selector. 我们通过使用选择器的所有键创建一个新列表来解决这个问题。 Canceling those keys by iterating this list prevent two objects from modifying the same key's set. 通过迭代此列表来取消这些键可防止两个对象修改同一个键的集合。

Our question are: 我们的问题是:

  1. Is our assumption correct? 我们的假设是否正确? When the client socket calls the close method- does it really wake up the selector? 当客户端套接字调用close方法时 - 它是否真的唤醒了选择器?
  2. Does the creation of a new list is the appropriate solution or is it just a work-around? 创建新列表是否是适当的解决方案还是仅仅是一种解决方法?

EDIT: Added some code snippets for clarifications (We tried to narrow the code as possible) 编辑:添加了一些代码片段以进行说明(我们试图尽可能缩小代码范围)

IncomingReactor: IncomingReactor:

public boolean startAcceptingIncomingData() {
    Selector selector = Selector.open();
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open());
    serverSocketChannel.bind(new InetSocketAddress(incomingConnectionsPort));
    serverSocketChannel.configureBlocking(false);
    SelectionKey acceptorSelectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    acceptorSelectionKey.attach((Worker) this::acceptIncomingSocket);
    startSelectionLoop(selector);
    return true;
  }

private boolean acceptIncomingSocket() {
    try {
        SocketChannel socketChannel = serverSocketChannel.accept();
        socketChannel.configureBlocking(false);
        SelectionKey selectionKey = socketChannel.register(selector, SelectionKey.OP_READ);
        selectionKey.attach(new WorkerImpl() /*Responsible for reading data and tranferring it into a parsing thread*/);
        return true;
    } catch (IOException e) {
      return false;
    }
  }

private void startSelectionLoop(Selector selector) {
    shouldLoop = true;
    while (shouldLoop) {
      try {
        selector.select();
        Set<SelectionKey> selectedKeys = selector.selectedKeys();
        if (!shouldLoop) {
          break;
        }
        selectedKeys.forEach((key) -> {
          boolean workSuccess = ((Worker) key.attachment()).work();
          if (!workSuccess) {
              key.channel().close();
              key.cancel();
          }
        });
        selectedKeys.clear();
      } catch (ClosedSelectorException ignore) {
      }
    }
  }

public void shutDown() {
    shouldLoop = false;
    selector.keys().forEach(key -> { /***EXCEPTION - This is where the exception points to (this is line 129) ***/
        key.channel().close();
        key.cancel();
    });
    try {
      selector.close();
    } catch (IOException e) {
    }
  }

UnitTest: 单元测试:

   @Test
  public void testMaximumConnectionsWithMultipleThreads() {
    final int PORT = 24785;
    final int MAXINUM_CONNECTIONS = 10;

    IncomingReactor incomingReactor = new IncomingReactor(PORT);
    Callable<Boolean> acceptorThread = () -> {
      incomingReactor.startAcceptingIncomingData();
      return true;
    };

    ExecutorService threadPool = Executors.newFixedThreadPool(MAXIMUM_CONNECTIONS + 1);
    Future<Boolean> acceptorFuture = threadPool.submit(acceptorThread);

    List<Future<Boolean>> futureList = new ArrayList<>(MAXIMUM_CONNECTIONS);
    for (int currentSenderThread = 0; currentSenderThread < MAXIMUM_CONNECTIONS; currentSenderThread++) {
      Future<Boolean> senderFuture = threadPool.submit(() -> {
        Socket socket = new Socket(LOCALHOST, PORT);
        int bytesRead = socket.getInputStream().read();
        if (bytesRead == -1) { //The server has closed us
          socket.close();
          return true;
        } else {
          throw new RuntimeException("Got real bytes from socket.");
        }
      });
      futureList.add((senderFuture));
    }

    Thread.sleep(1000); //We should wait to ensure that the evil socket is indeed the last one that connects and the one that will be closed
    Socket shouldCloseSocket = new Socket(LOCALHOST, PORT);
    Assert.assertEquals(shouldCloseSocket.getInputStream().read(), -1);
    shouldCloseSocket.close();
    incomingReactor.shutDown();
    for (Future<Boolean> senderFuture : futureList) {
      senderFuture.get();
    }
    acceptorFuture.get();
    threadPool.shutdown();
  }

Exception: 例外:

java.util.ConcurrentModificationException
    at java.util.HashMap$HashIterator.nextNode(HashMap.java:1437)
    at java.util.HashMap$KeyIterator.next(HashMap.java:1461)
    at java.lang.Iterable.forEach(Iterable.java:74)
    at java.util.Collections$UnmodifiableCollection.forEach(Collections.java:1080)
    at mypackage.IncomingReactor.shutDown(IncomingReactor.java:129)
    at mypackage.tests.TestIncomingReactor.testMaximumConnectionsWithMultipleThreads(TestIncomingReactor.java:177)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.testng.internal.MethodInvocationHelper.invokeMethod(MethodInvocationHelper.java:85)
    at org.testng.internal.Invoker.invokeMethod(Invoker.java:659)
    at org.testng.internal.Invoker.invokeTestMethod(Invoker.java:845)
    at org.testng.internal.Invoker.invokeTestMethods(Invoker.java:1153)
    at org.testng.internal.TestMethodWorker.invokeTestMethods(TestMethodWorker.java:125)
    at org.testng.internal.TestMethodWorker.run(TestMethodWorker.java:108)
    at org.testng.TestRunner.privateRun(TestRunner.java:771)
    at org.testng.TestRunner.run(TestRunner.java:621)
    at org.testng.SuiteRunner.runTest(SuiteRunner.java:357)
    at org.testng.SuiteRunner.runSequentially(SuiteRunner.java:352)
    at org.testng.SuiteRunner.privateRun(SuiteRunner.java:310)
    at org.testng.SuiteRunner.run(SuiteRunner.java:259)
    at org.testng.SuiteRunnerWorker.runSuite(SuiteRunnerWorker.java:52)
    at org.testng.SuiteRunnerWorker.run(SuiteRunnerWorker.java:86)
    at org.testng.TestNG.runSuitesSequentially(TestNG.java:1199)
    at org.testng.TestNG.runSuitesLocally(TestNG.java:1124)
    at org.testng.TestNG.run(TestNG.java:1032)
    at org.testng.IDEARemoteTestNG.run(IDEARemoteTestNG.java:74)
    at org.testng.RemoteTestNGStarter.main(RemoteTestNGStarter.java:124)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

The shutting down procedure of the reactor is iterating over the selector.keys() and for each of them closing the corresponding channel and cancelling the key. 反应器的关闭程序在selector.keys()上迭代,并且每个关闭相应的通道并取消键。

It should start by stopping the selector loop. 它应该从停止选择器循环开始。 NB Closing the channel cancels the key. 注意关闭频道会取消该键。 You don't have to cancel it yourself. 您不必自己取消它。

We wrote the following unit test for the shutdown procedure: 我们为关机程序编写了以下单元测试:

Open a reactor thread running the selction loop. 打开运行选择循环的反应器线程。 Open several Sender threads. 打开几个Sender线程。 Each opens a socket to the reactor and reads. 每个都打开一个到反应器的插座并读取。 The read blocks until it gets -1 (meaning the reactor closed the socket). 读取阻塞直到它变为-1(意味着反应器关闭了套接字)。

The reactor closed its accepted socket. 反应堆关闭了接受的插座。 Your client socket remained open. 您的客户端套接字保持打开

After the read returns -1, the sender closes the socket and finishes. 读取返回-1后,发件人关闭套接字并完成。

I hope this means the sender closed its client socket. 我希望这意味着发件人关闭了它的客户端套接字。

The test causes ConcurrentModificationException pointing to the loop iterating over the sockets and closes them (which was in the main thread context). 该测试导致ConcurrentModificationException指向循环迭代套接字并关闭它们(在主线程上下文中)。

Really? 真? I don't see any stack trace in your question. 我的问题中没有看到任何堆栈跟踪。

Our assumption is that when a Sender read method got -1, it closed the socket and somehow it woke up the selector select method 我们的假设是,当Sender读取方法得到-1时,它关闭了套接字,并以某种方式唤醒了选择器选择方法

Not possible unless the reactor didn't close the channel, in which case you wouldn't have got -1 from read etc. 除非反应堆没有关闭通道,否则不可能,在这种情况下你不会从读取等中获得-1。

The selector then accessed its keys set which was iterated by the shutdown loop and hence the exception. 然后,选择器访问其密钥集,该密钥集由关闭循环迭代,因此异常。

The exception is caused by modifying the key set during iteration. 异常是在迭代期间修改密钥集引起的。 Bug in your server code. 服务器代码中的错误。

We worked around this problem by creating a new list with all the keys of the selector. 我们通过使用选择器的所有键创建一个新列表来解决这个问题。 Canceling those keys by iterating this list prevent two objects from modifying the same key's set. 通过迭代此列表来取消这些键可防止两个对象修改同一个键的集合。

You need to fix the actual problem, and for that you need to post the actual code. 您需要修复实际问题,为此您需要发布实际代码。

Our question are: 我们的问题是:

Is our assumption correct? 我们的假设是否正确? When the client socket calls the close method- does it really wake up the selector? 当客户端套接字调用close方法时 - 它是否真的唤醒了选择器?

Not unless the selector-end channel is still open. 除非选择器端通道仍处于打开状态。

Does the creation of a new list is the appropriate solution or is it just a work-around? 创建新列表是否是适当的解决方案还是仅仅是一种解决方法?

It is just a nasty workaround for a problem you haven't identified yet. 对于尚未确定的问题,这只是一个讨厌的解决方法。

You cannot modify the selector.keys() Set<SelectionKey> from inside of the for loop because that Set is not capable of concurrent modification. 您无法修改selector.keys()从for循环内部Set<SelectionKey> ,因为该Set不能进行并发修改。 (calling channel.close() will modify the Set from inside the loop reading the Set ) (调用channel.close()将从循环内部修改Set ,读取Set

https://docs.oracle.com/javase/7/docs/api/java/util/HashSet.html https://docs.oracle.com/javase/7/docs/api/java/util/HashSet.html

The iterators returned by this class's iterator method are fail-fast: if the set is modified at any time after the iterator is created, in any way except through the iterator's own remove method, the Iterator throws a ConcurrentModificationException. 这个类的迭代器方法返回的迭代器是快速失败的:如果在创建迭代器之后的任何时候修改了set,​​除了通过迭代器自己的remove方法之外,Iterator抛出ConcurrentModificationException。 Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future. 因此,在并发修改的情况下,迭代器快速而干净地失败,而不是在未来的未确定时间冒着任意的,非确定性行为的风险。

SelectionKey[] keys = selector.keys().toArray(new SelectionKey[0]);

for( SelectionKey k : keys )
{
    try
    {
        k.channel().close();
    }
    catch(Throwable x )
    {
        // print
    }
}

try
{
    selector.close();
}
catch(IoException e )
{
    // print
}

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

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