성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[tree soap] 아차! f는 기억이 나는데, m은 ㅜㅜ 감사합니다!!! ^...
[정성태] 'm'은 decimal 타입의 숫자에 붙는 접미사입니다. ...
[정성태] https://lxr.sourceforge.io/ http...
[정성태] 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...
[정성태] 다시 질문을 정리할 필요가 있을 것 같습니다. 제가 본문에...
글쓰기
제목
이름
암호
전자우편
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# - 컨테이너에서 실행하기 위한 (소켓) 콘솔 프로젝트 구성</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;' > .NET Core 콘솔 응용 프로그램을 배포(publish) 시 docker image 자동 생성 - 두 번째 이야기 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12197'>https://www.sysnet.pe.kr/2/0/12197</a> </pre> <br /> 비주얼 스튜디오의 경우, 콘솔 프로젝트에도 "Add" / "Docker Support..." 메뉴를 이용하면 쉽게 Docker 컨테이너를 위한 개발 환경을 누릴 수 있다고 했습니다.<br /> <br /> 그렇다면 IPv4 소켓을 사용하면 어떨까요? ^^<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; namespace ConsoleApp2; internal class Program { static void Main(string[] args) { Socket socket = new Socket(<span style='color: blue; font-weight: bold'>AddressFamily.InterNetwork</span>, SocketType.Stream, ProtocolType.Tcp); IPEndPoint ep = new IPEndPoint(<span style='color: blue; font-weight: bold'>IPAddress.Loopback</span>, 16000); Console.WriteLine(ep); socket.Bind(ep); socket.Listen(10); new Thread(() => { byte[] buffer = new byte[10]; while (true) { Socket client = socket.Accept(); Console.WriteLine("Client connected"); int recvBytes = client.Receive(buffer); client.Send(new byte[] { 1, 2, 3, 4 }); client.Close(); } }).Start(); Console.WriteLine("Press any key to exit..."); Console.ReadLine(); socket.Close(); } } </pre> <br /> 위의 경우, "Add" / "Docker Support..."로 Docker 지원을 추가 후 F5 키를 눌러 실행하면 단순히 컨테이너 내에서 응용 프로그램을 실행만 시킬 뿐, 가장 중요한 16000번 포트를 노출시켜주지 않아 외부에서 테스트하기가 불편합니다.<br /> <br /> 왜냐하면 비주얼 스튜디오는 docker run을 다음과 같이 실행하기 때문입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > docker run -dt ...[생략: 각종 볼륨 매핑 및 환경 변수 설정]... --name ConsoleApp1 --entrypoint tail consoleapp1:dev -f /dev/null </pre> <br /> 즉, "-p 16000:16000"과 같은 설정이 있어야 하는 건데요, ASP.NET Core 웹 프로젝트와는 달리 Console App의 경우에는 저것을 자동으로 해주지 않습니다. 따라서 개발자가 직접 설정해야 하는데요, 이에 대해서는 이미 <a target='tab' href='https://www.sysnet.pe.kr/2/0/13548#publish_ports'>지난 글에서 2가지 방법을 설명</a>했습니다.<br /> <br /> <ol> <li>랜덤 포트 매핑 - dockerfile에 EXPOSE + launchSettings.json에 publishAllPorts</li> <li>고정 포트 매핑 - csproj에 DockerfileRunArguments를 이용한 포트 명시</li> </ol> <br /> 하지만, 1번 방법은 C# 프로젝트가 "Microsoft.NET.Sdk.Web"인 경우의 빌드 템플릿에서만 제공하는 기능이므로 콘솔 프로젝트인 "Microsoft.NET.Sdk"라면 DockerfileRunArguments 옵션에 "-P" 옵션을 명시하는 식으로 우회해야 합니다.<br /> <br /> 결국, 2번 방법만 가능한데요, 여기서는 테스트를 편하게 하도록 고정 포트를 할당하는 것으로 가정하겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net8.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS> <!-- <a target='tab' href='https://learn.microsoft.com/en-us/visualstudio/containers/container-msbuild-properties#properties-for-dockerfile-projects'>Container Tools build properties</a> --> <span style='color: blue; font-weight: bold'><DockerfileRunArguments>-p 16000:16000</DockerfileRunArguments></span> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5" /> </ItemGroup> </Project> </pre> <br /> 이후 실행하면, 비주얼 스튜디오는 이렇게 -p 옵션을 추가해 docker run을 해줍니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > docker run -dt ...[생략: 각종 볼륨 매핑 및 환경 변수 설정]... --name ConsoleApp1 <span style='color: blue; font-weight: bold'>-p 16000:16000</span> --entrypoint tail consoleapp1:dev -f /dev/null </pre> <br /> <hr style='width: 50%' /><br /> <a name='docker_backend_localhost'></a> <br /> 위의 예제 코드는 IPAddress.Loopback 주소로 바인딩하고 있는데요, 이렇게 되면 컨테이너 외부에서는 접근할 수 없다고 <a target='tab' href='https://www.sysnet.pe.kr/2/0/13548'>이전 글에서도 언급</a>했습니다. 그러면서 아래와 같은 내용도 언급했는데요,<br /> <br /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> 그나저나, 위의 출력에서 "Container 외부에서 실행"하는 경우 curl의 출력이 "Empty reply from server"인 것이 좀 이상하지 않나요? ^^ 원래는 "Couldn't connect to server"라고 나와야 하는데요, 분량상 이에 대해서는 다른 글에서 자세하게 더 다뤄보겠습니다.<br /> </div><br /> <br /> 이제 저 현상을 자세하게 살펴보겠습니다. 실습을 위해 서버 측 바인딩을 IPAddress.Any로 바꾸고,<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(<span style='color: blue; font-weight: bold'>IPAddress.Any</span>, 16000); </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; namespace ConsoleApp1; internal class Program { static void Main(string[] args) { Socket client = new Socket(<span style='color: blue; font-weight: bold'>AddressFamily.InterNetwork</span>, SocketType.Stream, ProtocolType.Tcp); IPEndPoint ep = new IPEndPoint(<span style='color: blue; font-weight: bold'>IPAddress.Loopback</span>, 16000); Console.WriteLine(ep); <span style='color: blue; font-weight: bold'>client.Connect(ep);</span> Console.WriteLine("Connected"); byte[] buffer = new byte[1024]; client.Send(new byte[] { 1, 2, 3, 4 }); int recvBytes = client.Receive(buffer); Console.WriteLine("Press any key to exit..."); Console.ReadLine(); client.Close(); } } </pre> <a name='docker_backend'></a> <br /> 자, 이제 서버와 클라이언트 프로그램의 실행을 한 번 해보면, 서버 측 컨테이너 내부의 네트워크 연결이 이렇게 나옵니다.<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'>netstat -an | grep 16000</span> tcp 0 0 0.0.0.0:16000 0.0.0.0:* LISTEN tcp 0 0 172.17.0.2:16000 172.17.0.1:40454 FIN_WAIT2 </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;' > C:\temp> <span style='color: blue; font-weight: bold'>netstat -ano | findstr 16000</span> <span style='color: blue; font-weight: bold'>TCP 0.0.0.0:16000 0.0.0.0:0 LISTENING 9324</span> TCP 127.0.0.1:16000 127.0.0.1:32826 FIN_WAIT_2 9324 <span style='color: blue; font-weight: bold'>TCP 127.0.0.1:32826 127.0.0.1:16000 CLOSE_WAIT 40240</span> TCP [::]:16000 [::]:0 LISTENING 9324 TCP [::1]:16000 [::]:0 LISTENING 26120 </pre> <br /> 위의 출력에서 9324는 com.docker.backend.exe 프로세스입니다. 즉, docker run 시 "-p 16000:16000" 옵션을 준 경우, Docker Desktop for Windows는 해당 포트를 com.docker.backend.exe에서 미리 열어두고 있는 것입니다.<br /> <br /> 따라서 위의 연결을 정리해 보면 대략 이런 네트워크 연결 구조가 나옵니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 클라이언트 <---> 16000 포트 com.docker.backend <---> docker 가상 네트워크 <---> 16000 포트 socket server in docker container </pre> <br /> 그렇다면 만약, 서버 소켓 프로그램을 종료해 두고 컨테이너만 실행해 둔 채로 클라이언트를 실행해 보면 어떨까요? 서버는 없어졌지만, 여전히 com.docker.backend가 살아 있기 때문에 16000 포트로의 연결은 정상적으로 이뤄지지만 곧바로 소켓이 닫혀 버리는 현상이 나옵니다.<br /> <br /> 바로 이것이 "localhost"로 바인딩하고 있는 ASP.NET Core로 웹 브라우저 접속을 했을 때 "Empty reply from server" 응답이 나오는 이유입니다. 즉, 웹 브라우저는 com.docker.backend가 열어 놓고 있는 포트로 TCP 연결은 되었지만, com.docker.backend 측은 컨테이너 내부에 연결의 실질적인 대상이 되는 포트가 열려 있지 않아 곧바로 소켓 연결을 끊어버리기 때문에 나타나는 현상입니다.<br /> <br /> 그리고, 왜? "localost/127.0.0.1"로 바인딩한 ASP.NET Core를 컨테이너 외부에서 접속하지 못하는 이유도 위의 컨테이너 측 연결에서 찾을 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > tcp 0 0 <span style='color: blue; font-weight: bold'>172.17.0.2</span>:16000 172.17.0.1:40454 FIN_WAIT2 </pre> <br /> 보는 바와 같이, 외부의 연결을 컨테이너 내부의 소켓 연결을 중재할 때 "172.17.0.2"로 시도를 하기 때문에 "localhost/127.0.0.1"로 바인딩하고 있는 서버 소켓에 대해서는 동작하지 않았던 것입니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 중요하지 않은 이야기 하나 덧붙이자면.<br /> <br /> Docker Desktop for Windows의 경우, 포트 처리에서 다소 이상한 점이 하나 있습니다. 아래는 위에서 출력한 netstat를 클라이언트 연결이 없을 때를 보여줍니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C:\temp> <span style='color: blue; font-weight: bold'>netstat -ano | findstr 16000</span> <span style='color: blue; font-weight: bold'>TCP 0.0.0.0:16000 0.0.0.0:0 LISTENING 9324</span> TCP [::]:16000 [::]:0 LISTENING 9324 TCP [::1]:16000 [::]:0 LISTENING 26120 </pre> <br /> 윈도우 호스트 측의 연결을 컨테이너 내부로 연결하기 위해 "0.0.0.0", "[::]" 주소에 대해 com.docker.backend 프로세스가 대기하는 것은 알겠는데요, 특이하게도 26120 pid를 가진 프로세스는 유독 IPv6인 [::1] Loopback 주소에 대해 연결을 지정하고 있습니다. 이 프로세스의 이름은 wslrelay.exe인데,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > wslrelay.exe --mode 2 --vm-id {541d40ee-8df6-4f85-b1ca-8088aa0e2e41} --handle 2156 </pre> <br /> 어떤 의도로 이렇게 작성된 것인지는 모르겠습니다. ^^; 어쨌든, 127.0.0.1의 연결 시도는 com.docker.backend 프로세스가, [::1]의 연결 시도는 wslrelay가 중계합니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
3311
(왼쪽의 숫자를 입력해야 합니다.)