IOCP까지 경험한 수강생을 대상으로 진행하는 수업입니다. 이미 커널 오브젝트, 비동기 I/O, Worker Thread, 그리고 바이트 오더링(Byte Ordering)에 대한 깊이 있는 이해가 있다고 생각합니다.
C++에서는 sockaddr_in 구조체를 Zero Memory하고 inet_addr이나 htons를 직접 호출해 비트를 채워 넣었죠. C#의 IPAddress와 IPEndPoint는 결국 윈속(Winsock) 구조체의 추상화 래퍼(Wrapper)입니다.
1강: 주소 체계의 추상화 - IPAddress & IPEndPoint
C++ 개발자에게 네트워크 주소 설정이란 sockaddr_in 구조체에 AF_INET을 넣고 포트를 htons로 변환해 넣는 과정을 의미합니다. C#에서는 이 반복적이고 실수가 잦은 과정을 객체 지향적으로 캡슐화했습니다.
1. 클래스 핵심 상세 설명 (C++ 관점)
- IPAddress (The IP Wrapper): C++의 in_addr 구조체 혹은 unsigned long IP 값에 대응합니다. 단순히 주소 문자열을 저장하는 것이 아니라, 내부적으로는 네트워크 바이트 오더(Big-Endian) 형태의 바이트 배열을 관리합니다.
- IPEndPoint (The sockaddr_in Wrapper): C++의 sockaddr_in 구조체와 1:1 대응합니다. IPAddress와 Port를 멤버로 가지며, 나중에 소켓의 Bind()나 Connect() 함수에 인자로 넘겨질 때 내부적으로 네이티브 sockaddr 구조체로 마샬링되어 커널에 전달됩니다.
2. 클래스 원형 및 주요 멤버
C#
namespace System.Net {
public class IPAddress {
// 문자열(Dotted-decimal)을 분석하여 네트워크 바이트 순서의 주소로 변환
public static IPAddress Parse(string ipString);
// 내 주소를 바이트 배열로 반환 (C++의 in_addr.s_addr 데이터를 가져오는 것과 동일)
public byte[] GetAddressBytes();
// IPv4/IPv6 구분 (AddressFamily.InterNetwork 등)
public AddressFamily AddressFamily { get; }
// 스태틱 속성들
public static readonly IPAddress Any; // 0.0.0.0 (INADDR_ANY)
public static readonly IPAddress Loopback; // 127.0.0.1
}
public class IPEndPoint : EndPoint {
public IPEndPoint(IPAddress address, int port);
public IPAddress Address { get; set; }
public int Port { get; set; }
}
}
3. 왜 Parse와 IPEndPoint를 사용하는가?
A. 엔디안(Endianness) 처리의 자동화
C++에서는 호스트 바이트 순서(Little-endian)를 네트워크 바이트 순서(Big-endian)로 바꾸기 위해 htons, htonl을 명시적으로 호출해야 했습니다.
- C#의 IPEndPoint 생성자는 내부적으로 포트 번호에 대한 엔디안 변환을 자동으로 처리합니다. 개발자는 그냥 int 타입으로 포트를 넘기면 끝입니다.
B. Parse() 함수의 실체
IPAddress.Parse()는 단순한 문자열 파싱 그 이상입니다.
- 입력된 문자열이 IPv4인지 IPv6인지 유효성을 검사합니다.
- inet_addr처럼 문자열을 32비트 정수로 바꾸는 동시에, 시스템 환경에 무관하게 네트워크 바이트 오더로 정렬된 객체를 생성합니다.
- 유효하지 않은 주소 형식일 경우 FormatException을 발생시켜, 런타임 에러를 방지합니다.
C. 객체 지향적 다형성
IPEndPoint는 EndPoint라는 추상 클래스를 상속받습니다. 이는 나중에 로비(TCP)와 게임(UDP/P2P) 소켓 함수들이 주소 타입을 구분하지 않고 유연하게 인자를 받을 수 있게 해줍니다.
4. 실습: 주소 정보 추출 및 바이트 확인
C++ 개발자라면 실제 바이트가 어떻게 들어있는지 눈으로 확인하는 것이 가장 빠릅니다.
C#
using System;
using System.Net;
using UnityEngine;
public class NetworkDeepDive : MonoBehaviour
{
void Start()
{
string targetIP = "192.168.0.1";
int targetPort = 8888;
// 1. Parse를 통한 주소 객체화 (내부적으로 네트워크 바이트 오더로 저장)
IPAddress address = IPAddress.Parse(targetIP);
// 2. IPEndPoint 결합 (sockaddr_in 생성과 동일)
IPEndPoint endPoint = new IPEndPoint(address, targetPort);
// 3. 내부 바이트 배열 확인 (C++의 메모리 덤프와 비교해보세요)
byte[] ipBytes = address.GetAddressBytes();
string hexView = BitConverter.ToString(ipBytes);
Debug.Log($"[IPAddress] {targetIP} -> Hex: {hexView}");
Debug.Log($"[IPEndPoint] {endPoint.ToString()} (Family: {endPoint.AddressFamily})");
// Tip: C#에서도 Any(0.0.0.0)를 사용해 모든 인터페이스에서 Listen할 준비를 합니다.
IPEndPoint listenEP = new IPEndPoint(IPAddress.Any, 7777);
}
}
정리하자면...
C++에서 struct sockaddr_in을 선언하고 비트 단위로 값을 채우던 번거로운 작업이 C#에서는 IPEndPoint라는 객체 하나로 응축되었습니다.
'게임서버프로그래밍' 카테고리의 다른 글
| 수강생 포트폴리오 : MMORPG OLBAID (4) | 2025.09.16 |
|---|