Serialization

Eunho Bae·2022년 5월 5일
0

코드

ServerCore

Session.cs

namespace ServerCore
{
	public abstract class PacketSession : Session
	{
		public static readonly int HeaderSize = 2;

		// size = size 포함한 전체 패킷 크기
		// [size(2)][packetId(2)][ ... ][size(2)][packetId(2)][ ... ]
		public sealed override int OnRecv(ArraySegment<byte> buffer) // 오버라이드 다시 불가
		{
			int processLen = 0;

			while (true)
			{
				// 최소한 헤더는 파싱할 수 있는지 확인
				if (buffer.Count < HeaderSize)
					break;

				// 패킷이 완전체로 도착했는지 확인
				ushort dataSize = BitConverter.ToUInt16(buffer.Array, buffer.Offset); // ushort
				if (buffer.Count < dataSize)
					break;

				// 여기까지 왔으면 패킷 조립 가능. new 사용했다고 힙에다 할당해주는게 아니라 스택 복사
				OnRecvPacket(new ArraySegment<byte>(buffer.Array, buffer.Offset, dataSize));
				
				processLen += dataSize;
				// [size(2)][packetId(2)][ ... ] 다음 부분으로 위치 변경
				buffer = new ArraySegment<byte>(buffer.Array, buffer.Offset + dataSize, buffer.Count - dataSize);
			}

			return processLen;
		}

		public abstract void OnRecvPacket(ArraySegment<byte> buffer);
	}

	public abstract class Session
	{
		Socket _socket;
		int _disconnected = 0;

		RecvBuffer _recvBuffer = new RecvBuffer(1024);

		object _lock = new object();
		Queue<ArraySegment<byte>> _sendQueue = new Queue<ArraySegment<byte>>();
		List<ArraySegment<byte>> _pendingList = new List<ArraySegment<byte>>();
		SocketAsyncEventArgs _sendArgs = new SocketAsyncEventArgs();
		SocketAsyncEventArgs _recvArgs = new SocketAsyncEventArgs();

		public abstract void OnConnected(EndPoint endPoint);
		public abstract int  OnRecv(ArraySegment<byte> buffer); // 얼마만큼 데이터를 처리했는지 리턴
		public abstract void OnSend(int numOfBytes);
		public abstract void OnDisconnected(EndPoint endPoint);

		public void Start(Socket socket)
		{
			_socket = socket;

			_recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvCompleted);
			_sendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnSendCompleted);

			RegisterRecv();
		}

		public void Send(ArraySegment<byte> sendBuff)
		{
			lock (_lock)
			{
				_sendQueue.Enqueue(sendBuff);
				if (_pendingList.Count == 0)
					RegisterSend();
			}
		}

		public void Disconnect()
		{
			if (Interlocked.Exchange(ref _disconnected, 1) == 1)
				return;

			OnDisconnected(_socket.RemoteEndPoint);
			_socket.Shutdown(SocketShutdown.Both);
			_socket.Close();
		}

		#region 네트워크 통신

		void RegisterSend()
		{
			while (_sendQueue.Count > 0)
			{
				ArraySegment<byte> buff = _sendQueue.Dequeue();
				_pendingList.Add(buff);
			}
			_sendArgs.BufferList = _pendingList;

			bool pending = _socket.SendAsync(_sendArgs);
			if (pending == false)
				OnSendCompleted(null, _sendArgs);
		}

		void OnSendCompleted(object sender, SocketAsyncEventArgs args)
		{
			lock (_lock)
			{
				if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
				{
					try
					{
						_sendArgs.BufferList = null;
						_pendingList.Clear();

						OnSend(_sendArgs.BytesTransferred);

						if (_sendQueue.Count > 0)
							RegisterSend();
					}
					catch (Exception e)
					{
						Console.WriteLine($"OnSendCompleted Failed {e}");
					}
				}
				else
				{
					Disconnect();
				}
			}
		}

		void RegisterRecv()
		{
			_recvBuffer.Clean(); // 커서가 너무 뒤로 가있는 상태 방지
			// 유효한 범위 설정. 다음으로 버퍼를 받을 공간 Set.
			ArraySegment<byte> segment = _recvBuffer.WriteSegment;
			_recvArgs.SetBuffer(segment.Array, segment.Offset, segment.Count); // count=freesize, 이만큼 받을 수 있다.

			bool pending = _socket.ReceiveAsync(_recvArgs);
			if (pending == false)
				OnRecvCompleted(null, _recvArgs);
		}

		void OnRecvCompleted(object sender, SocketAsyncEventArgs args)
		{
			if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
			{
				try
				{
					// Write 커서 이동
					if (_recvBuffer.OnWrite(args.BytesTransferred) == false)
					{
						Disconnect();
						return;
					}

					// 컨텐츠 쪽으로 데이터를 넘겨주고 얼마나 처리했는지 받는다
					int processLen = OnRecv(_recvBuffer.ReadSegment);
					if (processLen < 0 || _recvBuffer.DataSize < processLen)
					{
						Disconnect();
						return;
					}

					// Read 커서 이동
					if (_recvBuffer.OnRead(processLen) == false)
					{
						Disconnect();
						return;
					}

					RegisterRecv();
				}
				catch (Exception e)
				{
					Console.WriteLine($"OnRecvCompleted Failed {e}");
				}
			}
			else
			{
				Disconnect();
			}
		}

		#endregion
	}
}

