성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
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# - .NET Core Unix Domain Socket 사용 예제</h1> <p> Unix Domain Socket 방식이 .NET Core 2.1부터 추가되었습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > UnixDomainSocketEndPoint ; <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.unixdomainsocketendpoint?view=netstandard-2.1'>https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.unixdomainsocketendpoint?view=netstandard-2.1</a> </pre> <br /> Unix Domain Socket의 대표적인 특징은, 파일 경로가 EndPoint가 되어 동일한 시스템 안에서 IPC 수단으로 사용할 수 있다는 점입니다. (로컬 파일 시스템의 파일 경로를 지정하는 것이기 때문에 당연히 다른 시스템에서는 접근할 방법이 없습니다.)<br /> <br /> 사용법은 일반 Socket 통신과 동일한데, 단지 EndPoint만 IP 주소가 아니라 파일 경로를 지정하는 식입니다.<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'>static string path = "/tmp/testd.sock";</span> [서버 측] using (var socket = new Socket(<span style='color: blue; font-weight: bold'>AddressFamily.Unix</span>, SocketType.Stream, <span style='color: blue; font-weight: bold'>ProtocolType.IP</span>)) { var unixEp = new <span style='color: blue; font-weight: bold'>UnixDomainSocketEndPoint</span>(path); socket.<span style='color: blue; font-weight: bold'>Bind(unixEp);</span> socket.Listen(5); using (Socket clntSocket = socket.Accept()) { Console.WriteLine("[Server] ClientConencted"); // ... 소켓 통신 ... } } [클라이언트 측] var socket = new Socket(<span style='color: blue; font-weight: bold'>AddressFamily.Unix</span>, SocketType.Stream, <span style='color: blue; font-weight: bold'>ProtocolType.IP</span>); var unixEp = new <span style='color: blue; font-weight: bold'>UnixDomainSocketEndPoint</span>(path); socket.<span style='color: blue; font-weight: bold'>Connect(unixEp);</span> // ... 소켓 통신 ... socket.Close(); </pre> <br /> 별거 없죠? ^^ 이게 끝입니다.<br /> <br /> (<a target='tab' href='https://github.com/stjeong/DotNetSamples/tree/master/NetCore/UnixDomainSocketSample'>이 글의 예제 코드는 github - UnixDomainSocketSample</a>에서 제공합니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 이러한 Unix Domain Socket의 특성상 다른 Socket 통신과 비교해 "포트" 고민은 안 해도 되지만, 파일 경로의 고민은 해야 합니다. 위의 예제에서는 "/tmp" 폴더를 선택했는데 아래의 글에서도 설명했지만,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > .NET Core - System.PlatformNotSupportedException: The named version of this synchronization primitive is not supported on this platform. ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11903'>http://www.sysnet.pe.kr/2/0/11903</a> </pre> <br /> 리눅스는 /tmp 폴더의 내용을 주기적으로 삭제하는 daemon 프로세스인,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Using /tmp/ And /var/tmp/ Safely ; <a target='tab' href='https://systemd.io/TEMPORARY_DIRECTORIES.html'>https://systemd.io/TEMPORARY_DIRECTORIES.html</a> </pre> <br /> systemd-tmpfiles 또는 tmpwatch 등에 의해 /tmp는 보통 10일, /var/tmp는 30일 동안 해당 파일이 change 또는 read된 적이 없다면 정리한다고 하므로 이에 대해 주의를 해야 합니다. 여기서 문제는, Domain Socket 파일은 Socket Read/Write 시 날짜 변경이 이뤄지지 않습니다. 따라서 해당 기간 동안 응용 프로그램이 살아 있는 Daemon 형식의 프로세스라면 이것이 문제가 될 수 있습니다.<br /> <br /> 따라서 이런 부분이 염려가 된다면 다른 폴더를 지정해야 하는데, /var/run 디렉터리, 또는 그 하위에 응용 프로그램만의 디렉터리를 만들어 그 안에 Domain Socket용 파일을 생성하면 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static string _unixSocket = "/var/run/yourappdir/testd.sock"; </pre> <br /> 그런데 여기서 또다시 문제가 있는데, "/var/run" 디렉터리에는 일반 사용자 권한으로 쓰기가 금지되어 있다는 점입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > $ cd /var/run $ echo "test" > test.txt bash: test.txt: Permission denied $ ls /var -l total 0 ...[생략]... lrwxrwxrwx 1 root root 4 Sep 23 2017 run -> /run ...[생략]... $ ls / -l ...[생략]... drwxr-xr-x 1 root root 512 Jun 29 14:12 run ...[생략]... </pre> <br /> 따라서, 설치 프로그램 단계부터 이를 고려해 /var/run 하위에 일반 사용자 권한으로 쓰기가 가능한 디렉터리를 하나 생성하든가, 아니면 아예 tmp는 잊어버리고 해당 프로그램이 설치된 디렉터리 하위를 Domain Socket용 파일 경로로 쓰는 것이 좋습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 또 하나 알아야 할 점이 있다면, 당연히 소켓 자원의 해제에 상관없이 Domain 소켓의 EndPoint로 사용한 파일(이 글에서는 testd.sock)은 그대로 남아 있다는 점입니다. 만약 동일한 파일 명이 남아 다음번 소켓 사용 시 Bind 작업을 하면 이미 주소가 사용 중이라는 예외가 발생합니다.<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 (98): Address already in use at System.Net.Sockets.Socket.UpdateStatusAfterSocketErrorAndThrowException(SocketError error, String callerName) at System.Net.Sockets.Socket.DoBind(EndPoint endPointSnapshot, SocketAddress socketAddress) at System.Net.Sockets.Socket.Bind(EndPoint localEP) at testd.Program.threadFunc() in /home/tusr/testd/Program.cs:line 42 </pre> <br /> 따라서 서버 측 소켓의 경우에는 Bind 전 파일 유무를 체크하는 것이 좋습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > if (File.Exists(path) == true) { File.Delete(path); // unlink } </pre> <br /> <hr style='width: 50%' /><br /> <br /> 그래도 또 하나 더 주의할 점이 있습니다. 파일 생성은 프로세스의 계정 권한을 따라가기 때문에 만약 root 사용자 권한에서 Unix Domain 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.Net.Sockets.SocketException (13): Permission denied at System.Net.Sockets.Socket.UpdateStatusAfterSocketErrorAndThrowException(SocketError error, String callerName) at System.Net.Sockets.Socket.DoBind(EndPoint endPointSnapshot, SocketAddress socketAddress) at System.Net.Sockets.Socket.Bind(EndPoint localEP) at testd.Program.threadFunc() in /home/tusr/testd/Program.cs:line 43 Unhandled Exception: System.Net.Internals.SocketExceptionFactory+ExtendedSocketException: Cannot assign requested address /var/run/testd.sock at System.Net.Sockets.Socket.DoConnect(EndPoint endPointSnapshot, SocketAddress socketAddress) at System.Net.Sockets.Socket.Connect(EndPoint remoteEP) at testd.Program.Main(String[] args) in /home/tusr/testd/Program.cs:line 25 </pre> <br /> 따라서 이런 경우에는 서버 측 소켓에서 Bind 후, 해당 파일의 권한을 일반 사용자 권한도 접근할 수 있도록 조정을 해야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ==== Linux 환경 ==== $ chmod 766 /var/run/myapp/testd.sock // C# Process.Start("chmod", "766 /var/run/myapp/testd.sock"); // srwxrw-rw- ==== Windows 환경 ==== FileSecurity securityRules = new FileSecurity(); var sid = new SecurityIdentifier(WellKnownSidType.WorldSid, null); securityRules.AddAccessRule(new FileSystemAccessRule(sid, FileSystemRights.FullControl, AccessControlType.Allow)); securityRules.AddAccessRule(new FileSystemAccessRule(sid, FileSystemRights.ChangePermissions, AccessControlType.Allow)); File.SetAccessControl("...tested.sock_file_path...", securityRules); </pre> <br /> <hr style='width: 50%' /><br /> <br /> Windows 10부터 Unix Domain Socket이 지원된다는 소식이 있었습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > AF_UNIX comes to Windows ; <a target='tab' href='https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/'>https://devblogs.microsoft.com/commandline/af_unix-comes-to-windows/</a> </pre> <br /> 사용법은 위에서 소개한 것과 다르지 않고 단지 파일 경로만 조정해 주면 됩니다. Linux와는 달리 temp 폴더를 주기적으로 삭제하는 프로그램이 없긴 해도 사용자에 의해 언제든 삭제될 수 있으므로 %TEMP% 폴더를 사용하는 것은 주의를 해야 합니다. (도메인 소켓 파일은 잠겨 있지 않으므로 삭제가 됩니다.)<br /> <br /> <hr style='width: 50%' /><br /> <a name='unix_end_point'></a> <br /> .NET Core 2.1부터, Unix Domain Socket 통신 방식이 제공되지만 2.0 이하에서도 다음의 글에 추가된,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > How to connect to a Unix Domain Socket in .NET Core in C# ; <a target='tab' href='https://stackoverflow.com/questions/40195290/how-to-connect-to-a-unix-domain-socket-in-net-core-in-c-sharp'>https://stackoverflow.com/questions/40195290/how-to-connect-to-a-unix-domain-socket-in-net-core-in-c-sharp</a> </pre> <br /> UnixEndPoint 타입을 추가하면 동일하게 구현할 수 있습니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1401
(왼쪽의 숫자를 입력해야 합니다.)