03_01_패킷 직렬화_Serialization #1_기본
확인 테스트
Q1. 직렬화, 역직렬화란 무엇을 말하는 것인가?
-> 어떤 인스턴스로 존재하는 애를 납작하게 만들어서 buffer안에 밀어 넣는 작업이 직렬화, byte로 되어 있는 애를 꺼내쓰는 작업을 역직렬화
Q2. DummyClient에 ServerSession, Server에는 ClientSession을 만든 이유가 무엇인가? 또 이렇게 이름을 나눈 이유는 무엇인가?
-> Server에 Client의 대리인으로 있는게 ClientSession이고, Client에 Server의 대리인으로 있는게 ServerSession이기 때문이다. 이름을 나누는 이유는 나중에 분산서버를 만들게 되면 각 다른 부분을 관리하는 서버의 대리자 역할을 할 Session이 필요할 것이기 때문이다.
Q3. 어떤 프로세스로 코드를 개선하고 있는가?
-> 샘플을 몇개 만들어 본 다음에 대강 코드가 어떤 식으로 작업을 이어갈지 한번씩 고민을 한 다음에 어느정도 윤곽이 잡히면 따로 코드를 빼가지고 자동화하는 작업을 수행한다.
Q4. DummyClient의 ServerSession에 추가한 PlayerInfoReq와 PlayerInfoOk는 각각 무슨 역할을 하는가?
-> 클라에서 서버로 플레이어의 정보를 알고 싶다 요청하는 것, 서버에서 클라로 답변을 주는 것
Q5. BitConverter.GetBytes를 어떻게 개선해 줄 수 있는가? 개선했을 때 장점과 단점은 무엇인가?
-> byte배열로 만든 다음에 Copy 하는 두 스텝을 거쳤는데 TryWriteBytes를 사용해 그냥 한번에 넣게 했다.
Q4. 자동화 하기 위해 어떤 작업을 하였는가?
-> count를 사용했다.
Q5. 어떤 부분을 자동화 할 예정인가?
-> 직열화, 역직열화를 자동화 처리할 것이다.
Q6. 작업 한 내용을 나열해 보세요.
→
DummyClient에 ServerSession, Server에 ClientSession클래스 파일을 추가하고 각 Program.cs 파일에서 class Program을 제외한 나머지 부분을 복붙해서 옮긴다. 그리고 GameSession클래스의 이름을 각각 ServerSession, ClientSession으로 바꾼다. Server의 Program.cs의 Main의 _listener.Init의 람다 함수에 return new GameSession을 ClientSession으로 바꾸고, DummyClient도 마찬가지로 Connect의 인자중 람다 함수의 리턴값을 new ServerSession();으로 바꾼다.
-> DummyClient의 ServerSession에 Packet을 상속받은 PlayerInfoReq, PlayerInfoOk 클래스와 enum PacketID를 만들어 준다. PlayerInfoReq에는 long playerId를 PlayerInfoOk에는 int hp, int attack을 추가한다. enumPacketID에는 PlayerInfoReq =1, PlayerInfoOk=2를 구성 요소로 추가한다.
→ Server의 ClientSession에도 PlayerInfoReq, PlayerInfoOk, packetID를 복붙한다.
-> ServerSession의 OnConnected에 PlayerInfoReq를 생성하며 생성자를 정의한다. size=4를 packetId = (ushort) PlayerInfoReq를 실행한다. for문을 주석처리 한다. buffer와 buffer2를 size와 packetId로 바꿔준다. PlayerId도 BitConverter.GetBytes로 값을 넣어주게 추가한다. PlayerInfoRequ의 생성자에 PlayerId = 1001도 추가한다.
-> 자동화 하기 위해 ushort count = 0;을 선언한다. Copy로 버퍼에 데이터를 넣을 때 마다 count를 바이트 수만큼 더해준다. ArraySegment<byte> s = SendBufferHelper.Open(4096);를 선언하고 해당하는 건 s로 바꿔준다. Close에도 count를 매개변수로 넣는다.
-> 받는 쪽도 수정을 해준다. ClientSession에 OnRecvpacket에도 count를 선언하고 size와 Id를 추출할 때 받은 파일의 바이트 수를 카운트 해준다. switch문으로 id가 PlayerInfoReq라면 playerId에 Id를 추출하고, 출력도 한다.
-> 실행해 테스트 한다.
-> 개선한다 클라에서 서버로 보낼 때 BitConverter.getBytes를 자주 쓰는데 이미 버퍼를 만들었는데 여기에서 내부적으로 또 만들고 있었어. BitConverter.TryWriteBytes를 호출해서 packetId, playerId, count를 넣어주며 count를 하게 수정한다. 기존의 바이트 배열을 만들고 Copy하는 코드를 주석처리 한다.
→unSafe 코드도 사용할 수 있지만 여기선 그냥 보기만 한다.
-> OnConnecte에서 PlayerInfoReq의 생성자에서 size=4하는 부분을 삭제한다. 패킷의 크기는 Close할 때 알 수 있기 때문이다. success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset, s.Count), count);를 packetId, playerId를 넣어주는 것 보다 아래로 옮긴다.
지난 시간 까지 클라이언트와 서버간에 패킷을 이용한 통신을 간단히 해봤었어.
지난 시간에 DummyClient에서 Packet이라는 애를 만들어서 OnConnected로 보내고 있었는데
namespace DummyClient
{
class Packet
{
public ushort size;
public ushort packetId;
}
class GameSession : Session
{
public override void OnConnected(EndPoint endPoint)
{
Console.WriteLine($"OnConnected : {endPoint}");
Packet packet = new Packet() { size = 4, packetId = 7 };
// 보낸다
for (int i = 0; i < 5; i++)
{
ArraySegment<byte> openSegment = SendBufferHelper.Open(4096);
byte[] buffer = BitConverter.GetBytes(packet.size);
byte[] buffer2 = BitConverter.GetBytes(packet.packetId);
Array.Copy(buffer, 0, openSegment.Array, openSegment.Offset, buffer.Length);
Array.Copy(buffer2, 0, openSegment.Array, openSegment.Offset + buffer.Length, buffer2.Length);
ArraySegment<byte> sendBuff = SendBufferHelper.Close(packet.size);
Send(sendBuff);
}
}
오늘 본격적으로 연구할 작업이 이 작업인데 직렬화, 즉 Serialization이라는 용어로 불리게 된다.
Serialization은 딱히 네트워크에 종속적인 그런 부분은 아니고 포괄적으로 다양한 상황에서 쓰이는 용어다.
애당초 직렬화 한다는 의미는
예를 들면 지난 시간에 패킷이라는 객체가 하나 있었어.
Packet packet = new Packet() { size = 4, packetId = 7 };
인스턴스가 하나 있었는데 얘는 메모리 상에 존재하는 그런 아이였어.
이걸 네트워크 상에 흘려보내기 위해 어떤 작업을 했는지 유심히 살펴 보면
public override void OnConnected(EndPoint endPoint)
{
Console.WriteLine($"OnConnected : {endPoint}");
Packet packet = new Packet() { size = 4, packetId = 7 };
// 보낸다
for (int i = 0; i < 5; i++)
{
ArraySegment<byte> openSegment = SendBufferHelper.Open(4096);
**byte[] buffer = BitConverter.GetBytes(packet.size);
byte[] buffer2 = BitConverter.GetBytes(packet.packetId);**
Array.Copy(buffer, 0, openSegment.Array, openSegment.Offset, buffer.Length);
Array.Copy(buffer2, 0, openSegment.Array, openSegment.Offset + buffer.Length, buffer2.Length);
ArraySegment<byte> sendBuff = SendBufferHelper.Close(packet.size);
Send(sendBuff);
}
이런 식으로 byte 배열에 밀어 넣고 있었어.
Open함수로 _buffer를 4096사이즈 만큼 집어서 openSegment에 연결해. buffer, buffer2를 byte배열로 변환해 openSegment에 Copy해 밀어 넣고, Close함수로 _buffer에서 집은 것을 실제로 사용한 크기로 다시 집은 다음에 sendBuff에 연결한다.
최종적으로는 sendBuff를 Send를 해서 네트워크로 보내고 있었어.
간단한 ushort타입으로 했으니까 이게 왜 직열화인지 이해가 안갈 수 있지만 경우에 따라서 Packet 안에 list가 있다거나 string 같이 일반적이지 않은 그런 클래스도 충분히 들고 있을 수 있다.
그런 클래스도 버퍼 안에 넣어서 버퍼 안에 넣어서 통신을 하게 되는 상황이 당연히 찾아 오는데
이런 식으로 메모리 상에 어떤 인스턴스로 존재하는 애를 납작하게 만들어서 buffer안에 밀어 넣는 작업이 직렬화 라고 생각하면 된다.
역으로 Server에서는 byte로 되어 있는 애를 꺼내쓰는 작업도 하고 있었어.
public override void OnRecvPacket(ArraySegment<byte> buffer)
{
ushort size = BitConverter.ToUInt16(buffer.Array, buffer.Offset);
ushort id = BitConverter.ToUInt16(buffer.Array, buffer.Offset+2);
Console.WriteLine($"RecvPacketId: {id}, Size: {size}");
}
얘는 Serialization의 반대인 DeSerialization이라고 한다.
역직렬화 라고 생각하면 된다.
이렇게 네트워크 통신을 하다 보면 바이트 배열에다 넣고 꺼내는 작업이 많이 일어나기 때문에 중요하다.
직열화 라는게 이런식으로 네트워크 통신에서만 사용되는 게 아니라 일반적으로 세이브 파일을 만든다고 가정을 하면, 세이브 파일에서도 똑같이 현재 메모리 상에 있는 다양한 객체들 유저이든, 아이템 정보들을 파일로 만들어서 그걸 세이브 파일로 저장하게 된다. 이 때 마찬가지로 byte배열을 만들어서 파일로 저장하게 될텐데 그럴 때도 직렬화라는 용어를 사용한다.
본격적으로 연구를 하기 전에 정리를 하고 넘어갈건데
GameSession의 경우 굉장히 자주 사용 하게 될거야. 지금은 간단하게 사용하고 있었으니까 DummyClient, Server의 Program.cs안에 같이 기생을 시켰지 이제 분리를 할거야.
DummyClient로 가서 GameSession이란 이름이 성의가 없으니까 구분을 하기 위해서 ServerSession이란 이름의 파일을 추가한다.
Client인데 왜 ServerSession인지 이상하게 생각할 수 있다.
애당초 Session이라는 개념 자체가 대리자의 개념이었어. 식당에서 예로 든게 어떤 일행이 식당 예약을 한 다음에 거기에 자신의 일행을 밀어 넣는, 그런 대리인을 앉힌다는 표현을 했었는데 그게 세션이라는 애였어.
클라 쪽에는 서버의 대리자가 ServerSession이고, 반대로 서버 쪽에는 클라이언트의 대리인의 역할을 하는게 ClientSession이 될테니까 그래서 거꾸로 이름을 지어 준 거 .
DummyClient의 Program 빼고 나머지 class Packet, class GameSession 부분을 잘라내서 ServerSession에 옮기고 GameSession 클래스 이름을 ServerSession으로 바꾼다.
using 부분도 복붙을 해줘 error를 해결한다.
마찬가지로 Server쪽에도 추가→새항목으로 Client를 대표하는 애니까 ClientSession이란 애를 만들어 준다.
마찬가지로 Server의 Program의 class Program을 제외한 class Packet, class GameSession 코드를 옮겨주고, GameSession을 ClientSession으로 이름을 바꾼다.
using 부분도 복붙을 해줘 error를 해결한다.
이렇게 이름을 나누는 이유는 나중에 Session이 여러개가 필요하게 될 수도 있다.
Server에서 나중에 연결하는 대상이 꼭 클라이언트만 있는게 아닐 수 있다. 분산 서버를 만들면 서버끼리도 예를 들면 DataBase를 관리하는 DB서버, 게임서버 랑도 통신을 해야 될 수도 있기 때문에 이런식으로 Session 의 이름을 정확하게 지어준다.
ClientSession이라고 이름을 바꿔 줬으면 Server의 Program의 Main에서
static void Main(string[] args)
{
// DNS ( Domain Name System )
string host = Dns.GetHostName();
IPHostEntry ipHost = Dns.GetHostEntry(host);
IPAddress ipAddr = ipHost.AddressList[0];
IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777);
//_listener.Init(endPoint, OnAcceptHandler);
//_listener.Init(endPoint, () => { return new GameSession(); });
**_listener.Init(endPoint, () => { return new ClientSession(); });**
Console.WriteLine("Listening...");
while (true)
{
;
}
}
return new ClientSession(); 으로 변경한다.
DmummyClient의 Program에서도 마찬가지로
connector.Connect(endPoint, () => { return new ServerSession(); });
return new ServerSession으로 변경해준다.
왼쪽화면엔 Client의 Program, ServerSession, 오른쪽 화면엔 Server의 Program, ClientSession를 둔다.
패킷을 만들 때 여러 방법이 있겠지만 지난 시간에 했던대로 샘플을 몇개 만들어 본 다음에 대충 코드가 어떤 식으로 작업을 이어갈지 한번씩 고민을 한 다음에 어느정도 윤곽이 잡히면 따로 코드를 빼가지고 자동화하는 작업으로 이어지면 될 거 같아.
일단 DummyClient의 ServerSession에 Packet을 상속받은 PlayerInfoReq라는 클래스를 만들어 준다.
그리고 PlayerInfoOk라는 클래스도 만들어 준다. enum PacketID도 만들어준다.
namespace DummyClient
{
class Packet
{
public ushort size;
public ushort packetId;
}
class PlayerInfoReq : Packet // 클라에서 서버로: 나는 플레이어 정보를 알고 싶어. 그 정보는 playerId라는 플레이어야.
{
public long playerId;
}
class PlayerInfoOk : Packet // 서버에서 클라로: 답변을 주는 거.
{
public int hp;
public int attack; // hp와 attack을 반환한다고 가정을 해보자. 간단하게 상황을 만들어 준 거.
}
// 나중에 가면 패킷마다 자신의 번호가 있을거야. PlayerInfoReq도 그렇고 PlayerInfoOk도 그렇고 Packet을 상속받고 있으니까
// size랑 packetId는 당연히 들어갈거야. 나중에는 다 자동화를 해야 할거야.
public enum PacketID
~~~~{
PlayerInfoReq = 1,
PlayerInfoOk = 2,
}
지금은 일단 이걸로 테스트를 해볼거야.
이 정보는 클라에서만 갖고 있으면 안되고 서버쪽에서도 똑같이 얘를 알고 이어야만 걔를 맞춰서 파싱을 하고 사용할 수 있다. Server의 ClientSession에도 PlayerInfoReq, PlayerInfoOk, packetID를 복붙해준다.
나중에는 이런 식으로 패킷을 정의하는 애를 각각에 놓을 게 아니라 공통적인 부분에다 배치를 시켜 줘야겠다 생각이 든다.
보내는 거 부터할지 받는 거 부터 할지 고민이 좀 되는데 보내는 거 먼저 해보자.
지난 시간에 인터페이스는 간략하게 나마 만들어 주긴 했었어.
class ServerSession : Session
{
public override void OnConnected(EndPoint endPoint)
{
Console.WriteLine($"OnConnected : {endPoint}");
Packet packet = new Packet() { size = 4, packetId = 7 };
// 보낸다
for (int i = 0; i < 5; i++)
{
ArraySegment<byte> openSegment = SendBufferHelper.Open(4096);
byte[] buffer = BitConverter.GetBytes(packet.size);
byte[] buffer2 = BitConverter.GetBytes(packet.packetId);
Array.Copy(buffer, 0, openSegment.Array, openSegment.Offset, buffer.Length);
Array.Copy(buffer2, 0, openSegment.Array, openSegment.Offset + buffer.Length, buffer2.Length);
ArraySegment<byte> sendBuff = SendBufferHelper.Close(packet.size);
Send(sendBuff);
}
}
일반적으로 어떤 패킷을 보낸다고 했을 때 어떻게 바뀔지 계속 생각을 하면서 작업을 하는 거.
PlayerInfoReq패킷을 만약 보내고 싶다고 가정 해보자.
ServerSession의 OnConnected를 수정해준다.
public override void OnConnected(EndPoint endPoint)
{
Console.WriteLine($"OnConnected : {endPoint}");
**PlayerInfoReq** packet = new **PlayerInfoReq**() { size = 4, packetId = 7 };
// 보낸다
for (int i = 0; i < 5; i++)
{
ArraySegment<byte> openSegment = SendBufferHelper.Open(4096);
byte[] buffer = BitConverter.GetBytes(packet.size);
byte[] buffer2 = BitConverter.GetBytes(packet.packetId);
Array.Copy(buffer, 0, openSegment.Array, openSegment.Offset, buffer.Length);
Array.Copy(buffer2, 0, openSegment.Array, openSegment.Offset + buffer.Length, buffer2.Length);
ArraySegment<byte> sendBuff = SendBufferHelper.Close(packet.size);
Send(sendBuff);
}
}
Client가 서버쪽에 붙자마자 OnConnected가 뜰텐데
그 때
**PlayerInfoReq** packet = new **PlayerInfoReq**() { size = 4, packetId = 7 };
바로 이런 식으로 나의 플레이어 정보를 줘 라는 요청을 보내게 된다.
지난 시간에는 packetId를 7로 하드코딩 했었는데
지금은 PlayerInfoReq : Packet의 구성 요소인 size, packetId, playerId가 들어가야 한다.
PlayerInfoReq packet
= new PlayerInfoReq() { **size** = 4, **packetId** = (ushort)PacketID.**PlayerInfoReq**};
일단 (ushort)PacketID.PlayerInfoReq를 packetId에 넣어 주면 된다.
그리고 5번 보낼 필요 없으니까 for문은 주석처리를 한다.
// 보낸다
//for (int i = 0; i < 5; i++)
{
ArraySegment<byte> openSegment = SendBufferHelper.Open(4096);
**byte[] buffer = BitConverter.GetBytes(packet.size);**
**byte[] buffer2 = BitConverter.GetBytes(packet.packetId);**
Array.Copy(buffer, 0, openSegment.Array, openSegment.Offset, buffer.Length);
Array.Copy(buffer2, 0, openSegment.Array, openSegment.Offset + buffer.Length, buffer2.Length);
ArraySegment<byte> sendBuff = SendBufferHelper.Close(packet.size);
Send(sendBuff);
일단은 Open을 해서 원하는 사이즈를 예약 한 다음에 그 만큼의 공간을 확보 해서 작업을 하고 있고,
그 다음에 유심히 뭘 하는지 살펴보면 BitConverter.GetBytes를 이용해가지고 정보를 하나하나씩 만들고 있었어. 이 버전이 마음에 안드는데 일단은 나중에 수정하도록 하고,
buffer, buffer2라고 하면 헷갈리니까 size와 packetId로 바꿔준다.
playerId도 BitConverter.GetBytes로 만들어 준다.
{
ArraySegment<byte> openSegment = SendBufferHelper.Open(4096);
byte[] **size** = BitConverter.GetBytes(packet.**size**);
byte[] **packetId** = BitConverter.GetBytes(packet.**packetId**);
byte[] **playerId** = BitConverter.GetBytes(packet.**playerId**);
****Array.Copy(size, 0, openSegment.Array, openSegment.Offset, size.Length);
Array.Copy(packetId, 0, openSegment.Array, openSegment.Offset + size.Length, packetId.Length);
ArraySegment<byte> sendBuff = SendBufferHelper.Close(packet.size);
Send(sendBuff);
}
여기에도 일단 테스트니까 palyerId를 1001 이라 가정을 하고 매칭을 해보도록 하자.
PlayerInfoReq packet
= new PlayerInfoReq() { size = 4, packetId = (ushort)PacketID.PlayerInfoReq, **playerId =1001**};
class ServerSession : Session
{
public override void OnConnected(EndPoint endPoint)
{
Console.WriteLine($"OnConnected : {endPoint}");
PlayerInfoReq packet = new PlayerInfoReq() { size = 4, packetId = (ushort)PacketID.PlayerInfoReq, playerId = 1001 };
// 보낸다
//for (int i = 0; i < 5; i++)
{
ArraySegment<byte> openSegment = SendBufferHelper.Open(4096);
byte[] size = BitConverter.GetBytes(packet.size);
byte[] packetId = BitConverter.GetBytes(packet.packetId);
byte[] playerId = BitConverter.GetBytes(packet.playerId);
// 여기서 일단 바이트 배열을 만들어 줬고,
****Array.Copy(size, 0, openSegment.Array, openSegment.Offset, size.Length);
Array.Copy(packetId, 0, openSegment.Array, openSegment.Offset + size.Length, packetId.Length);
ArraySegment<byte> sendBuff = SendBufferHelper.Close(packet.size);
// 이부분이 중요한데
Send(sendBuff);
}
}
Array.Copy(size, 0, openSegment.Array, openSegment.**Offset**, size.Length);
Array.Copy(packetId, 0, openSegment.Array, openSegment.**Offset** + **size**.**Length**, packetId.Length);
ArraySegment<byte> sendBuff = SendBufferHelper.Close(packet.size);
얘를 나중에 자동화 하기 위해서는 어떤 작업이 들어가야 될지를 살펴 봅시다. 일단 Copy를 매번마다 하고 있었는데 Offset에서 정보가 조금씩 달라지고 있었어. Offset에다가 지금까지 작업한 길이를 늘려주면서 수정하고 있었어.
만약 같은 방법으로 playerId도 넣어줘야 한다고 해보자.
Array.Copy(size, 0, openSegment.Array, openSegment.Offset, size.Length);
Array.Copy(packetId, 0, openSegment.Array, openSegment.Offset + size.Length, packetId.Length);
Array.Copy(playerId, 0, openSegment.Array, openSegment.Offset + size.Length + packetId.Length, playerId.Length);
ArraySegment<byte> sendBuff = SendBufferHelper.Close(packet.size);
하드 코딩을 해야 한다고 하면 지금까지 몇 바이트를 썼는지 계속 추적을 해야 한다.
byte[] size = BitConverter.GetBytes(packet.size); // 2바이트
byte[] packetId = BitConverter.GetBytes(packet.packetId); // 2바이트
byte[] playerId = BitConverter.GetBytes(packet.playerId); // 8바이트 짜리가 될 거야.
openSegment에 이 3가지 정보를 넣는다는 것은
처음에는 Offset에 +0이 생략된 것이다.
Array.Copy(size, 0, openSegment.Array, openSegment.**Offset + 0**, **2**);
Array.Copy(packetId, 0, openSegment.Array, openSegment.Offset **+ 2, 2**);
Array.Copy(playerId, 0, openSegment.Array, openSegment.Offset **+ 4, 8**);
ArraySegment<byte> sendBuff = SendBufferHelper.Close(**12**);
이렇게 숫자를 넣어 줘야 한다.
이런식으로 숫자를 계속 넣어주기에는 계산하기 어려우니까 변수를 하나 두도록 할거야.
**ushort count = 0;** // 지금까지 **몇 바이트를 버퍼에 밀어 넣었는지 추적**할거야.
Array.Copy(size, 0, openSegment.Array, openSegment.Offset+0, 2);
count += 2;
// Offset에 더해주는 부분을 count로 한다.
Array.Copy(packetId, 0, openSegment.Array, openSegment.Offset + count, 2);
count += 2;
Array.Copy(playerId, 0, openSegment.Array, openSegment.Offset + count, 8);
count += 8;
ArraySegment<byte> sendBuff = SendBufferHelper.Close(count);
이렇게 구현을 할 수 있다;
openSegment도 이름이 너무 기니까 s로 줄여주자
class ServerSession : Session
{
public override void OnConnected(EndPoint endPoint)
{
ArraySegment<byte> s = SendBufferHelper.Open(4096);
byte[] size = BitConverter.GetBytes(packet.size); // 2
byte[] packetId = BitConverter.GetBytes(packet.packetId); // 2
byte[] playerId = BitConverter.GetBytes(packet.playerId); // 8
ushort count = 0; // 지금까지 몇 바이트를 버퍼에 밀어 넣었는지 추적할거야.
Array.Copy(size, 0, s.Array, s.Offset + count, 2);
**count += 2;**
Array.Copy(packetId, 0, s.Array, s.Offset + count, 2);
**count += 2;**
Array.Copy(playerId, 0, s.Array, s.Offset + count, 8);
**count += 8;**
ArraySegment<byte> sendBuff = SendBufferHelper.Close(**count**);
Send(sendBuff);
}
이런식으로 카운팅을 하면 간단하게 자동화 할 수 있다는 생각이 든다.
이런 식으로 중간 중간에 count를 사이즈 만큼 늘려주면 된다.
2,2,8은
namespace DummyClient
{
class Packet
{
public ushort size; // 2바이트
public ushort packetId; // 2바이트
}
class PlayerInfoReq : Packet
public long playerId; // 8바이트
}
일단은 ServerSession에서 이렇게 해서 보내면 될 거 같다는 생각이 든다.
이제 받는 쪽에서 어떻게 수정해야 편하게 받을지 고민해보자.
ClientSession에
public override void OnRecvPacket(ArraySegment<byte> buffer)
{
ushort size = BitConverter.ToUInt16(buffer.Array, buffer.Offset);
ushort id = BitConverter.ToUInt16(buffer.Array, buffer.Offset + 2);
Console.WriteLine($"RecvPacketId: {id}, Size: {size}");
}
OnRecvPacket에 오면 buffer 안에는 완성된 패킷이 들어와 있을 거고,
얘도 똑같이 파싱을 해야 하는데, 지금 했던 것과 마찬가지로 여기서도 카운팅을 하면 굉장히 편할 거 같아.
public override void OnRecvPacket(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;**
**switch((PacketID)id)
{
case PacketID.PlayerInfoReq:
{
long playerId = BitConverter.ToInt64(buffer.Array, buffer.Offset + count);
count += 8; // 나중에 이어서 파싱을 해야 할 경우를 대비해서 맞춰준다.
Console.WriteLine($"PlayerInfoReq: {playerId}");
}
break;
}**
Console.WriteLine($"RecvPacketId: {id}, Size: {size}");
}
지금 하는 건 나중에 이걸 자동화 하기 앞서서 어떤 식으로 얘를 만들어 줄지를 계속 생각을 하면서 만들어 주고 있는거.
이 상태에서 실행을 해보면
서버쪽에 PlayerInfoReq: 1001 이 잘 들어온 것을 볼 수 있다.
대충 이런 느낌으로 만들어주면 되겠다는 생각이 들긴 한데
이제 개선할 게 없을지 살본다.
패킷을 직렬화 해서 보내고 받는 부분은 정말 많이 사용될 것이기 때문에 신경을 곤두 세워서 최적화 할 수 있는 부분을 하고 넘어가는게 좋다.
지금 가장 마음에 안드는 부분은
받는 부분은 버퍼를 받아서 뽑아 오는 건 문제 없는데, 반대로 클라에서 서버로 보낼 떄
class ServerSession : Session
{
public override void OnConnected(EndPoint endPoint)
{
Console.WriteLine($"OnConnected : {endPoint}");
PlayerInfoReq packet = new PlayerInfoReq() { size = 4, packetId = (ushort)PacketID.PlayerInfoReq, playerId = 1001 };
// 보낸다
//for (int i = 0; i < 5; i++)
//{
ArraySegment<byte> s = SendBufferHelper.Open(4096);
**** byte[] size = **BitConverter**.**GetBytes**(packet.size);
byte[] packetId = **BitConverter**.**GetBytes**(packet.packetId);
byte[] playerId = **BitConverter**.**GetBytes**(packet.playerId);
BitConverter.GetBytes는 좀 찜찜하긴 하다.
ushort나 long을 넣어 줬는데 byte배열을 반환 했다는 건,
결국 내부적으로 보면 어떤 식으로든
byte[] n = new byte[4]
new를 해줘가지고 byte배열을 동적으로 중간 중간에 만들어 줬다는 얘기가 되니까 이게 찝찝하다.
사실은 이렇게 할게 아니라 애당초 우리는 버퍼를 하나 만들어 줬었어.
ArraySegment<byte> s = SendBufferHelper.Open(4096);
ArraySegment<byte> s 얘가 열린 버퍼이니까 여기다가 막바로 집어 넣어주면 좋겠다는 생각이 든다.
byte[] size = **BitConverter**.**GetBytes**(packet.size);
byte[] packetId = **BitConverter**.**GetBytes**(packet.packetId);
byte[] playerId = **BitConverter**.**GetBytes**(packet.playerId);
근데 이 버전은 그렇게 하지 못하니까 효율성이 많이 떨어진다.
이걸 해결하기 위해서는 버젼이 다양하게 존재한다. 선택의 시간이 오는 거 .
일단 하나의 예를 봐보자.
여러가지 버젼을 예로 드는 이유는 나중에 유니티에서 모든 버젼을 다 사용할 수 있는지 하나씩 다 체크를 해봐야 한다.
간단히 생각을 보면 BitConverter.을 눌러보면 다양한 버젼이 나온다.
TryWriteBytes가 있어.
얘를 사용하면 Span<byte> destination 여기다가 어디에 집어 넣을지를 입력을 해서 막바로 넣어 줄 수 있는데
이를테면 ArraySegment<byte> s에 넣을 수 있어.
사용하는 모습을 살펴보면
이전에 사용하던 arraySegment와 비슷하다고 볼 수 있는데
byte배열이랑 start, length를 넣어주고 있는 걸 볼 수 있다.
BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset, s.Count), packet.size);
//( 기본 형태가 될거고, 추출해야 될 거)
이런 인터페이스로 맞춰준다.
bool success = true;
success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset, s.Count), packet.size);
// 성공했냐 실패했냐를 리턴할거니까 boolean으로 받아주면 된다.
// 혹시라도 중간에 한번이라도 실패하면 false가 뜬다.
byte[] size = BitConverter.GetBytes(packet.size);
byte[] packetId = BitConverter.GetBytes(packet.packetId);
byte[] playerId = BitConverter.GetBytes(packet.playerId);
그럼 TryWriteBytes가 실패할 수 있는 경우가 무엇인지 생각해 보면
s.Count가 2byte 였는데 넣어주는 packet.size는 8byte거나 해가지고 공간이 부족하면 실패를 하게 될 것이다.
C#에서 이렇게 쉽게 GetBytes 형식으로 쉽게 만들어준 이유는 애초에 안정성 때문에 이렇게 만들어준 것이다. 안정성을 계속 보장을 해주다 보니 속도가 느려져서 최적화 할 버전을 찾고 있는 거.
success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset, s.Count), packet.size);
실패했느냐 성공했느냐 여부가 갈리게 된다.
이거를 2개 더 만들어 줘야 하는데, count를 해주자.
public override void OnConnected(EndPoint endPoint)
{
Console.WriteLine($"OnConnected : {endPoint}");
PlayerInfoReq packet = new PlayerInfoReq() { size = 4, packetId = (ushort)PacketID.PlayerInfoReq, playerId = 1001 };
// 보낸다
//for (int i = 0; i < 5; i++)
{
ArraySegment<byte> s = SendBufferHelper.Open(4096);
**ushort count = 0;** // 지금까지 몇 바이트를 버퍼에 밀어 넣었는지 추적할거야.
bool success = true;
// [] [] [] [] [] [] [] [] [] []
// 기존에 버퍼를 2바이트 만큼 넣어줬으면 커서가 2바이트만큼 갈거야, 그럼 유효 범위가 3번째 부터가 될거고, 버퍼의 유효범위도 2만큼 줄어들어야 아다리가 맞게 될거야.
// 그래서 s.Offset에 count를 더해 줘야 하고, 버퍼 크기에다가는 count를 빼줘야 맞는다는 얘기가 된다.
// 혹시라도 중간에 한번이라도 실패하면 false가 뜬다.
success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset, s.Count), packet.**size**);
**count += 2;**
success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset + **count**, s.Count - **count**), packet.**packetId**);
**count += 2;**
success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset + **count**, s.Count - **count**), packet.**playerId**);
**count += 8;**
//byte[] size = BitConverter.GetBytes(packet.size); // 2
//byte[] packetId = BitConverter.GetBytes(packet.packetId); // 2
//byte[] playerId = BitConverter.GetBytes(packet.playerId); // 8
//Array.Copy(size, 0, s.Array, s.Offset + count, 2);
//count += 2;
//Array.Copy(packetId, 0, s.Array, s.Offset + count, 2);
//count += 2;
//Array.Copy(playerId, 0, s.Array, s.Offset + count, 8);
//count += 8;
ArraySegment<byte> sendBuff = SendBufferHelper.Close(count);
Send(sendBuff);
}
}
이렇게 BitConverter.GetBytes를 이용하기 보다는 BitConverter.TryWriteBytes를 사용해서 한번에 집어넣어 줄 수 있다.
결국 이부분은 필요 없게 된다.
//byte[] size = BitConverter.GetBytes(packet.size); // 2
//byte[] packetId = BitConverter.GetBytes(packet.packetId); // 2
//byte[] playerId = BitConverter.GetBytes(packet.playerId); // 8
//Array.Copy(size, 0, s.Array, s.Offset + count, 2);
//count += 2;
//Array.Copy(packetId, 0, s.Array, s.Offset + count, 2);
//count += 2;
//Array.Copy(playerId, 0, s.Array, s.Offset + count, 8);
//count += 8;
처음보다 더 효율적으로 되었다. 하지만 이게 유니티에서도 되는지는 나중 문제다. 확인을 해봐야 한다.
옛날 버전에는 TryWriteBytes 버전이 없어가지고
그럴 때 옵션으로 사용할 수 있는 방법이 여러가지 있다.
샘플을 하나 보여주자.
참고로 unsafe 코드를 사용하기 시작하는 거. C#에서는 포인터를 직접적으로 다루지 못한다 원래. 하지만 unsafe를 명시를 하면 마치 C++ 코드인마냥 포인터를 조작할 수 있게 된다.
// 여기의 array의 offset에댜 value를 넣어주고 싶다고 하면
static unsafe void ToBytes(byte[] array, int offset, ulong value)
{
fixed (byte* ptr = &array[offset])
*(ulong*)ptr = value;
}
array의 offset에 value를 넣어주는 부분을 C++과 유사한 문법으로 처리를 해줄 수 있게 된다.
에러 나는 건 잠재적 수정사항 표시에서 이 프로젝트에서 안전하지 않은 코드 허용을 누르면 된다.
이러면 unsafe 계열의 기능들도 사용할 수 있게 된다. 이러면 훨씬 더 속도는 빨라진다. 당장은 사용하지 않을 거다.
이외에도 다양한 옵션이 있다. 비트연산 노가다로 맞춰주는 방법도 있고 다양하게 존재를 하는데 일단은 TryWriteBytes가 이미 있으니까 이걸 사용하자.
public override void OnConnected(EndPoint endPoint)
{
Console.WriteLine($"OnConnected : {endPoint}");
PlayerInfoReq packet = new PlayerInfoReq() { size = 4, packetId = (ushort)PacketID.PlayerInfoReq, playerId = 1001 };
// 보낸다
//for (int i = 0; i < 5; i++)
{
ArraySegment<byte> s = SendBufferHelper.Open(4096);
ushort count = 0; // 지금까지 몇 바이트를 버퍼에 밀어 넣었는지 추적할거야.
bool success = true;
// 혹시라도 중간에 한번이라도 실패하면 false가 뜬다.
success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset, s.Count), packet.size);
count += 2;
success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset + count, s.Count - count), packet.packetId);
count += 2;
success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset + count, s.Count - count), packet.playerId);
count += 8;
ArraySegment<byte> sendBuff = SendBufferHelper.Close(count);
if(success)
Send(sendBuff);
}
}
결국 얘가 if(success) 까지 왔으면 Send해주면 된다. success가 fail이면 뭔가 잘못 했거나 공간이 부족하거나 해서 문제가 있었던 거.
그래서 이렇게 보내 주면 똑같이 받을 수 있는지를 보면 된다.
한가지 묘한 상황이 있다.
class ServerSession : Session
{
public override void OnConnected(EndPoint endPoint)
{
Console.WriteLine($"OnConnected : {endPoint}");
PlayerInfoReq packet = new PlayerInfoReq() { **size = 4**, packetId = (ushort)PacketID.PlayerInfoReq, playerId = 1001 };
size를 4바이트로 넣어줘서 작업을 하고 있었어. 실제로 최종적으로 이 패킷의 사이즈는 4바이트가 아닐거야. 최종적인 크기는
ArraySegment<byte> sendBuff = SendBufferHelper.**Close**(count);
Close까지 와야 알 수 있다.
12가 실제 크기가 된다.
PlayerInfoReq packet = new PlayerInfoReq() {/***size = 4**,*/ packetId = (ushort)PacketID.PlayerInfoReq, playerId = 1001 };
결국 size는 예외적으로 시작하는 부분에서 넣어줄 수 없고, 마지막에 모든 작업이 다 끝난 다음에서야 몇 바이트인지 아니까 이때 마지막으로 채워져야 하는게 정석적인 방법이 된다.
// 결국 size를 여기서 할 수 없기 때문에 얘를 여기서 작업할 수 없다. 맨 마지막으로 옮겨야 한다.
//success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset, s.Count), packet.size);
count += 2;
success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset + count, s.Count - count), packet.packetId);
count += 2;
success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset + count, s.Count - count), packet.playerId);
count += 8;
// 여기서 넣어준 count 만큼을 맨 처음에 있던 식에 넣어줘야 한다.
success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset, s.Count), **count**);
이렇게 count를 맨 처음 offset에 넣어줘야 한다.
**ushort** count = 0;
count는 반드시 ushort 타입으로 연산을 해야 2바이트가 들어가게 된다. int가 되면 목적지에 4바이트를 넣어주는게 되기 때문에 조심해야 한다.
TryWriteBytes를 보면 버전이 엄청 많은데 두번재 인자 value에 넣어주는 값에 따라 가지고 알맞은 타입의 크기를 찾아서 버퍼에 복사를 해주게 된다.
정상적인 상황이라 가정을 하면 12바이트가 Server의 ClientSession으로 전송이 될거야. 근데 찜찜한 점은 ServerSession에서
PlayerInfoReq packet = new PlayerInfoReq() { **size = 4**, packetId = (ushort)PacketID.PlayerInfoReq, playerId = 1001 };
잘못된 4라는 크기로 보냈는데도 ClientSession의 OnRecvPacket에서 정상적으로 파싱이 됐었다는 건 찜찜하다.
public override void OnRecvPacket(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;
결국 체크를 할 때 ToUInt16을 계속 하는 것도 중요하지만 count+=2 얘가 진짜 버퍼의 크기를 벗어났냐 야니냐도 지속적으로 체크를 해야 한다.
지금은 운 좋게 데이터 크기 잘 못 와있었지만, 실제 버퍼 크기는 제대로 와 있었기 때문에 운좋게 실행이 된 것이지만 나중에 가면 이런 부분을 조심해서 생각해야 한다.
만약 count를 잘못 넣어줬다 가정을 해보자.의도적일 수도 있고 코딩 실수로 잘못 넣은 걸수도 있어. 해커가 장난치기 위해 말도 안되는 이상한 값을 넣은 경우도 존재 할 것이다. 그럴 때 우리 코드에서 그걸 찾아가지고, 뭔가 이상한 점이 있으면 컷트를 해야 한다.
일단 ClientSession의
public override void OnRecvPacket(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;
**switch ((PacketID)id)**
{
case PacketID.PlayerInfoReq:
{
long playerId = BitConverter.ToInt64(buffer.Array, buffer.Offset + count);
count += 8; // 나중에 이어서 파싱을 해야 할 경우를 대비해서 맞춰준다.
Console.WriteLine($"PlayerInfoReq: {playerId}");
}
break;
}
Console.WriteLine($"RecvPacketId: {id}, Size: {size}");
}
switch((PacketID)id)에 breakPoint을 잡고 실행을 해보자.
id는 1
playerId는 1001
size는 12바이트로 잘 전송 된 것을 알 수 있다.
결국 우리가 한 건 나중에 자동화 할 때를 대비해서 어떤 식으로 코드를 만들어 줄지 조금씩 생각을 하기 시작한 것
class ServerSession : Session
{
public override void OnConnected(EndPoint endPoint)
{
Console.WriteLine($"OnConnected : {endPoint}");
PlayerInfoReq packet = new PlayerInfoReq() {packetId = (ushort)PacketID.PlayerInfoReq, playerId = 1001 };
// 보낸다
//for (int i = 0; i < 5; i++)
{
ArraySegment<byte> s = SendBufferHelper.Open(4096);
ushort **count** = 0; // 지금까지 몇 바이트를 버퍼에 밀어 넣었는지 추적할거야.
bool success = true;
// 혹시라도 중간에 한번이라도 실패하면 false가 뜬다.
//success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset, s.Count), packet.size);
**count += 2;**
success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset + count, s.Count - count), packet.packetId);
**count += 2;**
success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset + count, s.Count - count), packet.playerId);
**count += 8;**
success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset, s.Count), count);
ArraySegment<byte> sendBuff = SendBufferHelper.Close(count);
if (success)
Send(sendBuff);
}
}
맨 처음 했던 것처럼 그냥 숫자를 넣는게 아니라 count를 해서 더해가면서 만들어 주면 된다는 생각이 든다.
여기서 +2, +2, +8 하는 건 애당초 하드코딩이 아니냐 하는 생각이 들 수 있는데
class Packet
{
public ushort size;
public ushort packetId;
}
나중에 가면 여깄는 Packet의 정보를 통해가지고 count에다가 +2를 더해주는 부분은 자동화 해서 처리를 하면 된다.
그렇게 되면 넣어주는 부분은 통째로 신경 안쓰고,
PlayerInfoReq packet
= new PlayerInfoReq() {packetId = (ushort)PacketID.PlayerInfoReq, playerId = 1001 };
그냥 PlayerInfoReq packet만 만들어 준 다음에 아래의 자동화 된 부분을 호출만 하면 serialization 해가지고 밀어넣어주는 부분은 자동으로 처리 해줄거라는 얘기가 된다.
마찬가지로 반대로 받는 쪽에서도 count+=2 하는 부분들은 다 자동화 해가지고 제대로 처리를 한다고 하면
switch ((PacketID)id)
{
case PacketID.PlayerInfoReq:
{
long playerId = BitConverter.ToInt64(buffer.Array, buffer.Offset + count);
count += 8; // 나중에 이어서 파싱을 해야 할 경우를 대비해서 맞춰준다.
Console.WriteLine($"PlayerInfoReq: {playerId}");
}
break;
}
이런 식으로 데이터를 추출해서 처리하는 것도 자동화 처리를 할 수 있을거야.
지금은 Packet들을 class 형태로 하나씩 정의를 하고 있었어.
근데 놀랍게도 프로젝트에 따라 패킷을 어떻게 관리할지가 갈리게 되는데
지금처럼 Packet을 class로 하나하나 만들어서 별도의 파일에다 관리하는 프로젝트도 더러 있다.
일반적인 경우에는 별도의 파일 xml이나 json같은 데다가 패킷을 정의하는 파일을 하나 만들어서 거기서 온갖 정보들을 관리해서 걔를 자동화 해서 처리하는 경우가 가장 많다.
작업한 코드
Server/DummyClient/Program.cs
using ServerCore;
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace DummyClient
{
internal class Program
{
static void Main(string[] args)
{
// DNS ( Domain Name System )
string host = Dns.GetHostName();
IPHostEntry ipHost = Dns.GetHostEntry(host);
IPAddress ipAddr = ipHost.AddressList[0];
IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777);
// ipAddre는 식당 주소, 7777은 식당 정문인지 후문인지 문의 번호 }
// 식당 주소 찾는 부분은 똑같을 거야.
Connector connector = new Connector();
connector.Connect(endPoint, () => { return new ServerSession(); });
while(true)
{
try
{
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
Thread.Sleep(100);
}
}
}
}
Server/DummyClient/ServerSession.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using ServerCore;
namespace DummyClient
{
class Packet
{
public ushort size;
public ushort packetId;
}
class PlayerInfoReq : Packet // 클라에서 서버로 플레이어 정보를 알고 싶어
{
public long playerId;
}
class PlayerInfoOk : Packet // 서버에서 클라로 답변을 주는 거
{
public int hp;
public int attack;
}
public enum PacketID
{
PlayerInfoReq = 1,
PlayerInfoOk = 2,
}
class ServerSession : Session
{
public override void OnConnected(EndPoint endPoint)
{
Console.WriteLine($"OnConnected : {endPoint}");
PlayerInfoReq packet = new PlayerInfoReq() { size = 4, packetId = (ushort)PacketID.PlayerInfoReq, playerId = 1001 };
// 보낸다
//for (int i = 0; i < 5; i++)
{
ArraySegment<byte> s = SendBufferHelper.Open(4096);
ushort count = 0; // 지금까지 몇 바이트를 버퍼에 밀어 넣었는지 추적할거야.
bool success = true;
// 혹시라도 중간에 한번이라도 실패하면 false가 뜬다.
//success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset, s.Count), packet.size);
count += 2;
success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset + count, s.Count - count), packet.packetId);
count += 2;
success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset + count, s.Count - count), packet.playerId);
count += 8;
success &= BitConverter.TryWriteBytes(new Span<byte>(s.Array, s.Offset, s.Count), count);
ArraySegment<byte> sendBuff = SendBufferHelper.Close(count);
if(success)
Send(sendBuff);
}
}
public override void OnDisconnected(EndPoint endPoint)
{
Console.WriteLine($"OnDisconnected : {endPoint}");
}
// 이동 패킷 (3,2 좌표로 이동하고 싶다!)
// 이동하고 싶다는 패킷의 번호가 15번이라고 하면
// 15, 3, 2 이런 식으로 데이터가 들어가서 서버쪽에다가 쏴주면 서버에서는 이 패킷을 까서 15번이니까 클라이언트는 이동하고 싶어하구나 하고
// 3, 2 좌표를 까서 이동하게 한다.
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}");
}
}
}
Server/Server/Program.cs
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using ServerCore;
namespace Server
{
class Program
{
static Listener _listener = new Listener();
static void Main(string[] args)
{
// DNS ( Domain Name System )
string host = Dns.GetHostName();
IPHostEntry ipHost = Dns.GetHostEntry(host);
IPAddress ipAddr = ipHost.AddressList[0];
IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777);
// ipAddre는 식당 주소, 7777은 식당 정문인지 후문인지 문의 번호
_listener.Init(endPoint, () => { return new ClientSession(); });
Console.WriteLine("Listening...");
while (true)
{
;
}
}
}
}
Server/Server/ClientSession.cs
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using ServerCore;
namespace Server
{
class Packet
{
public ushort size;
public ushort packetId;
}
class PlayerInfoReq : Packet // 클라에서 서버로 플레이어 정보를 알고 싶어
{
public long playerId;
}
class PlayerInfoOk : Packet // 서버에서 클라로 답변을 주는 거
{
public int hp;
public int attack;
}
public enum PacketID
{
PlayerInfoReq = 1,
PlayerInfoOk = 2,
}
class ClientSession : PacketSession
{
public override void OnConnected(EndPoint endPoint)
{
Console.WriteLine($"OnConnected : {endPoint}");
// Packet packet = new Packet() { size = 100, packetId = 10 };
// ArraySegment<byte> openSegment = SendBufferHelper.Open(4096);
// byte[] buffer = BitConverter.GetBytes(packet.size);
// byte[] buffer2 = BitConverter.GetBytes(packet.packetId);
// Array.Copy(buffer, 0, openSegment.Array, openSegment.Offset, buffer.Length);
// Array.Copy(buffer2, 0, openSegment.Array, openSegment.Offset + buffer.Length, buffer2.Length);
// ArraySegment<byte> sendBuff = SendBufferHelper.Close(buffer.Length + buffer2.Length);
// Send(sendBuff);
Thread.Sleep(5000);
Disconnect();
}
public override void OnRecvPacket(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;
switch((PacketID)id)
{
case PacketID.PlayerInfoReq:
{
long playerId = BitConverter.ToInt64(buffer.Array, buffer.Offset + count);
count += 8;
Console.WriteLine($"PlayerInfoReq: {playerId}");
}
break;
}
Console.WriteLine($"RecvPacketId: {id}, Size: {size}");
}
public override void OnDisconnected(EndPoint endPoint)
{
Console.WriteLine($"OnDisconnected : {endPoint}");
}
public override void OnSend(int numOfBytes)
{
Console.WriteLine($"Transferred bytes: {numOfBytes}");
}
}
}