2009-05-09 8 views
3

간단한 nio 기반 자바 서버가 있다고 가정 해 보겠습니다. 예 : (간략화 된 코드) :자바에서 비동기 채널 닫기 NIO

while (!self.isInterrupted()) { 
    if (selector.select() <= 0) { 
    continue; 
    } 

    Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); 
    while (iterator.hasNext()) { 
    SelectionKey key = iterator.next(); 
    iterator.remove(); 
    SelectableChannel channel = key.channel(); 

    if (key.isValid() && key.isAcceptable()) { 
     SocketChannel client = ((ServerSocketChannel) channel).accept(); 
     if (client != null) { 
     client.configureBlocking(false); 
     client.register(selector, SelectionKey.OP_READ); 
     } 
    } else if (key.isValid() && key.isReadable()) { 
     channel.read(buffer); 
     channel.close(); 
    } 
    } 
} 

따라서 간단한 단일 스레드 비 차단 서버입니다.

문제는 다음 코드에 있습니다.

channel.read(buffer); 
channel.close(); 

같은 스레드 (연결을 허용하고 데이터를 읽는 스레드)에서 채널을 닫으면 모두 정상적으로 작동합니다. 하지만 다른 스레드에서 연결이 닫히면 문제가 발생합니다. 예를 들어

((SocketChannel) channel).read(buffer); 
executor.execute(new Runnable() { 
    public void run() { 
    channel.close(); 
    } 
}); 

이 시나리오에서 서버의 상태 TIME_WAIT에 소켓이 있고 클라이언트에 ESTABLISHED가 설정되었습니다. 따라서 연결이 정상적으로 닫히지 않습니다. 어떤 아이디어가 잘못된거야? 나는 무엇을 놓쳤는가?

+1

호스트 OS는 무엇입니까? 이것은 패키지 사양이 모두 스레드 안전 구조임을 주장하기 때문에 구현 기능/버그 일 수 있습니다. – alphazero

+0

Mac OS X 10.5.6 하의 호스트 시스템. 오늘 저는 Windows 7 하에서 반복 테스트를하고 있습니다. 네가 옳은 것 같아. –

+0

채널을 닫기 전에 channel.socket()을 닫으면 어떻게됩니까? – Nuoji

답변

0

닫기가 예외를 throw하지 않는 한 왜 효과가 있는지 알 수 없습니다. 만약 그렇다면 당신은 예외를 보지 않을 것입니다. 나는 catch (Throwable t)를 닫고 (예외가 있다고 가정하고) 예외를 출력하는 것이 좋습니다.

+0

나는 그것을 시험해 보았다. 내 코드의 모든 핵심 포인트가 로깅 예외와 함께 try-catch로 래핑됩니다. 이 시점에서 예외는 발생하지 않습니다. 채널을 닫기 전에 채널이 닫힌 후에 활성화 상태가됩니다 (단, TIME_WAIT 상태의 소켓). –

0

조금 더 신중한 테스트를 거친 후에 Mac에서 결과를 재현 할 수 없습니다.

연결이 서버 측에서 닫힌 후 약 1 분 동안 TIME_WAIT에 남아있는 것은 사실이지만 클라이언트 측에서 즉시 닫힙니다 (테스트 할 텔넷 클라이언트를 사용하여 연결하면).

채널을 닫는 스레드에 관계없이 동일합니다. 어떤 머신에서 실행 중이며 어떤 Java 버전입니까?

0

the problem mentioned here과 관련이있을 수 있습니다. 만약 실제로 이라면, BSD/OS X poll() 메소드의 동작은 여러분이 운이 좋았다고 생각합니다. 나는 그것을 이해 - -

은 내가 인해 비 휴대용으로이 코드를 표시 할 것입니다 생각 TIME_WAIT 소켓을 닫을 수 요청을받은 OS를 의미

1

BSD/OS X의 버그,하지만 대기 클라이언트 측에서 가능한 늦은 통신을 위해. 클라이언트는 RST를 얻지 못했을 것입니다. RST가 아직 확립 된 것으로 생각하기 때문입니다. 그것은 자바가 아니라 OS입니다. 어떤 이유로 든 RST는 OS에 의해 지연됩니다.

다른 스레드에서 닫을 때만 발생하는 이유는 무엇입니까? OS가 다른 스레드에서 닫히면 원본 스레드가 종료 될 때까지 기다려야한다고 생각할 수도 있습니다. 내가 말했듯이, 그것은 OS 내부 역학이다.

0

예제에 큰 문제가 있습니다. 자바 NIO의 동의하고있는 스레드와

