성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] How can I tell whether two programs...
[정성태] The case of the fail-fast crashes c...
[정성태] Creating Docker multi-arch images f...
[정성태] BinaryFormatter removed from .NET 9...
[정성태] Extending the Windows Shell Progres...
[우광현] 와..... 범위를 잡았으니 클라이언트가 해당 범위를 확인해본다...
[정성태] 딱히, 그것 이상으로 더 설명할 내용이 없습니다. 동적 포...
[정성태] If Windows 3.11 required a 32-bit p...
[정성태] What is a hard error, and what make...
[괴물신인] 질문작성자인데 이 글을 이제봤네요 ㄷㄷ 이 글처럼 타입별로 인...
글쓰기
제목
이름
암호
전자우편
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# - Windows 10/2019부터 추가된 SIO_TCP_INFO</h1> <p> 우연히 다음의 pdf 자료를 읽게 되었는데,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > TCP improvements in the Windows network stack ; <a target='tab' href='https://datatracker.ietf.org/meeting/98/materials/slides-98-tcpm-tcp-improvements-in-windows-01'>https://datatracker.ietf.org/meeting/98/materials/slides-98-tcpm-tcp-improvements-in-windows-01</a> </pre> <br /> Windows 10 Creators 업데이트(빌드 15014+)부터 SIO_TCP_INFO 옵션이 I/O Control Code에 추가되었다고 합니다. ^^ 리눅스의 TCP_INFO API를 따온 거라고 하는데 소켓 단위로 상태 조회를 할 수 있는 기능입니다.<br /> <br /> C#으로 간단하게 테스트 코드를 만들어 보면 대충 이렇습니다.<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.IO; using System.Net.Sockets; using System.Runtime.InteropServices; using System.Text; namespace ConsoleApp1 { class Program { static void Main(string[] args) { string ipAddr = "www.naver.com"; int port = 80; using (Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) { try { Console.WriteLine($"{DateTime.Now} Connecting..."); client.Connect(ipAddr, port); Console.WriteLine($"{DateTime.Now} Connected."); string request = @"GET / HTTP/1.0 HOST: www.naver.com "; byte[] buffer = Encoding.UTF8.GetBytes(request); Console.WriteLine($"Sent: {buffer.Length}"); client.Send(buffer); MemoryStream ms = new MemoryStream(); while (true) { byte[] rcvBuffer = new byte[4096]; int nRecv = client.Receive(rcvBuffer); if (nRecv == 0) { break; } ms.Write(rcvBuffer, 0, nRecv); } Console.WriteLine($"Receive: {ms.Length}"); <span style='color: blue; font-weight: bold'>DisplaySocketInfo(client);</span> client.Close(); } catch (Exception e) { Console.WriteLine(e.ToString()); Console.WriteLine($"{DateTime.Now} Failed."); } } } private static void DisplaySocketInfo(Socket client) { TCP_INFO_v0 info = new TCP_INFO_v0(); const int SIO_TCP_INFO = unchecked((int)0xd8000027); byte[] version = new byte[4]; byte[] tcpInfo = new byte[Marshal.SizeOf(info)]; try { if (<span style='color: blue; font-weight: bold'>client.IOControl(SIO_TCP_INFO, version, tcpInfo)</span> == tcpInfo.Length) { unsafe { fixed (byte* ptr = tcpInfo) { IntPtr ptrBuf = new IntPtr(ptr); info = (TCP_INFO_v0)Marshal.PtrToStructure(ptrBuf, typeof(TCP_INFO_v0)); } } } } catch (Exception e) { Console.WriteLine(e.ToString()); return; } <span style='color: blue; font-weight: bold'>Console.WriteLine($"State: {info.State}"); // 소켓 상태 출력 Console.WriteLine($"Connection: {info.ConnectionTimeMs}ms"); // 연결 시 걸린 시간 출력 Console.WriteLine($"Bytes-In: {info.BytesIn} bytes(s)"); // 소켓에서 Receive한 총 데이터 Console.WriteLine($"Bytes-Out: {info.BytesOut} bytes(s)"); // 소켓에서 Send한 총 데이터</span> } } } public enum TCPSTATE { CLOSED, LISTEN, SYN_SENT, SYN_RCVD, ESTABLISHED, FIN_WAIT_1, FIN_WAIT_2, CLOSE_WAIT, CLOSING, LAST_ACK, TIME_WAIT, MAX }; [StructLayout(LayoutKind.Sequential)] public struct TCP_INFO_v0 { public TCPSTATE State; public uint Mss; public ulong ConnectionTimeMs; [MarshalAs(UnmanagedType.U1)] public bool TimestampsEnabled; public uint RttUs; public uint MinRttUs; public uint BytesInFlight; public uint Cwnd; public uint SndWnd; public uint RcvWnd; public uint RcvBuf; public ulong BytesOut; public ulong BytesIn; public uint BytesReordered; public uint BytesRetrans; public uint FastRetrans; public uint DupAcksIn; public uint TimeoutEpisodes; public byte SynRetrans; } </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;' > 2020-12-14 오전 1:16:42 Connecting... 2020-12-14 오전 1:16:42 Connected. Sent: 39 Receive: 334 <span style='color: blue; font-weight: bold'>State: CLOSE_WAIT Connection: 19ms Bytes-In: 334 bytes(s) Bytes-Out: 39 bytes(s)</span> </pre> <br /> <hr style='width: 50%' /><br /> <br /> 참고로, 몇 가지 예외 상황을 살펴보면.<br /> <br /> SIO_TCP_INFO 옵션을 지원하지 않는 환경(예를 들어 Windows Server 2016)에서 Ioctl 명령을 수행하면 (C++에서는 WSAEOPNOTSUPP(10045) 오류 코드 반환) 0x80004005 예외가 발생합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > System.Net.Sockets.SocketException (0x80004005): The attempted operation is not supported for the type of object referenced at System.Net.Sockets.Socket.IOControl(Int32 ioControlCode, Byte[] optionInValue, Byte[] optionOutValue) at ConsoleApp1.Program.Main(String[] args) in C:\ConsoleApp1\ConsoleApp1\Program.cs:line 27 </pre> <br /> 또한, 이 오류는 소켓을 Connect 하기 전에 실행해도 동일하게 발생하는데요, 따라서 만약 connect 여부를 판단할 수 없는 소켓에 대해서도 SIO_TCP_INFO 명령을 실행해야 한다면 예외 처리를 하거나, WSAIoctl 함수를 DllImport로 직접 호출하면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <span style='color: blue; font-weight: bold'>[DllImport("Ws2_32.dll")] unsafe static extern int WSAIoctl(IntPtr s, uint dwIoControlCode, byte* lpvInBuffer, int cbInBuffer, byte* lpvOutBuffer, int cbOutBuffer, ref int lpcbBytesReturned, IntPtr lpOverlapped, IntPtr lpCompletionRoutine);</span> using (Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) { IntPtr nativeSocketHandle = client.Handle; DisplaySocketInfo(nativeSocketHandle); } private unsafe static void DisplaySocketInfo(Socket socket) { TCP_INFO_v0 info; const uint SIO_TCP_INFO = 0xd8000027; int outBufferLen = Marshal.SizeOf(typeof(TCP_INFO_v0)); int returnBufferLen = 0; byte* version = stackalloc byte[4]; byte* tcpInfo = stackalloc byte[outBufferLen]; if (<span style='color: blue; font-weight: bold'>WSAIoctl(socket.Handle, SIO_TCP_INFO, version, 4, tcpInfo, outBufferLen, ref returnBufferLen, IntPtr.Zero, IntPtr.Zero)</span> != 0) { return; } if (returnBufferLen != outBufferLen) { return; } unsafe { IntPtr ptrBuf = new IntPtr(tcpInfo); info = (TCP_INFO_v0)Marshal.PtrToStructure(ptrBuf, typeof(TCP_INFO_v0)); } Console.WriteLine($"State: {info.State}"); Console.WriteLine($"Connection: {info.ConnectionTimeMs}ms"); Console.WriteLine($"Bytes-In: {info.BytesIn} bytes(s)"); Console.WriteLine($"Bytes-Out: {info.BytesOut} bytes(s)"); } </pre> <br /> 재미있는 것은, Socket을 Close한 이후에도 SIO_TCP_INFO 옵션을 사용할 수 없는데 단지 이번에는 Socket.IOControl의 오류가 아닌, 닫힌 Socket 인스턴스를 사용하기 때문에 예외가 발생합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > System.ObjectDisposedException: Cannot access a disposed object. Object name: 'System.Net.Sockets.Socket'. at System.Net.Sockets.Socket.IOControl(Int32 ioControlCode, Byte[] optionInValue, Byte[] optionOutValue) at ConsoleApp1.Program.DisplaySocketInfo(Socket client) in C:\ConsoleApp1\ConsoleApp1\Program.cs:line 116 </pre> <br /> 따라서 Socket의 닫힘 여부를 알 수 없는 상황에서도 Win32 API를 호출하는 것으로 우회할 수 있습니다. (그러니까... 결국, 그냥 Win32 API를 직접 호출하는 것이 더 편리합니다. ^^;)<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1683&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1867
(왼쪽의 숫자를 입력해야 합니다.)