Microsoft MVP성태의 닷넷 이야기
.NET Framework: 182. WCF의 InactivityTimeout [링크 복사], [링크+제목 복사],
조회: 22126
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 4개 있습니다.)
WCF의 InactivityTimeout


"유수석의 WCF 바이블", 다들 읽어보셨죠?
WCF 실무에 뛰어드시기 전에 꼭 한번 읽어보시기를 바랍니다. (사실 저도 ^^; 아직 9장까지밖에 못 읽었습니다.)

어쨌든! 저 역시 위의 책을 읽으면서 많이 배우고 있습니다. 예를 들어 FaultException 예외를 사용하면 세션이 Faulted 상태로 빠지지 않는다는 사실을 몰랐었고, WCF가 지원하는 세션을 크게 "트랜스포트 세션", "보안 세션", "신뢰할 수 있는 세션"으로 나눈다는 것을 머릿속으로 정리해놓지는 못했었습니다.

그런데,,, 옥의 티가 하나 있는데요. 바로 "신뢰할 수 있는 세션"의 "InactivityTimeout" 설명이 완벽하지 못하다는 정도! (완벽?)

그 책의 636페이지에 보면 아래와 같은 설명이 있는데요.

"
신뢰할 수 있는 세션은 자체적으로 타임아웃을 InactivityTimeout 속성을 통해 설정할 수 있다. 이 타임아웃 시간 동안 클라이언트가 서비스를 호출하지 않으면 신뢰할 수 있는 세션은 종료된다.
... [생략] ...
ReceiveTimeout과 InactivityTimeout 속성이 모두 설정될 수 있으므로 신뢰할 수 있는 세션에 실제로 적용되는 타임아웃 시간은 두 속성값 중 작은 값으로 결정됨을 알아두자.
"



위의 설명에 의하면, "ReceiveTimeout 설정과 InactivityTimeout 속성은 동일한 기능을 하는데, 클라이언트가 서버에 대해 지정된 ReceiveTimeout 또는 InactivityTimeout 시간 동안 호출하지 않으면 세션이 종료된다."로 요약될 수 있습니다. 단지, "트랜스포트 세션"에서는 ReceiveTimeout 값이 사용되고, "신뢰할 수 있는 세션"에서는 부가적으로 InactivityTimeout 값이 하나 더 제공된다는 정도의 의미로 해석됩니다.

하지만, 실제로 테스트 해보면 이 설명이 사실과 다르다는 것을 알 수 있습니다.

예전에 제가 아래의 글에서 InactivityTimeout 값을 설명한 적이 있었는데요.

WCF : netTcpBinding에서의 각종 Timeout 값 설명
; https://www.sysnet.pe.kr/2/0/536

그 때는 말로만 설명하는 것으로 그쳤지만, 마침 질문해 주신 분도 있고 하니 실험적으로 알 수 있는 방법을 제공하도록 하겠습니다.




이를 테스트 해보기 위해, 세션 사용이 가능한 netTcpBinding 종단점을 제공하는 서비스를 만들고 config에 다음과 같이 ReceiveTimeout, InactivityTimeout을 설정합니다.

<netTcpBinding>
    <binding name="netTcpBindingConfig" receiveTimeout="00:01:30" >
        <security mode="None"/>
        <reliableSession enabled="true" inactivityTimeout="00:00:10" ordered="true" />
    </binding>
</netTcpBinding>

서비스의 메서드는 간단하게 Echo 메서드 하나만 정의하고 다음과 같이 시간 출력을 합니다.

public void Echo()
{
    Console.WriteLine("Echo: " + DateTime.Now.ToString("HH:mm:ss"));

    OperationContext.Current.Channel.Closed += new EventHandler(Channel_Closed);
    OperationContext.Current.Channel.Faulted += new EventHandler(Channel_Faulted);
}

void Channel_Faulted(object sender, EventArgs e)
{
    Console.WriteLine("Endpoint Faulted: " + DateTime.Now.ToString("HH:mm:ss"));
}

void Channel_Closed(object sender, EventArgs e)
{
    Console.WriteLine("Endpoint Closed: " + DateTime.Now.ToString("HH:mm:ss"));
}

