성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
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'>프로세스 간 통신 시 소켓 필요 없이 간단하게 Pipe를 열어 통신하는 방법</h1> <p> Named Pipe가 .NET 3.5부터 <a target='tab' href='https://docs.microsoft.com/en-us/dotnet/api/system.io.pipes.namedpipeserverstream'>NamedPipeServerStream</a> / <a target='tab' href='https://docs.microsoft.com/en-us/dotnet/api/system.io.pipes.namedpipeclientstream'>NamedPipeClientStream</a> 객체를 통해 제공됩니다. 여건이 된다면 이것을 사용하는 것이 가장 좋고 관련해서 예제도 MSDN에 잘 공개되어 있습니다.<br /> <br /> 하지만, 범용 라이브러리 제작사의 경우에는 .NET 2.0을 포함시켜야 하는 상황이 종종 발생하므로 P/Invoke를 이용해 Win32 API를 직접 호출해서 파이프 통신을 구현할 수 있습니다.<br /> <br /> 왜 파이프 통신을 사용하는 걸까요? 사실, 프로세스 간에 간단하게 통신이 필요한 경우 소켓을 이용하자니 포트의 관리가 귀찮습니다. 고정 포트를 둬도 되겠지만 역시나 라이브러리 제작사에서는 포트를 위한 별도의 관리 지점을 만들어야 합니다. 바로 이럴 때! 그냥 Named Pipe를 쓰면 고민거리가 사라져버립니다.<br /> <br /> 찾아보면, CreateFile, CreateNamedPipe, ConnectNamedPipe, DisconnectNamedPipe API를 직접 이용해서 구현한 C# 예제가 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Interprocess Communication using Named Pipes in C# ; <a target='tab' href='http://tech.pro/tutorial/633/interprocess-communication-using-named-pipes-in-csharp'>http://tech.pro/tutorial/633/interprocess-communication-using-named-pipes-in-csharp</a> </pre> <br /> 기반은 위의 소스코드를 가져다 썼고, 그래도 개인적으로 개선해보았습니다.<br /> <br /> 우선, 클라이언트로부터의 연결 대기를 동기에서 비동기로 바꿨습니다. 이를 위해 ConnectNamedPipe에 Win32 Overlapped 구조체를 전달했습니다. (<a target='tab' href='http://www.sysnet.pe.kr/2/0/1750'>Overlapped 사용 방법 예제 참조</a>)<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > NativeOverlapped overlapped = new NativeOverlapped(); using (EventWaitHandle ewh = new EventWaitHandle(false, EventResetMode.ManualReset, ...)) { overlapped.EventHandle = ewh.SafeWaitHandle.DangerousGetHandle(); ConnectNamedPipe(clientPipe._pipeHandle, ref overlapped); int lastError = Marshal.GetLastWin32Error(); //...[생략]... } </pre> <br /> 그런데, 재미있는 것은 지금부터입니다. 비동기로 했으므로 ConnectNamedPipe는 곧바로 제어를 반환하게 되고 이후 lastError == ERROR_IO_PENDING 값이면 대기를 할 수 있는 동작을 하게 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > if (lastError == 997) // #define ERROR_IO_PENDING 997L { int completeCode = 0; <span style='color: blue; font-weight: bold'> WaitHandle[] waitHandles = new WaitHandle[] { ewh, _exitEvent }; completeCode = WaitHandle.WaitAny(waitHandles, Timeout.Infinite, false); </span> if (completeCode == 0) // connect completely { } else if (completeCode == 1) // Server.Dispose { Trace.WriteLine("(Dispose) End of PipeService"); clientPipe.Dispose(); return null; } } else if (lastError != 535) // #define ERROR_PIPE_CONNECTED 535L { Trace.WriteLine("Pipe Connect Error: " + lastError); clientPipe.Dispose(); return null; } else { // ERROR_PIPE_CONNECTED Debug.WriteLine("Connected"); } </pre> <br /> 그런데, 유독 "x64/.NET 2.0" 조합으로 빌드하는 경우 클라이언트가 연결되어 WaitHandle.WaitAny의 대기가 풀리게 되는 내부 코드에서 비정상 종료가 발생합니다. (x86/.NET 2.0에서는 OK!) 이때 이벤트 로그는 다음과 같은 기록이 남습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Log Name: Application Source: Windows Error Reporting Date: 2014-09-20 오후 3:05:39 Event ID: 1001 Task Category: None Level: Information Keywords: Classic User: N/A Computer: TESTPC2 Description: Fault bucket 81722438277, type 5 Event Name: BEX64 Response: Not available Cab Id: 0 Problem signature: P1: PipeServer.exe P2: 1.0.0.0 P3: 541d113f <span style='color: blue; font-weight: bold'>P4: mscorwks.dll</span> P5: 2.0.50727.8009 P6: 53a1205c P7: 00000000006b0bbb P8: c0000409 P9: 0000000000000000 P10: Attached files: c:\...\AppData\Local\Temp\WERB77B.tmp.WERInternalMetadata.xml c:\...\AppData\Local\Temp\WERBBF1.tmp.appcompat.txt These files may be available here: c:\...\AppData\Local\Microsoft\Windows\WER\ReportArchive\AppCrash_PipeServer.exe_6c8b5291e79e5faa45279476aec0c352b46de1b_f845d3a1_04a8bc00 Analysis symbol: Rechecking for solution: 0 Report Id: 23c94c7c-408c-11e4-82e5-b81809256582 Report Status: 1 Hashed bucket: d459af57af1f50a06f32b72cb4a2ea59 </pre> <br /> mscorwks.dll 파일은 native DLL이기 때문에 더 파고 들 수가 없군요. ^^; 여기서 더욱 재미있는 점은, WaitAny가 아닌 단일 이벤트 대기를 하는 경우에는 비정상 종료가 안되었습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // 오류 발생하지 않음. ewh.WaitOne(); // 하지만, WaitAny를 통하면 이벤트가 하나여도 오류 발생 WaitHandle[] waitHandles = <span style='color: blue; font-weight: bold'>new WaitHandle[] { ewh };</span> completeCode = WaitHandle.WaitAny(waitHandles, Timeout.Infinite, false); </pre> <br /> 혹시나 싶어, Win32 P/Invoke로 직접 대기를 했더니 이번에는 예외가 발생하지 않습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [DllImport("kernel32.dll")] static extern int <span style='color: blue; font-weight: bold'>WaitForMultipleObjects</span>(int nCount, IntPtr[] lpHandles, bool bWaitAll, int dwMilliseconds); // ... IntPtr[] waitHandles2 = new IntPtr[] { ewh.Handle, _exitEvent.Handle }; completeCode = <a target='tab' href='https://devblogs.microsoft.com/oldnewthing/20240823-00/?p=110169'>WaitForMultipleObjects</a>(waitHandles.Length, waitHandles, false, -1); </pre> <br /> 일단 이걸로 넘어가겠습니다. ^^ (.NET 2.0 지원만 아니면 그냥 NamedPipeServerStream / NamedPipeClientStream으로 넘어가시는 것이 깔끔합니다.)<br /> <br /> 그런데, 저렇게 어차피 대기할 거면 뭐하러 비동기로 하는지 궁금하신 분들이 계실텐데요. 비동기의 장점은 꼭,,, 다른 작업을 가능하게 하는 것뿐만 아니라 부가적으로 '대기'를 원하는 시간에 언제든지 풀어버릴 수 있는 것도 가능하다는 점입니다.<br /> <br /> 예를 들어, 위의 비동기 작업을 그냥 동기로 처리해 버리면 ConnectNamedPipe에서 해당 스레드는 무한정 대기하게 되고, 나중에 이 작업이 필요없어졌을 때 취할 수 있는 방법이라고는 그 스레드를 강제 종료(Thread.Abort)하는 수 밖에는 없습니다. 즉, 곧바로 이어서 대기하는 비동기 작업의 경우 '우아한 종료' 작업을 가능케 하는 이점이 있습니다.<br /> <br /> 클라이언트 연결 대기를 비동기로 처리했으니, 이제 Read/Write 작업도 비동기로 해보겠습니다. 역시 이번에도 곧바로 대기 상태로 들어갈텐데요. 그래도 이렇게 함으로써 Read/Write 작업에 대한 time-out 설정을 할 수 있어서 좋습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public string ReadString() { return ReadString(Timeout.Infinite); } public string ReadString(int maxWaitMillisecond) { if (CanRead == false || _pipeStream == null) { return ""; } MemoryStream ms = new MemoryStream(); UnicodeEncoding encoder = new UnicodeEncoding(); byte[] buffer = new byte[BUFFER_SIZE]; int bytesRead = 0; try { using (ManualResetEvent completed = new ManualResetEvent(false)) { FileStream fsStream = _pipeStream; _pipeStream.BeginRead(buffer, 0, buffer.Length, (ar) => { try { bytesRead = fsStream.EndRead(ar); } catch { } if (completed.SafeWaitHandle.IsClosed == false) { completed.Set(); } }, null); int result = <span style='color: blue; font-weight: bold'>WaitHandle.WaitAny</span>(new WaitHandle[] { completed, _exitEvent }, <span style='color: blue; font-weight: bold'>maxWaitMillisecond</span>, false); try { if (result == 0) // read completely { } else if (result == 1) // by exit event { } else // timed-out { } } catch { } } } catch { } if (bytesRead == 0) { return ""; } return encoder.GetString(buffer, 0, bytesRead); } public void WriteString(string message) { WriteString(message, Timeout.Infinite); } public void WriteString(string message, int maxWaitMillisecond) { if (CanWrite == false) { return; } UnicodeEncoding encoder = new UnicodeEncoding(); byte[] sendBuffer = encoder.GetBytes(message); if (sendBuffer.Length > BUFFER_SIZE) { throw new ArgumentException("too large message"); } try { using (ManualResetEvent completed = new ManualResetEvent(false)) { FileStream fsStream = _pipeStream; _pipeStream.BeginWrite(sendBuffer, 0, sendBuffer.Length, (ar) => { try { fsStream.EndWrite(ar); } catch { } if (completed.SafeWaitHandle.IsClosed == false) { completed.Set(); } }, null); <span style='color: blue; font-weight: bold'>WaitHandle.WaitAny</span>(new WaitHandle[] { completed, _exitEvent }, <span style='color: blue; font-weight: bold'>maxWaitMillisecond</span>, false); } } catch { } } </pre> <br /> 뭐... 이 정도면 대충 쓸만한 Pipe 통신이 되겠습니다. ^^<br /> <br /> (<a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=884&boardid=331301885'>첨부 파일은 위의 코드를 이용해 간단한 파이프 서버/클라이언트 통신을 포함</a>합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
6866
(왼쪽의 숫자를 입력해야 합니다.)