Protobuf 참고 사이트 및 공식
https://github.com/protobuf-net/protobuf-net
https://dotnetcoretutorials.com/protobuf-in-c-net-part-1-getting-started/#google_vignette
https://learn.microsoft.com/ko-kr/aspnet/core/grpc/protobuf?view=aspnetcore-7.0
ProtoBuff-net 이란?
protobuf-net는 Protocol Buffers 데이터 직렬화 라이브러리로서, C# 환경에서 Protocol Buffers 형식의 데이터를 직렬화하고 역직렬화하기 위한 라이브러리입니다. 이 라이브러리는 Google의 Protocol Buffers 형식을 기반으로 하며, C#에서 Protocol Buffers 메시지를 정의하고 사용하는 데 도움을 줍니다.
protobuf-net의 주요 특징과 개념은 다음과 같습니다:
- Protocol Buffers 지원: protobuf-net은 Google Protocol Buffers 형식을 지원하며, .proto 파일을 사용하여 데이터 메시지를 정의하고 이를 C# 클래스로 변환할 수 있습니다.
- C# 클래스 매핑: .proto 파일에서 생성된 C# 클래스를 사용하여 데이터를 표현합니다. 이러한 클래스는 ProtoContract 및 ProtoMember 특성을 사용하여 Protocol Buffers 메시지 필드와 C# 클래스의 필드 또는 속성을 매핑합니다.
- 직렬화 및 역직렬화: protobuf-net은 C# 객체를 Protocol Buffers 형식으로 직렬화하고, 직렬화된 데이터를 C# 객체로 역직렬화하는 기능을 제공합니다.
- 효율성: protobuf-net는 효율적인 직렬화 및 역직렬화를 위해 최적화되었습니다. 이는 데이터 크기와 직렬화/역직렬화 속도 측면에서 이점을 제공합니다.
- 데이터 규모 변화 대응: Protocol Buffers 형식을 사용하면 데이터 스키마를 버전 간에 변경하는 데 유연성을 제공합니다. protobuf-net은 다양한 데이터 규모 변화에 대응할 수 있도록 설계되었습니다.
- NuGet 패키지: protobuf-net은 NuGet 패키지로 제공되어 프로젝트에 쉽게 추가하고 사용할 수 있습니다.
protobuf-net를 사용하면 C#에서 Protocol Buffers 데이터를 직렬화 및 역직렬화하는 데 효과적으로 활용할 수 있으며, 특히 네트워크 통신 또는 데이터 저장 및 교환 작업에 유용합니다. Google의 Protocol Buffers 형식을 사용하면 데이터를 효율적으로 관리하고 서로 다른 플랫폼 간에 데이터를 교환할 수 있는 강력한 도구가 됩니다.
ProtoBuff-net 설치
패킷구조 설명
- Header [4Byte]
- Size[2byte]
- PacketID[2byte]
- Body [동적Byte]
패킷 정의
[ ProtoContract ]
ProtoContract를 사용하여 C# 클래스를 Protocol Buffers 메시지로 정의합니다. 각 클래스 필드나 속성을 Protocol Buffers 메시지의 필드로 매핑합니다.
[ProtoMember(1)]
ProtoMember 특성을 사용하여 클래스 내의 필드 또는 속성을 Protocol Buffers 메시지 필드와 연결합니다. ProtoMember에는 각 필드에 대한 번호를 할당합니다.
enum PacketID
{
CChat = 3,
SChat = 4,
}
public interface IMessage
{
}
[ProtoContract]
public class S_Chat : IMessage
{
[ProtoMember(1)]
public int objectId { get; set; }
[ProtoMember(2)]
public string chat { get; set; }
}
[ProtoContract]
public class C_Chat : IMessage
{
[ProtoMember(1)]
public string chat { get; set; }
}
Send
- ProtoBuf.Serializer.Serialize(memoryStream, packet)
- 정의해놓은 Packet을 직렬화한다.
- string msgName = packet.GetType().Name.Replace("_", string.Empty);
- 정의한 Packet Class Name을 리플렉션을 통해가져 와 Enum Type의 Id값으로 변경하기 위해
- ClassName : S_Chat, Enum PacketID : SChat 이기 때문에 _제거를 통해 동일한 이름으로 변경
- 정의한 Packet Class Name을 리플렉션을 통해가져 와 Enum Type의 Id값으로 변경하기 위해
- PacketID msgId = (PacketID)Enum.Parse(typeof(PacketID), msgName);
- PacketID 값이 Header로 들어가는 부분 2byte
- ushort size = (ushort)packetArray.Length;
- Packet 직렬화한 사이즈
- byte[] sendBuffer = new byte[size + 4];
Array.Copy(BitConverter.GetBytes((ushort)(size + 4)), 0, sendBuffer, 0, sizeof(ushort));
Array.Copy(BitConverter.GetBytes((ushort)msgId), 0, sendBuffer, 2, sizeof(ushort));
Array.Copy(packetArray, 0, sendBuffer, 4, size);- 앞에서 정의한 size, id 및 직렬화한 packet을 하나의 byte로 합쳐준다.
public override void OnConnecte(EndPoint endPoint)
{
Console.WriteLine("OnConnecte : " + endPoint.ToString());
S_Chat chat = new S_Chat() { objectId = 1, chat = "서버에 접속함." };
Send(chat);
}
void Send(IMessage packet)
{
MemoryStream memoryStream = new MemoryStream();
ProtoBuf.Serializer.Serialize(memoryStream, packet);
byte[] packetArray = memoryStream.ToArray();
string msgName = packet.GetType().Name.Replace("_", string.Empty);
PacketID msgId = (PacketID)Enum.Parse(typeof(PacketID), msgName);
ushort size = (ushort)packetArray.Length;
byte[] sendBuffer = new byte[size + 4];
Array.Copy(BitConverter.GetBytes((ushort)(size + 4)), 0, sendBuffer, 0, sizeof(ushort));
Array.Copy(BitConverter.GetBytes((ushort)msgId), 0, sendBuffer, 2, sizeof(ushort));
Array.Copy(packetArray, 0, sendBuffer, 4, size);
Send(sendBuffer);
}
Recive
먼저 데이터 size와 id값을 찾아와 id값에 따라 switch문에서 적절하게 패킷처리를 해준다.
- MemoryStream memoryStream = new MemoryStream(new ArraySegment<byte>(buffer.Array, buffer.Offset + 4, buffer.Count - 4).ToArray());
- 클라이언트의 역직렬화를 위한 작업으로 정의한 프로토콜은 헤더가 4이기 때문에 offset을 4 만큼 더해주고 count를 4만큼 빼준다.
- C_Chat chatPacket = Serializer.Deserialize<C_Chat>(memoryStream);
- C_Chat 클래스에 바로 역직렬화를 수행한다.
public override void OnRecvedPacket(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;
MemoryStream memoryStream = new MemoryStream(new ArraySegment<byte>(buffer.Array, buffer.Offset + 4, buffer.Count - 4).ToArray());
PacketID packetId = (PacketID)id;
switch (packetId)
{
case PacketID.CChat:
{
C_Chat chatPacket = Serializer.Deserialize<C_Chat>(memoryStream);
Console.WriteLine(chatPacket.chat);
}
break;
}
}
전체 소스코드
using ProtoBuf;
using ProtoBuf.Serializers;
using System;
using System.Collections;
using System.Diagnostics;
using System.IO;
using System.Linq.Expressions;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using static ProtoBuff_net.PacketSession;
namespace ProtoBuff_net
{
enum PacketID
{
CChat = 3,
SChat = 4,
}
public interface IMessage
{
}
[ProtoContract]
public class S_Chat : IMessage
{
[ProtoMember(1)]
public int objectId { get; set; }
[ProtoMember(2)]
public string chat { get; set; }
}
[ProtoContract]
public class C_Chat : IMessage
{
[ProtoMember(1)]
public string chat { get; set; }
}
public class ClientSession : PacketSession
{
public override void OnConnecte(EndPoint endPoint)
{
Console.WriteLine("OnConnecte : " + endPoint.ToString());
S_Chat chat = new S_Chat() { objectId = 1, chat = "서버에 접속함." };
Send(chat);
}
void Send(IMessage packet)
{
MemoryStream memoryStream = new MemoryStream();
ProtoBuf.Serializer.Serialize(memoryStream, packet);
byte[] packetArray = memoryStream.ToArray();
string msgName = packet.GetType().Name.Replace("_", string.Empty);
PacketID msgId = (PacketID)Enum.Parse(typeof(PacketID), msgName);
ushort size = (ushort)packetArray.Length;
byte[] sendBuffer = new byte[size + 4];
Array.Copy(BitConverter.GetBytes((ushort)(size + 4)), 0, sendBuffer, 0, sizeof(ushort));
Array.Copy(BitConverter.GetBytes((ushort)msgId), 0, sendBuffer, 2, sizeof(ushort));
Array.Copy(packetArray, 0, sendBuffer, 4, size);
Send(sendBuffer);
}
public override void OnDisconnected(EndPoint endPoint)
{
Console.WriteLine("OnDisconnected : " + endPoint.ToString());
}
public override void OnRecvedPacket(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;
MemoryStream memoryStream = new MemoryStream(new ArraySegment<byte>(buffer.Array, buffer.Offset + 4, buffer.Count - 4).ToArray());
PacketID packetId = (PacketID)id;
switch (packetId)
{
case PacketID.CChat:
{
C_Chat chatPacket = Serializer.Deserialize<C_Chat>(memoryStream);
Console.WriteLine(chatPacket.chat);
}
break;
}
}
}
public class Program
{
static Listener _listener = new Listener();
static void Main(string[] args)
{
string name = Dns.GetHostName();
IPHostEntry ipEntry = Dns.GetHostEntry(name);
IPAddress address = ipEntry.AddressList[1];// 1번 인덱스 ipv4
IPEndPoint endPoint = new IPEndPoint(address, 9999);
_listener.Init(endPoint, () => { return new ClientSession(); });
Console.WriteLine("Server On");
while (true)
{
;
}
}
}
public class Listener
{
Socket _listener;
event Func<Session> _sessionFacktory;
public void Init(IPEndPoint endPoint, Func<Session> sessionFacktory)
{
_listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_sessionFacktory = sessionFacktory;
_listener.Bind(endPoint);
_listener.Listen(10);
SocketAsyncEventArgs accepArgs = new SocketAsyncEventArgs();
accepArgs.Completed += AcceptCompleted;
RegisterAccept(accepArgs);
}
void RegisterAccept(SocketAsyncEventArgs args)
{
args.AcceptSocket = null;
try
{
bool pending = _listener.AcceptAsync(args);
if (pending == false)
{
AcceptCompleted(null, args);
}
}
catch (Exception err)
{
Console.WriteLine(err);
}
}
void AcceptCompleted(object sender, SocketAsyncEventArgs args)
{
if (args.SocketError == SocketError.Success)
{
Session session = _sessionFacktory.Invoke();
session.Start(args.AcceptSocket);
session.OnConnecte(args.AcceptSocket.RemoteEndPoint);
}
else
{
Console.WriteLine(args.SocketError);
}
RegisterAccept(args);
}
}
public abstract class PacketSession : Session
{
public static readonly int HeadSize = 2;
public sealed override int OnRecived(ArraySegment<byte> buffer)
{
int processLen = 0;
while (true)
{
if (buffer.Count < HeadSize)
break;
ushort dataSize = BitConverter.ToUInt16(buffer.Array, buffer.Offset);
if (buffer.Count < dataSize)
break;
OnRecvedPacket(new ArraySegment<byte>(buffer.Array, buffer.Offset, dataSize));
processLen += dataSize;
buffer = new ArraySegment<byte>(buffer.Array, buffer.Offset + dataSize, buffer.Count - dataSize);
}
return processLen;
}
public abstract void OnRecvedPacket(ArraySegment<byte> buffer);
}
public abstract class Session
{
Socket _socket;
SocketAsyncEventArgs _recvArgs = new SocketAsyncEventArgs();
SocketAsyncEventArgs _sendArgs = new SocketAsyncEventArgs();
int _counnected = 0;
object _lock = new object();
Queue<byte[]> _sendQueue = new Queue<byte[]>();
List<ArraySegment<byte>> _pandinglist = new List<ArraySegment<byte>>();
public abstract void OnConnecte(EndPoint endPoint);
public abstract int OnRecived(ArraySegment<byte> buff);
public abstract void OnDisconnected(EndPoint endPoint);
public void Start(Socket socket)
{
_socket = socket;
_recvArgs.Completed += RecvArgs_Completed;
_recvArgs.SetBuffer(new byte[1024], 0, 1024);
_sendArgs.Completed += SendArgs_Completed;
RegisterRecv();
}
public void Send(byte[] sendBuff)
{
lock (_lock)
{
_sendQueue.Enqueue(sendBuff);
if (_pandinglist.Count == 0)
RegisterSend();
}
}
void RegisterRecv()
{
if (_counnected == 1)
return;
try
{
bool pending = _socket.ReceiveAsync(_recvArgs);
if (pending == false)
RecvArgs_Completed(null, _recvArgs);
}
catch (Exception err)
{
Console.WriteLine(err);
}
}
void RegisterSend()
{
if (_counnected == 1)
return;
while (_sendQueue.Count > 0)
{
byte[] buff = _sendQueue.Dequeue();
_pandinglist.Add(new ArraySegment<byte>(buff, 0, buff.Length));
}
_sendArgs.BufferList = _pandinglist;
bool panding = _socket.SendAsync(_sendArgs);
if (panding == false)
SendArgs_Completed(null, _sendArgs);
}
private void RecvArgs_Completed(object sender, SocketAsyncEventArgs args)
{
if (_recvArgs.SocketError == SocketError.Success && _recvArgs.BytesTransferred > 0)
{
try
{
OnRecived(new ArraySegment<byte>(args.Buffer, args.Offset, args.BytesTransferred));
RegisterRecv();
}
catch (Exception err)
{
Console.WriteLine(err);
DisConnect();
}
}
else
{
DisConnect();
}
}
private void SendArgs_Completed(object sender, SocketAsyncEventArgs e)
{
lock (_lock)
{
if (_sendArgs.SocketError == SocketError.Success && _sendArgs.BytesTransferred > 0)
{
try
{
_sendArgs.BufferList = null;
_pandinglist.Clear();
if (_sendQueue.Count > 0)
RegisterSend();
}
catch (Exception err)
{
Console.WriteLine(err);
DisConnect();
}
}
else
{
DisConnect();
}
}
}
public void DisConnect()
{
if (Interlocked.Exchange(ref _counnected, 1) == 1)
return;
OnDisconnected(_socket.RemoteEndPoint);
_socket.Shutdown(SocketShutdown.Both);
_socket.Close();
_sendQueue.Clear();
}
}
}
'VisualStudio > C#' 카테고리의 다른 글
[프로그래머스/C++] 조건에 맞게 수열 변환하기 2 (0) | 2024.03.07 |
---|---|
[C#] 데이터베이스 종류 (0) | 2023.11.08 |
[C#] List<T> LINQ 모음 (0) | 2023.09.21 |
[C#] Enum타입 리플렉션(Reflection)사용하여 값 받아오는 방법 (0) | 2023.02.16 |
[C#] Byte와 문자열간 변환 방법과 인코딩 방식 설명 (0) | 2023.02.09 |