게임서버프로그래밍

C# Network 기초편 1. 주소 체계의 추상화

게임플밍마스터 2026. 2. 4. 13:33

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()는 단순한 문자열 파싱 그 이상입니다.

  1. 입력된 문자열이 IPv4인지 IPv6인지 유효성을 검사합니다.
  2. inet_addr처럼 문자열을 32비트 정수로 바꾸는 동시에, 시스템 환경에 무관하게 네트워크 바이트 오더로 정렬된 객체를 생성합니다.
  3. 유효하지 않은 주소 형식일 경우 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