Server

ClientSession.cs

namespace Server
{
	public abstract class Packet
	{
		public ushort size;
		public ushort packetId;

		public abstract ArraySegment<byte> Write();
		public abstract void Read(ArraySegment<byte> s);
	}

	class PlayerInfoReq : Packet
	{
		public long playerId;
		public string name;

		public struct SkillInfo
		{
			public int id;
			public short level;
			public float duration;

			public bool Write(Span<byte> s, ref ushort count)
			{
				bool success = true;
				success &= BitConverter.TryWriteBytes(s.Slice(count, s.Length - count), this.id);
				count += sizeof(int);
				success &= BitConverter.TryWriteBytes(s.Slice(count, s.Length - count), this.level);
				count += sizeof(ushort);
				success &= BitConverter.TryWriteBytes(s.Slice(count, s.Length - count), this.duration);
				count += sizeof(float);
				return success;
			}

			public void Read(ReadOnlySpan<byte> s, ref ushort count)
			{
				id = BitConverter.ToInt32(s.Slice(count, s.Length - count));
				count += sizeof(int);
				level = BitConverter.ToInt16(s.Slice(count, s.Length - count));
				count += sizeof(short);
				duration = BitConverter.ToSingle(s.Slice(count, s.Length - count));
				count += sizeof(float);
			}
		}

		public List<SkillInfo> skills = new List<SkillInfo>();

		public PlayerInfoReq()
		{
			this.playerId = (ushort)PacketID.PlayerInfoReq;
		}

		public override void Read(ArraySegment<byte> segment)
		{
			ushort pos = 0;

			ReadOnlySpan<byte> s = new ReadOnlySpan<byte>(segment.Array, segment.Offset, segment.Count);

			pos += sizeof(ushort);
			pos += sizeof(ushort);

			this.playerId = BitConverter.ToInt64(s.Slice(pos, s.Length - pos));
			pos += sizeof(long);

			ushort nameLen = BitConverter.ToUInt16(s.Slice(pos, s.Length - pos));
			pos += sizeof(ushort);
			this.name = Encoding.Unicode.GetString(s.Slice(pos, nameLen));
			pos += nameLen;

			// skill list
			skills.Clear();
			ushort skillLen = BitConverter.ToUInt16(s.Slice(pos, s.Length - pos));
			pos += sizeof(ushort);

			for (int i = 0; i < skillLen; i++)
			{
				SkillInfo skill = new SkillInfo();
				skill.Read(s, ref pos);
				skills.Add(skill);
			}
		}

		public override ArraySegment<byte> Write()
		{
			ArraySegment<byte> segment = SendBufferHelper.Open(4096); // SendBuffer의 _buffer

			ushort size = 0;
			bool success = true;

			// s는 확인용
			Span<byte> s = new Span<byte>(segment.Array, segment.Offset, segment.Count);

			size += sizeof(ushort); // 2 : 패킷 size 사이즈
			success &= BitConverter.TryWriteBytes(s.Slice(size, s.Length - size), this.packetId); // Slice가 Span<byte> 만들어서 반환하면 packetId를 그 범위에 바이트로 변환해서 씀
			size += sizeof(ushort); // packetId 사이즈
			success &= BitConverter.TryWriteBytes(s.Slice(size, s.Length - size), this.playerId);
			size += sizeof(long); // 8 : playerId 사이즈

			// + sizeof(ushort)는 nameLen 넣을 공간 확보
			// 데이터 복사 -> nameLen 추출
			ushort nameLen = (ushort)Encoding.Unicode.GetBytes(this.name, 0, this.name.Length, segment.Array, segment.Offset + size + sizeof(ushort));
			success &= BitConverter.TryWriteBytes(s.Slice(size, s.Length - size), nameLen);
			size += sizeof(ushort);
			size += nameLen;

			/*
			ushort nameLen = (ushort)Encoding.Unicode.GetByteCount(this.name);
			success &= BitConverter.TryWriteBytes(s.Slice(size, s.Length - size), nameLen);
			size += sizeof(ushort);
			Array.Copy(Encoding.Unicode.GetBytes(this.name), 0, segment.Array, size, nameLen);
			size += nameLen;
			*/

			// skill list
			success &= BitConverter.TryWriteBytes(s.Slice(size, s.Length - size), (ushort)skills.Count);
			size += sizeof(ushort);

			foreach (SkillInfo skill in skills)
				success &= skill.Write(s, ref size);

			success &= BitConverter.TryWriteBytes(s, size);


			if (success == false)
				return null; // 반환해서 밖에서 null체크

			return SendBufferHelper.Close(size); // 최종 패킷 크기
		}
	}

