25.10.09 개발일지 / C# TRex - TCP IP 네트워크 구상

2025. 11. 13. 14:08·LMS 7/개발일지

프로토콜 구상

TRex 게임은 구조적으로 멀티는 불가능한 게임이므로 네트워크 통신으로 구현할 수 있는 것은 클라이언트 게임 종료시의 점수(Socre)를 서버에 저장하고, 이를 클라이언트가 원할 때 전송받아 출력을 하는 정도라고 생각함

따라서 헤더와 바디를 아래처럼 구상(※ 이것이 C#이다 - Chapter21. 네트워크 프로그래밍 - p.790 ~ p.817 참조)하여 프로토콜을 만들어 보자

​

1. 헤더(12, 고정크기)

> MSGID(4) : 메시지 식별 번호

> MSGTYPE(4) : REQ(점수 등록 요청), REP(요청에 대한 회신), SCORE_REQ(점수 결과 요청), SCORE_RES(점수 결과 회신)

> BODYLEN(4) : 메시지 본문 길이

​

2. 바디

> REQ인 경우 : SCORE(4)

> REP인 경우 : MSGID(4), RESPONE(1)

> SCORE_REQ인 경우 : WANT_SCORE(1)

> SCORE_REP인 경우 : MSGID(4), SCORE(4), RESULT(1)


공통 라이브러리 만들기

SCORE_P.sln

└ SCORE_P

└ Message.cs

└ Header.cs

└ Body.cs

└ MessageUtil.cs

​

<Message.cs>

namespace SCORE_P
{
    public class CONSTANTS
    {
        // MSGTYPE
        public const uint REQ = 1;
        public const uint REP = 2;
        public const uint SCORE_REQ = 3;
        public const uint SCORE_RES = 4;
    }

    // 인터페이스 IMessage 정의
    public interface IMessage
    {
        byte[] GetBytes();
        int GetSize();
    }

    // Message 클래스 정의
    // Message 클래스는 Header와 Body를 더한 byte 배열 또는 이것의 크기를 반환함
    public class Message : IMessage
    {
        // Header와 Body 객체 생성
        public Header Header { get; set; }
        // Body는 여러 종류가 있으므로 IMessage 인터페이스로 선언됨
        public IMessage Body { get; set; }

        public byte[] GetBytes()
        {
            byte[] bytes = new byte[GetSize()];

            // Header와 Body에도 IMessage 인터페이스가 구현되어 있음
            // 따라서 각 목적에 맞는 구현에 따라 GetBytes() 호출을 할 수 있음
            Header.GetBytes().CopyTo(bytes, 0);
            Body.GetBytes().CopyTo(bytes, Header.GetSize());

            // 결과적으로 Header와 Body의 bytes를 합친 총 bytes를 반환하는 메서드가 되는 것
            return bytes;
        }

        public int GetSize()
        {
            // 위와 같이 각 목적에 맞는 구현에 따라 GetSize() 호출을 할 수 있음
            return Header.GetSize() + Body.GetSize();
        }
    }
}

 

<Header.cs>

using System;

namespace SCORE_P
{
    public class Header : IMessage
    {
        // 헤더를 구성하는 필드
        public uint MSGID { get; set; } // 4byte
        public uint MSGTYPE { get; set; } // 4byte
        public uint BODYLEN { get; set; } // 4byte

        // 기본 생성자
        // 바이트 배열을 받아 필드를 초기화하는 생성자
        public Header() { }
        public Header(byte[] bytes)
        {
            MSGID = BitConverter.ToUInt32(bytes, 0);
            MSGTYPE = BitConverter.ToUInt32(bytes, 4);
            BODYLEN = BitConverter.ToUInt32(bytes, 8);
        }

        // 인터페이스 구현(IMessage)
        // 초기화된 필드를 바이트 배열로 변환하여 반환
        public byte[] GetBytes()
        {
            byte[] bytes = new byte[12];

            byte[] temp = BitConverter.GetBytes(MSGID);
            Array.Copy(temp, 0, bytes, 0, temp.Length);

            temp = BitConverter.GetBytes(MSGTYPE);
            Array.Copy(temp, 0, bytes, 4, temp.Length);

            temp = BitConverter.GetBytes(BODYLEN);
            Array.Copy(temp, 0, bytes, 8, temp.Length);

            return bytes;
        }
        // 초기화된 필드의 총 크기를 반환(고정크기)
        public int GetSize()
        {
            return 12;
        }
    }
}

 

<Body.cs>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SCORE_P
{
    // Body 클래스 정의
    // Body는 MSGTYPE에 따라 MessageUtil.cs에서 분기가 됨
    // 총 4부분(REQ, REP, SCORE_REQ, SCORE_RES)로 나뉨

    // 1. 요청(REQ)
    public class BodyRequest : IMessage
    {
        public int SCORE; // 4byte

        // 기본 생성자
        // 바이트 배열을 받아 필드를 초기화하는 생성자
        public BodyRequest() { }
        public BodyRequest(byte[] bytes)
        {
            SCORE = BitConverter.ToInt32(bytes, 0);
        }

        // 인터페이스 구현(IMessage)
        // 초기화된 필드를 바이트 배열로 변환하여 반환
        public byte[] GetBytes()
        {
            byte[] bytes = new byte[GetSize()];

            byte[] temp = BitConverter.GetBytes(SCORE);
            Array.Copy(temp, 0, bytes, 0, temp.Length);

            return bytes;
        }
        // 초기화된 필드의 크기를 반환
        public int GetSize()
        {
            return sizeof(int);
        }
    }

    // 2. 응답(REP)
    public class BodyResponse : IMessage
    {
        public uint MSGID; // 4byte
        public byte RESPONSE; // 1byte

        // 기본 생성자
        // 바이트 배열을 받아 필드를 초기화하는 생성자
        public BodyResponse() { }
        public BodyResponse(byte[] bytes)
        {
            MSGID = BitConverter.ToUInt32(bytes, 0);
            RESPONSE = bytes[4];
        }

        // 인터페이스 구현(IMessage)
        // 초기화된 필드를 바이트 배열로 변환하여 반환
        public byte[] GetBytes()
        {
            byte[] bytes = new byte[GetSize()];

            byte[] temp = BitConverter.GetBytes(MSGID);
            Array.Copy(temp, 0, bytes, 0, temp.Length);

            bytes[temp.Length] = RESPONSE;

            return bytes;
        }
        // 초기화된 필드의 크기를 반환
        public int GetSize()
        {
            return sizeof(uint) + sizeof(byte);
        }
    }

    // 3. 점수 요청(SCORE_REQ)
    public class BodyScoreRequest : IMessage
    {
        public byte WANT_SCORE; // 1byte

        // 기본 생성자
        // 바이트 배열을 받아 필드를 초기화하는 생성자
        public BodyScoreRequest() { }
        public BodyScoreRequest(byte[] bytes)
        {
            WANT_SCORE = bytes[0];
        }

        // 인터페이스 구현(IMessage)
        // 초기화된 필드를 바이트 배열로 변환하여 반환
        public byte[] GetBytes()
        {
            byte[] bytes = new byte[GetSize()];

            bytes[0] = WANT_SCORE;

            return bytes;
        }
        // 초기화된 필드의 크기를 반환
        public int GetSize()
        {
            return sizeof(byte);
        }
    }

    // 4. 점수 결과(SCORE_RES)
    public class BodyScoreResult : IMessage
    {
        public uint MSGID; // 4byte
        public uint SCORE; // 4byte
        public byte RESULT; // 1byte

        // 기본 생성자
        // 바이트 배열을 받아 필드를 초기화하는 생성자
        public BodyScoreResult() { }
        public BodyScoreResult(byte[] bytes)
        {
            MSGID = BitConverter.ToUInt32(bytes, 0);
            SCORE = BitConverter.ToUInt32(bytes, 4);
            RESULT = bytes[8];
        }

        // 인터페이스 구현(IMessage)
        // 초기화된 필드를 바이트 배열로 변환하여 반환
        public byte[] GetBytes()
        {
            byte[] bytes = new byte[GetSize()];

            byte[] temp = BitConverter.GetBytes(MSGID);
            Array.Copy(temp, 0, bytes, 0, temp.Length);

            temp = BitConverter.GetBytes(SCORE);
            Array.Copy(temp, 0, bytes, 4, temp.Length);

            bytes[8] = RESULT;

            return bytes;
        }
        // 초기화된 필드의 크기를 반환
        public int GetSize()
        {
            return sizeof(uint) + sizeof(uint) + sizeof(byte);
        }
    }
}

 

<MessageUtil.cs>

using System;
using System.IO;

namespace SCORE_P
{
    public class MessageUtil
    {
        // 송수신(Send/Read) 메서드 구현

        // Send
        // Stream에 Message를 바이트 배열로 변환하여 전송
        public static void Send(Stream writer, Message msg)
        {
            writer.Write(msg.GetBytes(), 0, msg.GetSize());
        }

        // Read
        // Stream에서 바이트 배열을 읽어 Message로 변환하여 반환
        public static Message Read(Stream reader)
        {
            // Header 전용 헬퍼 필드
            int totalRead = 0; // 누적 읽은 바이트 수
            int sizeToRead = 12; // 읽어야 할 바이트 수 (Header 고정 크기)
            byte[] hBuffer = new byte[sizeToRead]; // Header용 버퍼
            
            // Header
            while (sizeToRead > 0)
            {
                byte[] buffer = new byte[sizeToRead];
                int recv = reader.Read(buffer, 0, sizeToRead);
                if(recv == 0) return null;

                buffer.CopyTo(hBuffer, totalRead);
                totalRead += recv;
                sizeToRead -= recv;
            }
            Header header = new Header(hBuffer); // 바이트 배열 내용을 가진 버퍼로 Header 객체 생성

            // Body 전용 헬퍼 필드
            totalRead = 0; // 누적 읽은 바이트 수 초기화
            sizeToRead = (int)header.BODYLEN; // 읽어야 할 바이트 수 (Header에 정의된 BODYLEN)
            byte[] bBuffer = new byte[header.BODYLEN]; // Body용 버퍼

            // Body
            while (sizeToRead > 0)
            {
                byte[] buffer = new byte[sizeToRead];
                int recv = reader.Read(buffer, 0, sizeToRead);
                if(recv == 0) return null;

                buffer.CopyTo(bBuffer, totalRead);
                totalRead += recv;
                sizeToRead -= recv;
            }

            // Body는 여러 종류가 있으므로 IMessage 인터페이스로 선언됨
            IMessage body = null;
            switch (header.MSGTYPE) // MSGTYPE에 따라 분기하여 Body 객체 생성
            {
                case CONSTANTS.REQ:
                    body = new BodyRequest(bBuffer);
                    break;
                case CONSTANTS.REP:
                    body = new BodyResponse(bBuffer);
                    break;
                case CONSTANTS.SCORE_REQ:
                    body = new BodyScoreRequest(bBuffer);
                    break;
                case CONSTANTS.SCORE_RES:
                    body = new BodyScoreResult(bBuffer);
                    break;
                default:
                    throw new Exception(String.Format("Unknown MSGTYPE : {0}", header.MSGTYPE));
            }

            // Header와 Body를 가진 Message 객체 생성 및 반환
            return new Message() { Header = header, Body = body };
        }
    }
}

REQ의 SCORE -> REQ_SCORE,

REP의 RESPONSE -> REP_SCORE,

SCORE_REP의 SCORE -> RES_SCORE

로 명확하게 표시하는 게 다음부터는 더 좋을 것 같다

​

변수명을 귀찮아서 자꾸 대충 짓는데 다른 곳 한번 둘러보고 오면 이 변수가 뭘하는 역할이지? 하고 있다

처음부터 잘 지어야 한다

'LMS 7 > 개발일지' 카테고리의 다른 글

25.10.14 개발일지 / C# 명코파크 (2일차)  (0) 2025.11.13
25.10.13 개발일지 / C# 명코파크 (1일차)  (0) 2025.11.13
25.10.08 개발일지 / C# T-Rex Endless Runner - 3 / 주요 메서드 정리 및 영상  (0) 2025.11.13
25.10.07 개발일지 / C# T-Rex Endless Runner - 2 / 이벤트, 필드, 메서드 등 멤버 정의  (0) 2025.11.13
25.10.06 개발일지 / C# T-Rex Endless Runner - 1 / 프로젝트 생성 및 디자인 배치  (0) 2025.11.11
'LMS 7/개발일지' 카테고리의 다른 글
  • 25.10.14 개발일지 / C# 명코파크 (2일차)
  • 25.10.13 개발일지 / C# 명코파크 (1일차)
  • 25.10.08 개발일지 / C# T-Rex Endless Runner - 3 / 주요 메서드 정리 및 영상
  • 25.10.07 개발일지 / C# T-Rex Endless Runner - 2 / 이벤트, 필드, 메서드 등 멤버 정의
m_Dev
m_Dev
  • m_Dev
    m_Dev
    m_Dev
  • 전체
    오늘
    어제
    • 분류 전체보기
      • MAIN STUDY
        • 정보보안
        • 빅데이터
        • 정보처리
        • 컴퓨터 구조
        • 기타
      • JOB
        • Study
        • Project
      • LMS 7
        • 개발일지
      • FRAMEWORK
        • Qt
        • MFC
        • Winform
        • WPF
        • MAUI
      • NETWORK
        • Study
        • Assignment
      • PYTHON
        • Set
        • Study
        • Assignment
        • Project
      • C
        • Set
        • Study
        • Assignment
        • Project
      • C++
        • Set
        • Study
        • Assignment
        • Project
      • C#
        • Set
        • Study
        • Assignment
        • Project
      • DATABASE
        • MySQL
        • SQLite
      • IDE
        • VisualStudioCode
        • VisualStudio
        • Pycharm
        • Colab
      • 기타
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
m_Dev
25.10.09 개발일지 / C# TRex - TCP IP 네트워크 구상
상단으로

티스토리툴바