[자바-NIO]원격 소켓의 종료 여부를 체크하는 방법이 있는지요?
자바 NIO로 서버를 개발하고 있습니다.(비블로킹 모드)
많은 분들이 문의를 하고 있는 내용입니다만
적절한 해답을 구하지 못해서 글을 올립니다.
원격 클라이언트 소켓이 종료되었을때 (정상/비정상)
서버에서 클라이언트 소켓이 Close 되었는지 알 수 있는 방법이 있는지,
없다면 다른분들은 어떻게 처리하셨는지 알고 싶습니다.
서버 구조는 Producer-Consumer 패턴으로
Selector를 Accept용, Read/Write용으로 구분하여 사용하고 있습니다.
-----------------------------------------------------------------------------------
Accept Selector monitor thread | AcceptQueue | AcceptWorker Thread(pool)
-----------------------------------------------------------------------------------
Read Selector monitor thread(pool) ① | ReadQueue | ReadWorker Thread(pool) ②
-----------------------------------------------------------------------------------
클라이언트에서는 Receive 후 바로 소켓을 종료 처리하고 있으며
이때 OP_READ 이벤트를 등록한 Selector에 계속 read 이벤트가 계속 들어옵니다.
①에서는 read 이벤트가 들어올 때 ReadQueue에 selectedKey를 push하고
②에서 Queue를 조회하여 실제 read 하는데 read 메소드의 리턴값(-1)을 체크하여
해당 Selector에서 소켓채널을 해제할 수 있지만
이미 ①에서 의미없는 값을 Queue에 쌓아놓음으로 해서 문제가 발생합니다.
①에서 클라이언트 소켓이 종료되었는지를 체크할 방법이 있는지 궁금합니다.
또는 구조가 잘못되어 있다면 어떤 식이 좋을지 알려주시면 감사하겠습니다.
① READ 이벤트를 대기하는 쓰레드 부분
public void run() { try { while(!Thread.currentThread().isInterrupted()) { selector.select(); // OP_READ 이벤트 대기 Iterator iter = selector.selectedKeys().iterator(); while(iter.hasNext()) { SelectionKey key = (SelectionKey) iter.next(); iter.remove(); <span>// 여기에서 해당 소켓채널의 클라이언트 소켓이 종료되었는지 체크하고 싶습니다.</span> pushJob(key); // ReadQueue 에 push } } } catch(Exception e) { e.printStackTrace(); } }
클래식IO에서는
제가 NIO로는 아직 본격적으로 개발해 본 적이 없어서 답변이 될 지 모르겠습니다만...
우선 read, write시의 오류처리
핑퐁 프로토콜 및 타임아웃 정의 등으로 연결 끊김을 정의했습니다.
말씀하신 내용중에
- ①에서 클라이언트 소켓이 종료되었는지를 체크할 방법이 있는지 궁금합니다.
이건 SocketChannel의 isConnected()가 있긴 한데 실제 그 의미인지 잘 모르겠구요, 그런 의미라고 해도 여러 네트웍 환경에 따라 실제 디텍트가 안되는 경우도 있을겁니다(방화벽같은).
결국 타임아웃을 정의하고 이에 맞게 핑퐁 프로토콜을 만들어서 API에서 커버할 수 없는 경우에 대비하는것도 만일을 위해 좋을것 같습니다.
- 이미 ①에서 의미없는 값을 Queue에 쌓아놓음으로 해서 문제가 발생합니다.
이 경우는 어떤 경우가 문제가 되는지 정확히 알 수가 없네요.
쓸데 없는 패킷값을 읽은 경우인가요?
아니면 처리할 필요 없는 잡이 큐에 등록된게 문제가 되나요?
처리중에 오류로 판단되거나 잘못된 값이 들어오면 여기서 채널을 셀렉터에서 등록 해제하는건 문제가 되는건가요?
-- Signature --
青い空大好き。
蒼井ソラもっと好き。
파란 하늘 너무 좋아.
아오이 소라 더좋아.
감사합니다.
우선 답변 주셔서 감사합니다.
일단 저도 isConnected() 가 그런 역할을 하지 않을까 했는데 API Doc을 보니 아니더군요.
일단 종료 프로토콜을 정의하고 그 타임아웃 을 오버하면 원격 소켓이 disconnect된 걸로 간주해야 하는군요
큐의 문제는 쓸모없는 잡이 큐에 너무 많이 쌓여서 실제 Worker에서 pop을 모두 하기전까지
다른 채널에서 들어온 처리해야할(Read해야할) 잡이 뒤로 밀린다는데 있습니다.
소켓이 정상 연결되어 있을 때는 read 이벤트가 실제로 receive 데이터가 있을때
한번만 들어오는데 왜 원격 소켓채널이 종료되었을 때 의미없는 read 이벤트가 연속적으로
발생하게 했는지 모르겠습니다.
read이벤트 발생으로
read이벤트 발생으로 읽은 길이가 -1(0이 나올 가능성도 포함해서)이 나온면 어떻게 하는지 자세히 설명해 주실 수 있나요?
채널에서 cancel하기 직전까지 많은 양의 이벤트가 이미 발생해 있는건가요?
-- Signature --
青い空大好き。
蒼井ソラもっと好き。
파란 하늘 너무 좋아.
아오이 소라 더좋아.
계속 관심 가져 주셔서 감사합니다 ^^
현재 -1이 라면 해당 키를 셀렉터에서 해제하고 있습니다.
하지만 그전에 이미 큐에 쓰레기 이벤트가 엄청나게 쌓여있습니다.
일단 소스를 올려보겠습니다.
각 클래스들의 역할에 대해 "file 설명.txt" 에 기술하였습니다.
바쁘실텐데 시간을 내어 주셔서 감사합니다.
이미 해결하셨을지도 모르지만...
답변이 늦어서 죄송합니다.
소스를 볼 시간이 잘 안나서 이제서야 보게 되었네요.
간단하게 테스트를 해 본 결과 ReadWorker, ReadSelectorMonitor의 분리에 의한 동기화 문제일 가능성이 높아 보입니다.
ReadSelectorMonitor에서 SelectionKey를 큐에 넣어주고 다시 루프의 처음으로 돌아가서 select하는 시점에 ReadWorker에서 아직 Read를 완료하지 못해서 다시 동일한 읽기 버퍼에 대한 SelectionKey를 얻어 큐에 넣고 있습니다.
이를 동기화 부분을 제거하고 ReadWorker를 쓰지 않고 ReadSelectorMonitor에서 직접 읽으면 말씀하신 현상은 일어나지 않는것으로 보입니다.
현재 올려주신 소스를 보면 상당히 복잡한 동기화 구조로 되어 있어서 제가 정확하게 원하는 답변을 드린건지는 잘 모르겠습니다.
단, 실제 하는 일이 상당히 단순한데 비해서 구조는 매우 복잡하게 되어 있어서 디버깅이나 분석이 쉽지 않아 보입니다.
주제넘은 말일지도 모르지만 아직 NIO나 Concurrent에 많이 익숙하지 않으시다면 아주 단순한 구조부터 시작해서 API나 동작 구조에 대해 정확히 인지하게 된 후에 복잡한 구조로 확장해 나가는것도 좋지 않을까 생각됩니다.
-- Signature --
青い空大好き。
蒼井ソラもっと好き。
파란 하늘 너무 좋아.
아오이 소라 더좋아.
댓글 달기