	public enum PacketID
	{
		PlayerInfoReq = 1,
		PlayerInfoOk = 2,
	}

	class ClientSession : PacketSession
	{
		public override void OnConnected(EndPoint endPoint)
		{
			Console.WriteLine($"OnConnected : {endPoint}");
			Thread.Sleep(5000);
			Disconnect();
		}

		public override void OnRecvPacket(ArraySegment<byte> buffer)
		{
			int pos = 0;

			ushort size = BitConverter.ToUInt16(buffer.Array, buffer.Offset); 
			pos += 2;
			ushort id = BitConverter.ToUInt16(buffer.Array, buffer.Offset + pos);
			pos += 2;

			switch ((PacketID)id)
			{
				case PacketID.PlayerInfoReq: // required
					{
						PlayerInfoReq p = new PlayerInfoReq();
						p.Read(buffer); // buffer에 있는 값을 역직렬화해서 p에 넣어줌
                        Console.WriteLine($"PlayerInfoReq : {p.playerId} {p.name}");

						foreach(PlayerInfoReq.SkillInfo skill in p.skills)
                            Console.WriteLine($"Skill({skill.id})({skill.level})({skill.duration})");
					}
					break;
				case PacketID.PlayerInfoOk:
					{
						int hp = BitConverter.ToInt32(buffer.Array, buffer.Offset + pos);
						pos += 4;
						int attack = BitConverter.ToInt32(buffer.Array, buffer.Offset + pos);
						pos += 4;
					}
					//Handle_PlayerInfoOk();
					break;
				default:
					break;
			}

			Console.WriteLine($"RecvPacketId: {id}, Size {size}");
		}

		// TEMP
		public void Handle_PlayerInfoOk(ArraySegment<byte> buffer)
		{

		}

		public override void OnDisconnected(EndPoint endPoint)
		{
			Console.WriteLine($"OnDisconnected : {endPoint}");
		}

		public override void OnSend(int numOfBytes)
		{
			Console.WriteLine($"Transferred bytes: {numOfBytes}");
		}
	}
}

DummyClient

ServerSession.cs

namespace DummyClient
{
	public abstract class Packet
	{
		public ushort size;
		public ushort packetId;

		public abstract ArraySegment<byte> Write();
		public abstract void Read(ArraySegment<byte> s);
	}

	class PlayerInfoReq : Packet
	{
		public long playerId;
		public string name;

		public struct SkillInfo
        {
			public int id;
			public short level;
			public float duration;

			public bool Write(Span<byte> s, ref ushort count)
            {
				bool success = true;
				success &= BitConverter.TryWriteBytes(s.Slice(count, s.Length-count), this.id);
				count += sizeof(int);
				success &= BitConverter.TryWriteBytes(s.Slice(count, s.Length - count), this.level);
				count += sizeof(ushort);
				success &= BitConverter.TryWriteBytes(s.Slice(count, s.Length - count), this.duration);
				count += sizeof(float);
				return success;
            }

			public void Read(ReadOnlySpan<byte> s, ref ushort count)
            {
				id = BitConverter.ToInt32(s.Slice(count, s.Length - count));
				count += sizeof(int);
				level = BitConverter.ToInt16(s.Slice(count, s.Length - count));
				count += sizeof(short);
				duration = BitConverter.ToSingle(s.Slice(count, s.Length - count));
				count += sizeof(float);
            }
        }

		public List<SkillInfo> skills = new List<SkillInfo>();

		public PlayerInfoReq()
        {
            this.playerId = (ushort)PacketID.PlayerInfoReq;
        }