그리고, 클라이언트에서는 바인딩 값에 아래와 같이 시간을 다르게 설정합니다.

netTcpBinding.ReliableSession.Enabled = true;
netTcpBinding.ReliableSession.InactivityTimeout = new TimeSpan(0, 0, 15);

이제, 서비스를 시작하고 클라이언트에서 접속해서 Echo 메서드를 호출하면 다음과 같이 출력되고,

D:\...\bin>ConsoleApplication1.exe
Press any key to exit...
Echo: 02:09:45

클라이언트에서 연결을 닫지 않은 체로 1분 30초가 지나면 서버 측의 ReceiveTimeout에서 지정한 데로 연결이 끊깁니다. 그래서 최종적으로 아래와 같은 출력 결과를 얻을 수 있습니다.

D:\...\ConsoleApplication1\bin>ConsoleApplication1.exe
D:\...\bin>ConsoleApplication1.exe
Press any key to exit...
Echo: 02:09:45
Endpoint Closed: 02:11:15

보시는 것처럼, 위와 같은 결과를 얻기 위해 정확하게 1분 30초가 소요되었습니다. 만약, 책의 내용대로라면 10초만에 연결이 끊겼어야 합니다.

자, 이제 그럼 InactivityTimeout 값을 테스트 해볼까요?

서버 측은 여전히 동일하게 실행하고, 클라이언트는 실행 후 연결을 닫지 않고 "Ctrl + C" 키를 누르거나 혹은 작업관리자를 통해서 강제 종료를 해봅니다. 이번에는 서버와 클라이언트에서 다음과 같이 동작하는 것을 확인할 수 있습니다.

wcf_inactivityTimeout_test_1.png

보시는 것처럼, 클라이언트는 2시 13분 07초에 프로그램을 (비정상) 종료했고 서버는 정확히 InactivityTimeout 값에 지정된 10초 후인 2시 13분 17초에 클라이언트와의 연결을 종료시켰습니다.

자, 그럼 이번엔 반대로 서버를 강제 종료해봅니다.

wcf_inactivityTimeout_test_2.png

서버가 (비정상) 종료한 시간은 2시 16분 03초이고, 클라이언트는 14초 걸려서 연결을 Faulted로 돌렸는데 이 값은 클라이언트 측의 코드를 통해 지정한 15초에 가까운 값입니다.

이 정도면, InactivityTimeout 값에 대해 알아볼 수 있는 실제적인 실험이었다고 볼 수 있겠지요.

테스트 한 프로젝트를 올려두었으니 각자 로컬에서 테스트 해보시고 의심스러운 부분이 있으면 질문 주시기 바랍니다.




제가 위에서 "완벽"하지 못하다고 말씀을 드렸는데요. 위의 테스트 결과대로라면 "완벽"이 아니라 아예 틀렸다고 볼 수 있는데, 왜 그런 단어를 사용했을까요? 궁금하지 않으세요? ^^

InactivityTimeout 값이 매우 헷갈리는 것이 하나 있는데요. 바로 서버 측의 InactivityTimeout 값을 10초 미만으로 조정하면 그 동작이 ReceiveTimeout과 동일하다는 이상한(?) 규칙을 가지고 있다는 점입니다. 즉, 위의 테스트에서 서버 측의 InactivityTimeout 값을 6초로 지정하면, 6초 내에 클라이언트로부터 호출이 없으면 연결이 끊기게 됩니다.

엄청 재미있는 사실은, 클라이언트 측의 InactivityTimeout 값을 10초 미만으로 지정하는 것은 무관하다는 것입니다. 즉 서버 측에 대해서만 10초 미만으로 지정한 경우 ReceiveTimeout처럼 동작합니다.

제 생각에는 아마도 이것은 버그가 아닐까 생각됩니다. 일관성을 위해서 InactivityTimeout 값을 10초 미만으로 지정해도 위의 테스트 결과대로 나와야 합니다. (버그가 아니라면, 아마도 상대편이 죽은 경우 곧바로 자원을 회수해야 하는 특별한 서비스를 위해서 10초 미만으로 지정되는 경우에 한해 그와 같은 동작을 일부러 하도록 한 것이 아닐런지...!)

