using System.Globalization;
using System.Xml;
namespace PacketGenerator
{
class Program
{
// 실시간으로 만들어지는 packet string 밀어넣어 관리
static string genPacket;
static ushort packetId;
static string packetEnums;
static string clientRegister; // server to client
static string serverRegister; // client to server
static void Main(string[] args)
{
string pdlPath = "../../PDL.xml";
XmlReaderSettings settings = new XmlReaderSettings()
{
IgnoreComments = true,
IgnoreWhitespace = true
};
if (args.Length >= 1)
pdlPath = args[0];
using (XmlReader r = XmlReader.Create(pdlPath, settings))
{
r.MoveToContent();
while (r.Read())
{
// Depth = 깊이, XmlNodeType.Element = 처음 요소를 가져옴
// XmlNodeType.EndElement = 마지막 요소를 가져옴
if (r.Depth == 1 && r.NodeType == XmlNodeType.Element)
ParsePacket(r);
//Console.WriteLine(r.Name + " " + r["name"]);
}
string fileText = string.Format(PacketFormat.fileFormat, packetEnums, genPacket);
// 파일을 생성하고 string 내용을 넣음
File.WriteAllText("GenPackets.cs", fileText);
string clientManagerText = string.Format(PacketFormat.managerFormat, clientRegister);
File.WriteAllText("ClientPacketManager.cs", clientManagerText);
string serverManagerText = string.Format(PacketFormat.managerFormat, serverRegister);
File.WriteAllText("ServerPacketManager.cs", serverManagerText);
}
}
// 요소가 packet일 경우 genPacket에 packetFormat에 각각 값을 넣어 채운값을 넣음
public static void ParsePacket(XmlReader r)
{
// end 요소 알 경우 return
if (r.NodeType == XmlNodeType.EndElement)
return;
// 노드 이름이 packet이 아닌 경우 리턴
if (r.Name.ToLower() != "packet")
{
Console.WriteLine("Invalid Packet Node");
return;
}
string packetName = r["name"];
if (string.IsNullOrEmpty(packetName))
{
Console.WriteLine("Packet Without Name");
return;
}
// Tuple로 각 format가져와서 packetFormat을 채움
Tuple<string,string,string> t = ParseMembers(r);
genPacket += string.Format(PacketFormat.packetFormat,
packetName, t.Item1, t.Item2, t.Item3); // format {n}에 대응값 넣음
packetEnums += string.Format(PacketFormat.packetEnumFormat, packetName, ++packetId) + Environment.NewLine + "\t";
if(packetName.StartsWith("S_") || packetName.StartsWith("s_"))
clientRegister += string.Format(PacketFormat.managerRegisterFormat, packetName) + Environment.NewLine;
else
serverRegister += string.Format(PacketFormat.managerRegisterFormat, packetName) + Environment.NewLine;
}
// {1} : 멤버 변수들
// {2} : 멤버 변수들 Read
// {3} : 멤법 변수들 Write
public static Tuple<string, string, string> ParseMembers(XmlReader r)
{
string packetName = r["name"];
string memberCode = "";
string readCode = "";
string writeCode = "";
// depth = 2
int depth = r.Depth + 1;
while (r.Read())
{
if (r.Depth != depth)
break;
string memberName = r["name"];
if (string.IsNullOrEmpty(memberName))
{
Console.WriteLine("Member Without Name");
return null;
}
// 줄바꿈해줘서 보기 편하게
if (string.IsNullOrEmpty(memberCode) == false)
memberCode += Environment.NewLine;
if (string.IsNullOrEmpty(readCode) == false)
readCode += Environment.NewLine;
if (string.IsNullOrEmpty(writeCode) == false)
writeCode += Environment.NewLine;
string memberType = r.Name.ToLower();
switch (memberType)
{
// format유형 따라 처리
case "byte":
case "sbyte":
memberCode += string.Format(PacketFormat.memberFormat, memberType, memberName);
readCode += string.Format(PacketFormat.readByteFormat, memberName, memberType);
writeCode += string.Format(PacketFormat.writeByteFormat, memberName, memberType);
break;
case "bool":
case "short":
case "ushort":
case "int":
case "long":
case "float":
case "double":
memberCode += string.Format(PacketFormat.memberFormat, memberType, memberName);
readCode += string.Format(PacketFormat.readFormat, memberName, ToMemberType(memberType), memberType);
writeCode += string.Format(PacketFormat.writeFormat, memberName, memberType);
break;
case "string":
memberCode += string.Format(PacketFormat.memberFormat, memberType, memberName);
readCode += string.Format(PacketFormat.readStringFormat, memberName);
writeCode += string.Format(PacketFormat.writeStringFormat, memberName);
break;
case "list":
Tuple<string, string, string> t = ParseList(r);
memberCode += t.Item1;
readCode += t.Item2;
writeCode += t.Item3;
break;
default:
break;
}
}
memberCode = memberCode.Replace("\n", "\n\t");
readCode = readCode.Replace("\n", "\n\t\t");
writeCode = writeCode.Replace("\n", "\n\t\t");
return new Tuple<string, string, string>(memberCode, readCode, writeCode);
}
public static Tuple<string, string, string> ParseList(XmlReader r)
{
string listName = r["name"];
if (string.IsNullOrEmpty(listName))
{
Console.WriteLine("List without name");
return null;
}
Tuple<string, string, string> t = ParseMembers(r);
string memberCode = string.Format(PacketFormat.memberListFormat,
FirstCharToUpper(listName),
FirstCharToLower(listName),
t.Item1,
t.Item2,
t.Item3);
string readCode = string.Format(PacketFormat.readListFormat,
FirstCharToUpper(listName),
FirstCharToLower(listName));
string writeCode = string.Format(PacketFormat.writeListFormat,
FirstCharToUpper(listName),
FirstCharToLower(listName));
return new Tuple<string, string, string>(memberCode, readCode, writeCode);
}
public static string ToMemberType(string memberType)
{
switch (memberType)
{
case "bool":
return "ToBoolean";
case "short":
return "ToInt16";
case "ushort":
return "ToUInt16";
case "int":
return "ToInt32";
case "long":
return "ToInt64";
case "float":
return "ToSingle";
case "double":
return "ToDouble";
default:
return "";
}
}
public static string FirstCharToUpper(string input)
{
if (string.IsNullOrEmpty(input))
return "";
return input[0].ToString().ToUpper() + input.Substring(1);
}
public static string FirstCharToLower(string input)
{
if (string.IsNullOrEmpty(input))
return "";
return input[0].ToString().ToLower() + input.Substring(1);
}
}
}
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
Client
ClientPacketManager
using ServerCore;
using System;
using System.Collections.Generic;
class PacketManager
{
#region Singleton
static PacketManager _instance;
public static PacketManager instance
{
get
{
if (_instance == null)
_instance = new PacketManager();
return _instance;
}
}
#endregion
// 프로토콜을 딕셔너리에 저장 / 어떤 프로토콜인지, 실행해야할 내용
Dictionary<ushort, Action<PacketSession, ArraySegment<byte>>> _onRecv = new Dictionary<ushort, Action<PacketSession, ArraySegment<byte>>>();
Dictionary<ushort, Action<PacketSession, IPacket>> _handler = new Dictionary<ushort, Action<PacketSession, IPacket>>();
// 프로토콜 등록
public void Register()
{
_onRecv.Add((ushort)PacketID.S_Test, MakePacket<S_Test>);
_handler.Add((ushort)PacketID.S_Test, PacketHandler.S_TestHandler);
}
// 기본적으로 패킷을 Recv 했을 때 하는 것들
public void OnRecvPacket(PacketSession session, ArraySegment<byte> buffer)
{
ushort count = 0;
ushort size = BitConverter.ToUInt16(buffer.Array, buffer.Offset);
count += 2;
ushort id = BitConverter.ToUInt16(buffer.Array, buffer.Offset + count);
count += 2;
Action<PacketSession, ArraySegment<byte>> action = null;
if (_onRecv.TryGetValue(id, out action))
action.Invoke(session, buffer);
}
// T는 IPacket을 구현한 클래스여야만 함
void MakePacket<T>(PacketSession session, ArraySegment<byte> buffer) where T : IPacket, new()
{
T pkt = new T();
pkt.Read(buffer);
Action<PacketSession, IPacket > action = null;
if (_handler.TryGetValue(pkt.Protocol, out action))
action.Invoke(session, pkt);
}
}
ClientPacketHandler
using ServerCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
// 패킷이 조립이 다 되었으면 무엇을 호출할까를 기술
class PacketHandler
{
// 어떤 세션에서 조립이 되었는가 / 어떤 패킷인가 받아옴
// 패킷 내부 내용을 출력
// S_ : packet(server to client)
public static void S_TestHandler(PacketSession session, IPacket packet)
{
}
}
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
Server
ServerPacketManager
using ServerCore;
using System;
using System.Collections.Generic;
class PacketManager
{
#region Singleton
static PacketManager _instance;
public static PacketManager instance
{
get
{
if (_instance == null)
_instance = new PacketManager();
return _instance;
}
}
#endregion
// 프로토콜을 딕셔너리에 저장 / 어떤 프로토콜인지, 실행해야할 내용
Dictionary<ushort, Action<PacketSession, ArraySegment<byte>>> _onRecv = new Dictionary<ushort, Action<PacketSession, ArraySegment<byte>>>();
Dictionary<ushort, Action<PacketSession, IPacket>> _handler = new Dictionary<ushort, Action<PacketSession, IPacket>>();
// 프로토콜 등록
public void Register()
{
_onRecv.Add((ushort)PacketID.C_PlayerInfoReq, MakePacket<C_PlayerInfoReq>);
_handler.Add((ushort)PacketID.C_PlayerInfoReq, PacketHandler.C_PlayerInfoReqHandler);
}
// 기본적으로 패킷을 Recv 했을 때 하는 것들
public void OnRecvPacket(PacketSession session, ArraySegment<byte> buffer)
{
ushort count = 0;
ushort size = BitConverter.ToUInt16(buffer.Array, buffer.Offset);
count += 2;
ushort id = BitConverter.ToUInt16(buffer.Array, buffer.Offset + count);
count += 2;
Action<PacketSession, ArraySegment<byte>> action = null;
if (_onRecv.TryGetValue(id, out action))
action.Invoke(session, buffer);
}
// T는 IPacket을 구현한 클래스여야만 함
void MakePacket<T>(PacketSession session, ArraySegment<byte> buffer) where T : IPacket, new()
{
T pkt = new T();
pkt.Read(buffer);
Action<PacketSession, IPacket > action = null;
if (_handler.TryGetValue(pkt.Protocol, out action))
action.Invoke(session, pkt);
}
}
ServerPacketHandler
using ServerCore;
using System;
using System.Collections.Generic;
using System.Text;
// 패킷이 조립이 다 되었으면 무엇을 호출할까를 기술
class PacketHandler
{
// 어떤 세션에서 조립이 되었는가 / 어떤 패킷인가 받아옴
// 패킷 내부 내용을 출력
// C_ : packet(client to server)
public static void C_PlayerInfoReqHandler(PacketSession session, IPacket packet)
{
C_PlayerInfoReq q = packet as C_PlayerInfoReq;
Console.WriteLine($"PlayerInfoReq : {q.playerId} {q.name}");
foreach (C_PlayerInfoReq.Skill skill in q.skills)
{
Console.WriteLine($"Skill {skill.id} {skill.level} {skill.duration}");
}
}
}
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
PacketManager 자동화 진행
Client, Server 마다 PacketManager를 자동 생성할 수 있도록 배치파일 조정
문제점
1) 양방향으로 가는 Packet은 거의 없음
: 대부분 Client to Server OR Server to Client
2) 사용하지 않음에도 불구하고 PacketHandler에 함수를 추가해야만 하는 문제
: PlayerInfoReq (플레이어 정보 요청 패킷)의 경우 client가 요청하지 받는 일은 없음, 그러나 자동화 시에 PacketManager Register()함수에 각 패킷을 긁어와서 등록해주기에 어쩔 수 없이 PacketHandler에 함수를 등록해줘야만 함
3) 보안 이슈
: 요청받을 일 없는 패킷을 받는다거나 등...
해결법
Packet을 용도를 구별해 네이밍을 함
S_ : Server 에서 Client 로 보내는 Packet
C_ : Cleint 에서 Server 로 보내는 Packet
위 용도에 따라 조건문을 통해 구별하여 ServerPacketManager , ClientPacketManager에 구별하여 등록
그에 따라 Handler 수정
'C# > 네트워크 관련' 카테고리의 다른 글
채팅 테스트 #2 (1) | 2025.01.05 |
---|---|
채팅 테스트 #1 (1) | 2025.01.01 |
Packet Generator #5 (1) | 2024.12.28 |
Packet Generator #3 (0) | 2024.12.22 |
Packet Generator #1,2 (0) | 2024.12.18 |