는() 해야()가을 받아 일을. 토이 예제는 아마도 예상되는 높은 연결 수 때문에 Java NIO를 사용하고있을 것입니다. 심지어 이라면에 대해 select와 동일한 스레드에서 읽기를 수행한다고 생각하면 보류중인 선택되지 않은 선택이 연결이 설정 될 때까지 대기하는 시간이 초과됩니다. 이 overwrought 쓰레드가 연결을 받아들이려고 할 때, 양쪽에있는 OS는 포기하고 accept()는 실패 할 것이다.

선택 스레드에서 절대 최소값 만 지정하십시오. 더 이상 당신은 최소한 만 할 때까지 코드를 다시 작성합니다.

만 장난감의 예에서

는 읽기는 주 스레드에서 처리되어야 [응답에서 언급하기].

  • 300 + 동시 연결 시도를 :

    은 처리하려고합니다.

  • 연결이 완료되면 24K 바이트를 단일 서버, 즉 작은 웹 페이지 인 작은 .jpg로 보냅니다.
  • 각 연결을 약간 늦추십시오 (전화 접속을 통해 연결이 설정되었거나 네트워크의 오류/재시도 속도가 높음). 따라서 TCP/IP ACK가 제어 OS 수준의 문제)
  • 몇 가지 테스트 연결이 있고 1 밀리 초마다 한 바이트를 보냅니다. (이것은 자신의 고부하 조건을 가진 클라이언트를 시뮬레이트하므로 매우 느린 속도로 데이터를 생성합니다.) 스레드는 24K 바이트만큼 1 바이트를 처리하는 데 거의 같은 노력을해야합니다.
  • 경고없이 연결이 끊어졌습니다 (연결이 끊어진 문제).

실용적인 문제로서 연결 시도는 시도하는 기계가 연결을 끊기 전에 500ms -1500ms 내에 설정되어야합니다.

이러한 모든 문제로 인해 단일 스레드는 다른 쪽 컴퓨터가 연결 시도를 포기하기 전에 모든 연결을 빠르게 설정할 수 없습니다. 읽기는 다른 스레드에 있어야합니다. 기간.

[키워드 점수] 나는 이것에 대해 정말로 깜빡했다. 그러나 독서를하는 스레드는 자신의 Selector를 갖습니다. 연결을 설정하는 데 사용 된 선택기를 사용하여 새 데이터를 수신해서는 안됩니다. 형편의 충돌에 응답

첨가 (에는 I/O는 실제로 스트림을 판독하는 자바 호출 동안 발생하지.

그 버퍼가 가득 차면 각 층. 상기 IO 정의 된 버퍼 크기했다 예를 들어, TCP/IP 버퍼는 연결 당 8K-64K 버퍼를 가지며 일단 TCP/IP 버퍼가 채워지면 수신 컴퓨터는 보내는 컴퓨터에 중지를 알립니다. 수신 컴퓨터가 버퍼링 된 바이트를 충분히 빨리 처리하지 않으면

수신 컴퓨터가 버퍼링 된 바이트를 처리하는 경우 보낸 사람은 다음 주소로 계속 스트리밍합니다. tes, Java io 읽기 호출이 수행되는 동안.

또한 도착할 첫 번째 바이트가 선택기에서 "읽을 수있는 바이트 수"를 트리거하는 것을 확인하십시오. 얼마나 많은 사람들이 도착했는지는 보증 할 수 없습니다.

Java 코드에 정의 된 버퍼 크기는 OS의 버퍼 크기와 관계가 없습니다.

+0

메인 스레드가 소켓에서 데이터를 읽어야한다는 것을 기억하십시오. 이 판독 값은 차단되지 않습니다 (데이터는 이미 읽을 준비가되어 있습니다). 다른 스레드로 읽기를 위임하면 선택기 스레드가 동일한 이벤트에서 여러 번 깨어납니다. 이것은 병렬 스레드 때문에 아마도 선택기 스레드가 다음 선택을 수행하기 전에 읽을 시간을 갖지 않을 것입니다. –

+0

"Key Point"가 끝에 추가되었습니다. – Pat

+0

나는 동의하지 않는다. 비 차단 비동기 방식으로 서버를 설정하는 경우 ** 비 차단 **이므로 모든 I/O 작업을 스레드하지 않아도됩니다. 즉, 메서드 자체를 호출하면 간단한 작업 만 수행하는 것입니다. OS 호출. 실제로 호출 한 스레드에서 I/O 작업을 수행하지는 않습니다. 또한 여러 개의 '선택자'가 가장 무의미한 아이디어입니다. '선택자'는 OS가 허용하는 한 빨리 작동 할 수 있습니다. 배수로 더하는 것은 다른 어떤'Selector's **의 준비 선택을 늦추기 만합니다 **. –