아무튼, 이런 이상한 현상으로 인해 10초 미만에 대해서는 책에서 설명한 내용이 맞기 때문에 "완벽"하지 않다고 표현한 것입니다.

참고로, .NET 3.5/4.0에서 모두 위와 같이 10초 현상을 겪고 있습니다.




좀 더 부가적인 설명을 드리자면, 위의 InactivityTimeout 실험 데이터는 아주 이상적인 경우에 맞아 떨어진 수치입니다. 왜냐하면, InactivityTimeout 값을 판단하기 위한 서버와 클라이언트의 heart-beat 체크가 정확히 1초마다 이뤄지는 것은 아니기 때문입니다. 확인을 위해 소켓 API를 가로채기 해보면 다음과 같은 결과를 얻을 수 있습니다.

[Srv] Send: 03시 01분 19초
[Cln] Send: 03시 01분 19초
[Srv] Send: 03시 01분 26초
[Cln] Send: 03시 01분 26초
[Srv] Send: 03시 01분 34초
[Cln] Send: 03시 01분 34초
[Srv] Send: 03시 01분 41초
[Cln] Send: 03시 01분 41초

서버(Srv)와 클라이언트(Cln) 모두 서로를 대략 7초 간격으로 확인하는 것을 볼 수 있는데요. 7초 간격은 클라이언트 측의 15초에서 절반으로 나뉜 시간입니다. 만약 18초로 설정하면 9초 간격으로 서로 체크하는 것이 확인됩니다.

테스트에 의하면, heart-beat을 체크하는 주기는 "서버와 클라이언트 중 큰 시간의 절반"과 "서버와 클라이언트 중 작은 값"을 비교해서 낮은 값으로 설정하게 됩니다.

예를 들어, 서버는 15초, 클라이언트는 20초라고 설정되어 있다면 "20 / 2 = 10초 < 15초"이기 때문에 10초로 선택되어 클라이언트, 서버 양측 모두 10초 단위로 서로 살아 있는지를 확인하게 됩니다.

(물론, 이 실험값은 공식적으로 문서화된 것이 아니기 때문에 향후 닷넷 프레임워크에서는 달라질 수 있겠고!)

그래서, 정확하게 상대편에서 종료한 시점부터 7초가 아니고, 체크하는 바로 그 시점에 상대편이 살아있어야 합니다. 즉, 위의 소켓 가로채기에서 보면 클라이언트가 03시 01분 37초에 종료했다면 그로부터 7초 후에 서버가 연결을 끊는 것이 아니고, 03시 01분 41초 - 체크를 하는 그 시점에 확인 후 곧바로 연결을 종료하게 됩니다.

한 가지 더, 위의 heart-beat 주기는 ReceiveTimeout에도 적용됩니다. 이 때문에, ReceiveTimeout 값이 1분 30초이고, InactivityTimeout 설정의 영향으로 heart-beat이 10초로 설정되어 있다면 최대 1분 40초까지 ReceiveTimeout이 적용될 수 있습니다. 즉, 03시 01분 10초에 마지막으로 클라이언트로부터 호출을 받았던 서비스인 경우 heart-beat 확인이 03시 02분 39초에 되었다면 그 시점에는 1분 30초가 지난 것이 아니기 때문에 연결이 끊기지 않다가 다음번 heart-beat 확인이 되는 03시 02분 49초가 되어서야 연결이 끊기게 됩니다.



[이 토픽에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 7/12/2021]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2010-07-31 12시28분
참... 이런 의미에서, 책의 790페이지에 있는 "리스트 9-17"의 코드가 변경되어야 합니다.

CommuicationState 상태는 클라이언트가 비정상 종료(또는 네트워크가 끊겼다거나)했을 때는 알아낼 수 없기 때문입니다. 즉, InactivityTimeout으로 인해 또는 ReceiveTimeout으로 인해 클라이언트 연결이 정리되기 이전에 CommuicationState 상태를 알아내려고 시도하면 Opened로 나오기 때문에 그런 상황에서는 callback.OnTick 메서드 호출이 예외가 발생할 수 있습니다.