        public override void Read(ArraySegment<byte> segment)
        {
			ushort pos = 0;

			ReadOnlySpan<byte> s = new ReadOnlySpan<byte>(segment.Array, segment.Offset, segment.Count);

			pos += sizeof(ushort);
			pos += sizeof(ushort);

			this.playerId = BitConverter.ToInt64(s.Slice(pos, s.Length - pos));
			pos += sizeof(long);

			ushort nameLen = BitConverter.ToUInt16(s.Slice(pos, s.Length - pos));
			pos += sizeof(ushort);
			this.name = Encoding.Unicode.GetString(s.Slice(pos, nameLen));
			pos += nameLen;

			// skill list
			skills.Clear();
			ushort skillLen = BitConverter.ToUInt16(s.Slice(pos, s.Length - pos));
			pos += sizeof(ushort);

			for(int i=0;i <skillLen; i++)
            {
				SkillInfo skill = new SkillInfo();
				skill.Read(s, ref pos);
				skills.Add(skill);
            }
		}

        public override ArraySegment<byte> Write()
        {
			ArraySegment<byte> segment = SendBufferHelper.Open(4096); // SendBuffer의 _buffer

			ushort size = 0;
			bool success = true;

			// s는 확인용
			Span<byte> s = new Span<byte>(segment.Array, segment.Offset, segment.Count);

			size += sizeof(ushort); // 2 : 패킷 size 사이즈
			success &= BitConverter.TryWriteBytes(s.Slice(size, s.Length - size), this.packetId); // Slice가 Span<byte> 만들어서 반환하면 packetId를 그 범위에 바이트로 변환해서 씀
			size += sizeof(ushort); // packetId 사이즈
			success &= BitConverter.TryWriteBytes(s.Slice(size, s.Length - size), this.playerId);
			size += sizeof(long); // 8 : playerId 사이즈

			// + sizeof(ushort)는 nameLen 넣을 공간 확보
			// 데이터 복사 -> nameLen 추출
			ushort nameLen = (ushort)Encoding.Unicode.GetBytes(this.name, 0, this.name.Length, segment.Array, segment.Offset + size + sizeof(ushort));
			success &= BitConverter.TryWriteBytes(s.Slice(size, s.Length - size), nameLen);
			size += sizeof(ushort);
			size += nameLen;

			/*
			ushort nameLen = (ushort)Encoding.Unicode.GetByteCount(this.name);
			success &= BitConverter.TryWriteBytes(s.Slice(size, s.Length - size), nameLen);
			size += sizeof(ushort);
			Array.Copy(Encoding.Unicode.GetBytes(this.name), 0, segment.Array, size, nameLen);
			size += nameLen;
			*/

			// skill list
			success &= BitConverter.TryWriteBytes(s.Slice(size, s.Length - size), (ushort)skills.Count);
			size += sizeof(ushort);

			foreach (SkillInfo skill in skills)
				success &= skill.Write(s, ref size);

			success &= BitConverter.TryWriteBytes(s, size);


			if (success == false)
				return null; // 반환해서 밖에서 null체크

			return SendBufferHelper.Close(size); // 최종 패킷 크기
		}
    }

	public enum PacketID
	{
		PlayerInfoReq = 1,
		PlayerInfoOk = 2,
	}

	class ServerSession : Session
	{
		static unsafe void ToBytes(byte[] array, int offset, ulong value) 
		{
			fixed (byte* ptr = &array[offset])
				*(ulong*)ptr = value;
		}

		static unsafe void ToBytes<T>(byte[] array, int offset, T value) where T : unmanaged
		{
			fixed (byte* ptr = &array[offset])
				*(T*)ptr = value;
		}

		public override void OnConnected(EndPoint endPoint)
		{
			Console.WriteLine($"OnConnected : {endPoint}");
			
			PlayerInfoReq packet = new PlayerInfoReq() { packetId = 1, playerId = 1001, name = "STREVELUN" };
			packet.skills.Add(new PlayerInfoReq.SkillInfo() { id=101, level = 1,duration=3.0f });
			packet.skills.Add(new PlayerInfoReq.SkillInfo() { id=1013153, level = 2,duration=4.0f });
			packet.skills.Add(new PlayerInfoReq.SkillInfo() { id=1016, level = 3,duration=5.0f });
			packet.skills.Add(new PlayerInfoReq.SkillInfo() { id=10111, level = 7,duration=6.6f });

			ArraySegment<byte> s = packet.Write();

			if (s != null)
				Send(s);
		}

		public override void OnDisconnected(EndPoint endPoint)
		{
			Console.WriteLine($"OnDisconnected : {endPoint}");
		}

		public override int OnRecv(ArraySegment<byte> buffer)
		{
			string recvData = Encoding.UTF8.GetString(buffer.Array, buffer.Offset, buffer.Count);
			Console.WriteLine($"[From Server] {recvData}");
			return buffer.Count;
		}

		public override void OnSend(int numOfBytes)
		{
			Console.WriteLine($"Transferred bytes: {numOfBytes}");
		}
	}

}
profile
개인 공부 정리

0개의 댓글