성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 그냥 RSS Reader 기능과 약간의 UI 편의성 때문에 사용...
[이종효] 오래된 소프트웨어는 보안 위협이 되기도 합니다. 혹시 어떤 기능...
[정성태] @Keystroke IEEE의 문서를 소개해 주시다니... +_...
[손민수 (Keystroke)] 괜히 듀얼채널 구성할 때 한번에 같은 제품 사라고 하는 것이 아...
[정성태] 전각(Full-width)/반각(Half-width) 기능을 토...
[정성태] Vector에 대한 내용은 없습니다. Vector가 닷넷 BCL...
[orion] 글 읽고 찾아보니 디자인 타임에는 InitializeCompon...
[orion] 연휴 전에 재현 프로젝트 올리자 생각해 놓고 여의치 않아서 못 ...
[정성태] 아래의 글에 정리했으니 참고하세요. C# - Typed D...
[정성태] 간단한 재현 프로젝트라도 있을까요? 저런 식으로 설명만 해...
글쓰기
제목
이름
암호
전자우편
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 7 Preview 5 신규 기능 - System.IO.Stream ReadExactly / ReadAtLeast</h1> <p> System.IO.Stream에 반가운 기능이 추가되었군요. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > System.IO.Stream - ReadExactly and ReadAtLeast ; <a target='tab' href='https://devblogs.microsoft.com/dotnet/announcing-dotnet-7-preview-5/#readexactly-and-readatleast'>https://devblogs.microsoft.com/dotnet/announcing-dotnet-7-preview-5/#readexactly-and-readatleast</a> </pre> <br /> TCP 프로토콜의 경우, message-oriented의 UDP와는 달리 stream-oriented 방식이기 때문에 패킷이 분할돼 오는 것을 감안해야 합니다. 예를 들어 볼까요? ^^<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;' > int size 0~4바이트: contents 길이 byte [] contents: size에 명시한 만큼의 데이터 </pre> <br /> 그럼 보내는 쪽에서는 처음 4바이트에 contents 버퍼의 데이터 크기를 실어 보내고, 이어서 contents를 보내게 될 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // 서버에서 클라이언트로 데이터 송신 string data = "12345"; byte[] contents = Encoding.UTF8.GetBytes(data); byte[] header = BitConverter.GetBytes(contents.Length); socket.Send(header); socket.Send(contents); </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;' > // 클라이언트에서 서버로부터 데이터 수신 byte[] buf = new byte[4]; socket.Receive(buf, 0, 4, SocketFlags.None); int len = BitConverter.ToInt32(buf, 0); buf = new byte[len]; socket.Receive(buf, 0, len, SocketFlags.None); // buf에는 문자열 "12345"가 utf-8 인코딩된 바이트를 수신 </pre> <br /> 대개의 경우 저런 상황이라면 정상적으로 수신이 될 것입니다. 하지만, 서버와 클라이언트를 원격지에 두고 contents의 길이를 늘린다면 이제 상황은 달라집니다.<br /> <br /> 예를 들어 송신 측에서 1_000_000 바이트의 데이터를 보내도 수신 측의 Receive는 그것을 전부 받지 못하고 분할 수신이 될 수 있습니다. 이런 이유로 인해 Receive의 경우는 대개 다음과 같은 식으로 코딩하는 것이 일반적입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public int Read(byte[] buffer, int offset, int size) { int totalRead = 0; int readRemains = size; while (true) { int readLen = 0; try { readLen = _socket.Receive(buffer, offset, readRemains, SocketFlags.None); } catch (SocketException ex) { } if (readLen == 0) { _socket.Close(); break; } totalRead += readLen; if (totalRead == size) { break; } offset += readLen; readRemains -= readLen; } return totalRead; } </pre> <br /> 저렇게 helper 메서드를 하나 만들어 두고 다시 이것을 활용해,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // Read 메서드를 이용한 데이터 수신 byte[] buf = new byte[4]; <span style='color: blue; font-weight: bold'>Read(buf, 0, 4);</span> int len = BitConverter.ToInt32(buf, 0); buf = new byte[len]; <span style='color: blue; font-weight: bold'>Read(buf, 0, len);</span> </pre> <br /> 수신하면 정상적으로 buf 바이트 배열에는 송신자가 약속한 크기의 데이터가 들어가게 됩니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> .NET 7부터 추가된 Stream 타입의 ReadExactly 메서드가 바로 저런 Read의 부차적인 코드를 없애줍니다. ^^ 그래서 다음과 같은 식으로 코드를 바꿔 처리할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // <a target='tab' href='https://www.sysnet.pe.kr/2/0/13080'>Visual Studio 2022 17.2의 경우 실습 방법</a> NetworkStream ns = new NetworkStream(socket); <span style='color: blue; font-weight: bold'>ns.ReadExactly(buf, 0, 4);</span> len = BitConverter.ToInt32(buf, 0); buf = new byte[len]; <span style='color: blue; font-weight: bold'>ns.ReadExactly(buf, 0, len);</span> </pre> <br /> 참 쉽죠? ^^<br /> <br /> 이와 함께 ReadAtLeast는 이름에서 의미하는 것처럼 최소 지정한 바이트만큼을 읽어들이는 것을 보장합니다. 이건 어느 때 유용할까요? 이전에 예를 든 코드를 다시 보면, 무조건 2번의 Read 메서드를 호출해야만 했는데요, 하지만 여러분들의 응용 프로그램이 대개의 경우 한 번의 Receive로 처리할 정도로 작은 데이터가 주를 이룬다고 가정하면 ReadAtLeast 한 번의 호출로 끝나는 코드를 다음과 같이 만들 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > NetworkStream ns = new NetworkStream(socket); int readBytes = ns.ReadAtLeast(buf, 4); // 최소 4바이트를 읽어들이지만, 이미 버퍼에 있는 4바이트 이상의 데이터를 더 읽어서 반환하는 것도 가능 len = BitConverter.ToInt32(buf, 0); if (readBytes < (len + 4)) { ns.ReadExactly(buf, 0, len - (readBytes - 4)); } </pre> <br /> 물론, 비동기 버전도 함께 Stream 레벨에 추가되었습니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > namespace System.IO; public partial class Stream { public void ReadExactly(Span<byte> buffer); public void ReadExactly(byte[] buffer, int offset, int count); public ValueTask ReadExactlyAsync(Memory<byte> buffer, CancellationToken cancellationToken = default); public ValueTask ReadExactlyAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default); public int ReadAtLeast(Span<byte> buffer, int minimumBytes, bool throwOnEndOfStream = true); public ValueTask<int> ReadAtLeastAsync(Memory<byte> buffer, int minimumBytes, bool throwOnEndOfStream = true, CancellationToken cancellationToken = default); } </pre> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1943&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 참고로 이런 helper 메서드는 이미 다른 언어의 라이브러리들도 제공하고 있었습니다. 가령 GoLang의 경우 이와 유사한 ReadFull / ReadAtLeast 함수가 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > func ReadFull(r Reader, buf []byte) (n int, err error) ; <a target='tab' href='https://pkg.go.dev/io#ReadFull'>https://pkg.go.dev/io#ReadFull</a> func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error) ; <a target='tab' href='https://pkg.go.dev/io#ReadAtLeast'>https://pkg.go.dev/io#ReadAtLeast</a> </pre> <br /> 또한 닷넷과 GoLang 모두 ReadExactly(ReadFull) 메서드는 ReadAtLeast에 적절한 인자를 전달하는 방식으로 처리를 대행합니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
3377
(왼쪽의 숫자를 입력해야 합니다.)