성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] VT sequences to "CONOUT$" vs. STD_O...
[정성태] NetCoreDbg is a managed code debugg...
[정성태] Evaluating tail call elimination in...
[정성태] What’s new in System.Text.Json in ....
[정성태] What's new in .NET 9: Cryptography ...
[정성태] 아... 제시해 주신 "https://akrzemi1.wordp...
[정성태] 다시 질문을 정리할 필요가 있을 것 같습니다. 제가 본문에...
[이승준] 완전히 잘못 짚었습니다. 댓글 지우고 싶네요. 검색을 해보...
[정성태] 우선 답글 감사합니다. ^^ 그런데, 사실 저 예제는 (g...
[이승준] 수정이 안되어서... byteArray는 BYTE* 타입입니다...
글쓰기
제목
이름
암호
전자우편
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# - 비동기 소켓 사용 시 메모리 해제가 finalizer 단계에서 발생하는 사례</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;' > Socket 관련 Leak (OverlappedAsyncResult, OverlappedData) 관련 문의 ; <a target='tab' href='https://www.sysnet.pe.kr/3/1/5826'>https://www.sysnet.pe.kr/3/1/5826</a> </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; using System.Net.Sockets; internal class Program { static void Main(string[] args) { Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); sock.Bind(new IPEndPoint(IPAddress.Any, 65300)); sock.Listen(5); while (true) { Socket client = sock.Accept(); Task.Run(() => { Thread.Sleep(16); client.Close(); }); } } } </pre> <br /> 보는 바와 같이 하는 일 없이 그냥 16ms 이후에 연결을 바로 종료해버립니다. 그리고 클라이언트는 이렇게 만들어 볼 텐데요,<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; using System.Net; using System.Net.Sockets; using System.Threading; internal class Program { static void Main(string[] args) { string ipAddress = "127.0.0.1"; int port = 65300; while (true) { Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket.Connect(new IPEndPoint(IPAddress.Parse(ipAddress), port)); byte[] buffer = new byte[4096]; <span style='color: blue; font-weight: bold'>socket.BeginReceive</span>(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), socket); Thread.Sleep(16); } } private static void ReceiveCallback(IAsyncResult ar) { Socket socket = (Socket)ar.AsyncState; <span style='color: blue; font-weight: bold'>socket.EndReceive</span>(ar); } } </pre> <br /> 역시 하는 일은, 접속한 다음 서버 측이 데이터를 보내주기를 기다리는 것이 전부입니다. 그리고 예상할 수 있듯이, 위와 같이 하면 당분간 메모리 leak과 같은 현상이 발생합니다. 비록 BeginReceive/EndReceive 호출 쌍은 맞췄지만, 서버가 연결을 끊으면서 EndReceive가 0을 반환한 후 클라이언트 측이 Socket.Close를 하지 않아 소켓 자원이 해제되지 않으면서 비동기를 위한 버퍼가 살아 있게 된 것입니다.<br /> <br /> 실제로 이때 해당 프로세스의 메모리 덤프를 떠서 windbg로 살펴보면 <br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>!dumpheap</span> ...[생략]... 00007ffad3b74d78 1504 96256 System.AsyncCallback 00007ffa89ff98a0 1504 96256 System.Net.Sockets.Socket+CacheSet 00007ffa89ff9dc8 565 103960 System.Net.Sockets.OverlappedAsyncResult 00007ffa89ff7d60 2068 115808 System.Net.IPAddress 00007ffad3b369b8 1460 163520 System.Threading.OverlappedData 00007ffad3b5ea78 2069 182072 System.Threading.ExecutionContext 00007ffad3b606f8 3673 208168 System.String 00007ffa89ff3010 1504 216576 System.Net.Sockets.Socket 000001bdabfec320 4553 1293860 Free 00007ffad3b657a0 3013 6281664 System.Byte[] </pre> <br /> 보는 바와 같이 OverlappedAsyncResult, OverlappedData 등의 개체와 함께 System.Byte[]가 꽤나 많이 쌓인 것을 볼 수 있습니다. 그럼 System.Byte[]의 경우 일부 데이터를 조사해 볼까요?<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> !gcroot 0000024441e09aa8 Finalizer Queue: 0000024441e09870 <span style='color: blue; font-weight: bold'>-> 0000024441e09870 System.Net.Sockets.Socket</span> -> 0000024441e09a80 System.Net.SocketAddress -> 0000024441e09aa8 System.Byte[] 0:000> !gcroot 0000018551a214b0 HandleTable: <span style='color: blue; font-weight: bold'>000001854fa11e00 (async pinned handle)</span> -> 0000018551685d08 System.Threading.OverlappedData -> 0000018551a214b0 System.Byte[] </pre> <br /> Socket 개체 또는 pinned handle에 의해 잡혀 있어 GC가 되지 못하고 있습니다. 이와 마찬가지로, OverlappedCache 인스턴스 역시 비슷한 양상을 보입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> !gcroot 0000018551927108 HandleTable: 000001854fa12348 (async pinned handle) -> 0000018551505378 System.Threading.OverlappedData -> 0000018551926ff8 System.Net.Sockets.OverlappedAsyncResult -> 0000018551927108 System.Net.Sockets.OverlappedCache </pre> <br /> 하지만, 이것이 직접적으로 메모리 누수까지는 이어지지 않습니다. 왜냐하면 Socket은 finalizer를 구현하고 있기 때문에 결국 GC가 반복되면서 비동기 수행 후 닫지 않았던 소켓은 자원이 해제되므로 그와 연관된 비관리 자원까지 모두 해제되기 때문입니다.<br /> <br /> 실제로, 위의 프로그램을 실행해 놓고 보면 메모리가 일정 수준 증가하다가 주춤하면서 유지되는 경향을 보입니다. 제 경우에는 위의 프로그램에서 대략 180,632K 정도 증가한 후에는 그 수준에서 약간의 변동폭을 보이기만 했습니다.<br /> <br /> 그나저나... finalizer 때문에 ^^; 완벽한(?) 메모리 누수가 나도록 만들 수가 없습니다.<br /> <br /> 그래도 쓰지도 않는 소켓이 열려 있는 것은 좋지 않으므로 꼭 Close 시키는 것이 좋습니다. "<a target='tab' href='https://www.sysnet.pe.kr/3/1/5826'>Socket 관련 Leak (OverlappedAsyncResult, OverlappedData) 관련 문의</a>" 글의 소스 코드를 보면 Socket.Close가 되지 않을 코드 수행 경로가 있기 때문에 저 현상이 발생할 수도 있습니다.<br /> <br /> <hr style='width: 50%' /><br /> <a name='timeout_apm'></a> <br /> 그 외, "<a target='tab' href='https://www.sysnet.pe.kr/3/1/5826'>Socket 관련 Leak (OverlappedAsyncResult, OverlappedData) 관련 문의</a>" 질문에 첨부한 소스 코드를 보고 언급할 만한 것이 있다면 ReceiveTimeOut의 설정입니다.<br /> <br /> 사실 Begin/End... 비동기에서 ReceiveTimeOut은 아무런 역할도 하지 않습니다. 예전에도 이와 비슷한 질문을 하신 분이 있는데,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 소켓 비동기 ReceiveTimeOut 구현 ; <a target='tab' href='https://www.sysnet.pe.kr/3/0/5282'>https://www.sysnet.pe.kr/3/0/5282</a> </pre> <br /> 비동기의 경우 별도로 (Timer 등을 활용해) timeout 설정을 해야 합니다.<br /> <br /> 마지막으로, Socket.Connecetd 속성으로 IsClientConnected 여부를 판정하고 있는데, 이것도 올바른 방법이 아닙니다. 예전에 이와 관련해 쓴 글에서 소개했으니 더 이상의 설명은 생략합니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > TCP 소켓 연결의 해제를 알 수 있는 방법 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/1825'>https://www.sysnet.pe.kr/2/0/1825</a> </pre> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
3172
(왼쪽의 숫자를 입력해야 합니다.)