성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 제가 큰 실수를 했군요. ^^; Delegate를 통한 Bein...
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>C# - TCP KeepAlive의 서버 측 구현</h1> <p> 지난 이야기에서는,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# - TCP KeepAlive에 새로 추가된 Retry 옵션 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/13531'>https://www.sysnet.pe.kr/2/0/13531</a> </pre> <br /> 클라이언트로 예제를 사용했는데요, 사실 현업으로 따지자면 서버 측에서의 구현이 더 일반적일 수 있습니다. 왜냐하면, 서버는 다수의 클라이언트와 연결을 맺는 것이므로 쓸데없는 연결이 남게 되면 자칫 자원 고갈의 문제로 이어질 수 있기 때문입니다. 따라서 끊긴 클라이언트를 가능하면 빠르게 인지하는 것이 서버 입장에서는 더 필요할 수밖에 없습니다.<br /> <br /> 일단, (클라이언트와는 달리) 서버는 그나마 자유롭게 고를 수 있으므로 Windows Server 2019 이상으로 가정한다면 TCP 레벨에 추가된 3가지 옵션(TcpKeepAliveTime, TcpKeepAliveInterval, TcpKeepAliveRetryCount)을 걱정 없이 사용할 수 있을 것입니다.<br /> <br /> 적용 방법은 간단한데요, Server 소켓에만 적용해 주면 이후 Accept한 클라이언트 소켓에 서버의 설정이 상속되므로 다음과 같이 간단하게 KeepAlive 설정을 할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > int port = 18500; Socket listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) == false || Environment.OSVersion.Version < new Version(10, 0, 15063)) { throw new NotSupportedException(); } <span style='color: blue; font-weight: bold'>listenSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); listenSocket.SetSocketOption(SocketOptionLevel.Tcp, (SocketOptionName)3, 1); listenSocket.SetSocketOption(SocketOptionLevel.Tcp, (SocketOptionName)17, 3); listenSocket.SetSocketOption(SocketOptionLevel.Tcp, (SocketOptionName)16, 10);</span> </pre> <br /> 이후, 실제로 (VM을 Pause 시킨 순간의) 연결 끊김을 알아내기 위해 Ping 코드를 추가해 다음과 같이 테스트 서버를 완성할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > IPEndPoint ep = new IPEndPoint(IPAddress.Any, port); listenSocket.Bind(ep); listenSocket.Listen(5); while (true) { Socket clientSocket = listenSocket.Accept(); Log($"{clientSocket} connected."); bool pingEnabled = true; Task.Run(() => { bool connected = true; IPEndPoint? ep = clientSocket.RemoteEndPoint as IPEndPoint; if (ep == null) { return; } while (pingEnabled) { Ping ping = new Ping(); PingOptions options = new PingOptions(); options.DontFragment = true; string data = "test"; byte[] buffer = ASCIIEncoding.ASCII.GetBytes(data); int timeout = 300; PingReply reply = ping.Send(ep.Address, timeout, buffer, options); bool replied = reply.Status == IPStatus.Success; if (connected != replied) { connected = replied; Log($"Status changed to {reply.Status}"); } Thread.Sleep(32); } }); Task.Run(() => { Log("SendData"); clientSocket.Send(new byte[4] { 1, 2, 3, 4 }); Log("Wait for receiving"); try { clientSocket.Receive(new byte[4]); Log($"Received"); } catch (Exception e) { Log($"Exception thrown: {e.Message}"); } pingEnabled = false; }); } </pre> <br /> 이에 대응하는 클라이언트 코드는 기본으로만 만들면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System.Net.Sockets; namespace ConsoleApp2; internal class Program { static void Main(string[] args) { string host = "192.168.0.22"; int port = 18500; Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket.Connect(host, port); Log("Connected"); int received = socket.Receive(new byte[4]); Log("Received"); try { socket.Receive(new byte[4]); Log("Received"); } catch (Exception ex) { Log($"Exception thrown: {ex.Message}"); } socket.Close(); } private static void Log(string text) { Console.WriteLine($"[{DateTime.Now:mm ss fff}] {text}"); } } </pre> <br /> 이후, 서버와 클라이언트를 실행하고 나서 클라이언트가 실행된 VM을 Pause 시키면 서버에는 다음과 같은 로그가 남게 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [02 05 573] Status changed to TimedOut [02 36 791] Exception thrown: 연결된 구성원으로부터 응답이 없어 연결하지 못했거나, 호스트로부터 응답이 없어 연결이 끊어졌습니다. </pre> <br /> 약 31초 만에 끊겼으니 KeepAlive에 설정한 대로 동작했습니다. 테스트를 위해 살짝 parameter를 바꿔보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > SocketOptionName.KeepAlive == 1 SocketOptionName.TcpKeepAliveInterval == 1 SocketOptionName.TcpKeepAliveRetryCount == 10 </pre> <br /> 이제 실행 결과는 10여 초 만에 끊기도록 나옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [17 34 240] Status changed to TimedOut [17 44 580] Exception thrown: 연결된 구성원으로부터 응답이 없어 연결하지 못했거나, 호스트로부터 응답이 없어 연결이 끊어졌습니다. </pre> <br /> <hr style='width: 50%' /><br /> <br /> 참고로, Accept 이후 얻게 된 클라이언트 소켓에서 기본값을 재정의하는 것도 가능합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Socket <span style='color: blue; font-weight: bold'>clientSocket</span> = listenSocket.Accept(); clientSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); clientSocket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, 1); clientSocket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, 3); clientSocket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount, 10); </pre> <br /> 현실적으로는, 저렇게 클라이언트마다 재정의하는 것이 필요한 상황은 거의 없을 것입니다.<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=2134&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
5080
(왼쪽의 숫자를 입력해야 합니다.)