[소켓프로그래밍 - Session]에서 서버와 클라이언트 사이의 통신 Session을 구현하였는데 서버-클라이언트간의 패킷 통신같은 경우 받은 데이터를 패킷으로 조립하는 과정이 필요하다.
물론 사용할 때마다 패킷을 조립하는 부분을 구현해도 되지만 코드의 중복이나 구현시간을 고려하면 미리 PacketSession이라는 Session의 한 종류를 구현해 놓는게 더 효율적이다.
Session을 구현한 TCP는 스트림 지향적인 프로토콜로 TCP는 데이터를 ByteStream으로 처리한다. ByteStream으로 처리한다는 것은 메세지의 경계를 구분하지 않는다는 것을 의미한다. 이로인해 아래 그림과 같은 상황이 발생할 수 있다.
데이터를 보내는 From
은 초록색x3
-파란색x2
-빨간색x2
를 보냈지만 데이터를 받는 To
는 초록색x2
-초록색x1, 파란색x2
-빨간색x2
또는 초록색x1
-초록색x2, 파란색x1
-파란색x1, 빨간색x2
와 같이 색깔의 순서는 같지만 같은 색깔이 한번에 Receive된다는 보장이 없다.
하지만, 색깔의 순서가 같다는 점을 이용해 앞으로 들어오는 메세지의 크기를 패킷의 처음 데이터로 입력한다면 From
이 To
에게 보내는 데이터를 메세지 단위로 구분할 수 있다.
먼저 패킷에 필수적으로 포함되는 정보는 다음과 같다.
[Packet Size(2 Byte)] [Packet ID(2 Byte)] [Data0...] ...
public abstract class PacketSession : Session
{
public static readonly int HeaderSize = 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);
if (buffer.Count < dataSize)
break;
// 패킷이 조립 가능하다.
OnRecvPacket(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 OnRecvPacket(ArraySegment<byte> buffer);
}
PacketSession은 Session을 상속받는 구조로 Receive시에 실행될 OnRecv를 구현하고 이후 PacketSession을 상속받는 컨텐츠 Session에서 Packet을 Receive받을 때의 동작을 구현하도록 추상 메소드인 OnRecvPacket을 두었다.
버퍼에 들어온 데이터가 패킷 하나 이상 들어올 수 있고 2개 이상의 패킷이 한번에 들어올 수 있기 때문에 여러 상황들에 대한 처리가 이뤄저야 한다.
// 최소한 헤더는 파싱할 수 있는가?
if (buffer.Count < HeaderSize)
break;
패킷의 처음 2 Byte는 전체 패킷의 사이즈가 들어와야 하기 때문에 2 Byte보다 더 적은 데이터가 들어왔을 때를 처리한다.
// 패킷이 완전체로 도작했는가?
ushort dataSize = BitConverter.ToUInt16(buffer.Array, buffer.Offset);
if (buffer.Count < dataSize)
break;
하나의 패킷이 덜 들어왔을 때를 처리해준다.
// 패킷이 조립 가능하다.
OnRecvPacket(new ArraySegment<byte>(buffer.Array, buffer.Offset, dataSize));
processLen += dataSize;
buffer = new ArraySegment<byte>(buffer.Array, buffer.Offset + dataSize, buffer.Count - dataSize);
dataSize
는 패킷시작부터 마지막 비트까지의 전체길이를 의미하기 때문에 부분 배열의 범위는 [Offset - Offset+dataSize]
이다. OnRecvPacket을 발생시키면 하나의 패킷처리가 끝났으므로 Buffer의 위치를 수정한다.