즉, 그 부분도 try/catch로 감싸야 한다는!
kevin25

1  2  3  4  5  6  7  8  9  10  11  [12]  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13332정성태4/27/20233870Windows: 248. Win32 C/C++ - 대화창을 위한 메시지 루프 사용자 정의파일 다운로드1
13331정성태4/27/20233919오류 유형: 856. dockerfile - 구 버전의 .NET Core 이미지 사용 시 apt update 오류
13330정성태4/26/20233566Windows: 247. Win32 C/C++ - CS_GLOBALCLASS 설명
13329정성태4/24/20233760Windows: 246. Win32 C/C++ - 직접 띄운 대화창 템플릿을 위한 Modal 메시지 루프 생성파일 다운로드1
13328정성태4/19/20233421VS.NET IDE: 184. Visual Studio - Fine Code Coverage에서 동작하지 않는 Fake/Shim 테스트
13327정성태4/19/20233836VS.NET IDE: 183. C# - .NET Core/5+ 환경에서 Fakes를 이용한 단위 테스트 방법
13326정성태4/18/20235261.NET Framework: 2109. C# - 닷넷 응용 프로그램에서 SQLite 사용 (System.Data.SQLite) [1]파일 다운로드1
13325정성태4/18/20234567스크립트: 48. 파이썬 - PostgreSQL의 with 문을 사용한 경우 연결 개체 누수
13324정성태4/17/20234372.NET Framework: 2108. C# - Octave의 "save -binary ..."로 생성한 바이너리 파일 분석파일 다운로드1
13323정성태4/16/20234298개발 환경 구성: 677. Octave에서 Excel read/write를 위한 io 패키지 설치
13322정성태4/15/20235105VS.NET IDE: 182. Visual Studio - 32비트로만 빌드된 ActiveX와 작업해야 한다면?
13321정성태4/14/20233931개발 환경 구성: 676. WSL/Linux Octave - Python 스크립트 연동
13320정성태4/13/20233894개발 환경 구성: 675. Windows Octave 8.1.0 - Python 스크립트 연동
13319정성태4/12/20234346개발 환경 구성: 674. WSL 2 환경에서 GNU Octave 설치
13318정성태4/11/20234189개발 환경 구성: 673. JetBrains IDE에서 "Squash Commits..." 메뉴가 비활성화된 경우
13317정성태4/11/20234263오류 유형: 855. WSL 2 Ubuntu 20.04 - error: cannot communicate with server: Post http://localhost/v2/snaps/...
13316정성태4/10/20233574오류 유형: 854. docker-compose 시 "json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)" 오류 발생
13315정성태4/10/20233788Windows: 245. Win32 - 시간 만료를 갖는 컨텍스트 메뉴와 윈도우 메시지의 영역별 정의파일 다운로드1
13314정성태4/9/20233883개발 환경 구성: 672. DosBox를 이용한 Turbo C, Windows 3.1 설치
13313정성태4/9/20233957개발 환경 구성: 671. Hyper-V VM에 Turbo C 2.0 설치 [2]
13312정성태4/8/20233973Windows: 244. Win32 - 시간 만료를 갖는 MessageBox 대화창 구현 (개선된 버전)파일 다운로드1
13311정성태4/7/20234473C/C++: 163. Visual Studio 2022 - DirectShow 예제 컴파일(WAV Dest)
13310정성태4/6/20234084C/C++: 162. Visual Studio - /NODEFAULTLIB 옵션 설정 후 수동으로 추가해야 할 library
13309정성태4/5/20234243.NET Framework: 2107. .NET 6+ FileStream의 구조 변화
13308정성태4/4/20234149스크립트: 47. 파이썬의 time.time() 실숫값을 GoLang / C#에서 사용하는 방법
13307정성태4/4/20233904.NET Framework: 2106. C# - .NET Core/5+ 환경의 Windows Forms 응용 프로그램에서 HINSTANCE 구하는 방법
1  2  3  4  5  6  7  8  9  10  11  [12]  13  14  15  ...