Server programming

04_06_Job Queue_패킷 모아 보내기

devRiripong 2023. 5. 16.
반응형

패킷 모아 보내기는 컴퓨터 네트워킹에서 여러 패킷을 모아 하나의 패킷으로 보내는 방법입니다. 이 과정은 많은 작은 패킷을 보내는 경우에 특히 네트워크 효율성을 높이고 오버헤드를 줄일 수 있습니다.

이 문맥에서 주요 아이디어는 패킷을 대기열(_pendingList)에 추가하는 작업과 패킷을 보내는 작업을 분리하는 것입니다

 

밀어 넣는 작업과 보내는 작업이랑 분리를 해서 모이면 Send를 하는 거.

모으는 작업을 서버단인 Session에서 할 수 도 있고 컨텐츠단인 GameRoom에서 할 수도 있다. 여기서는 컨텐츠단에서 하는 것을 살펴보자.

1.패킷 모아 보내기

1_1. 밀어 넣는 작업과 보내는 작업이랑 분리

GameRoom에

List<ArraySegment<byte>> _pendingList = new List<ArraySegment<byte>>();

를 생성하고,

GameRoom의 Broadcast에서는 _pendingList에 추가 하는 작업만 해준다.

public void Broadcast(ClientSession session, string chat)
{
    S_Chat packet = new S_Chat();
    packet.playerId = session.SessionId;
    packet.chat = $"{chat} I am {packet.playerId}";
    // 지금 사용하고 있는 인터페이스는 byte의 ArraySegment로 추출을 해야 되니까 
    ArraySegment<byte> segment = packet.Write();

    _pendingList.Add(segment);
    // N ^ 2
    //    foreach (ClientSession s in _sessions)
    //        s.Send(segment); 
}

1_2. 모인 애들 전송하는 작업

모여있는 애들을 전송하는 작업도 해줘야 한다.

Server의 Program에서

1_2_1. while에서 아무것도 안하고 있었는데 0.25초마다 Flush 해준다.

class Program
{
    static Listener _listener = new Listener();
    public static GameRoom Room = new GameRoom(); 

    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 SessionManager.Instance.Generate(); });
        Console.WriteLine("Listening...");

        while (true)
        {
              Room.Push(() => Room.Flush());
              Thread.Sleep(250);         
	}
    }

1_2_2. GameRoom에 Flush를 만들어 준다.

public void Flush()
{
    // N ^ 2
    foreach (ClientSession s in _sessions)
        s.Send(_pendingList);

		Console.WriteLine($"Flushed {_pendingList.Count} items");
    _pendingList.Clear();
}

몇개를 모았는지 로그도 찍어준다.

 

1_2_3. List<ArraySegment<byte>> _pendingListSend하는 함수는 아직 안만들었으니까 ServerCoreSession에 만든다.

RegisterSend이후 OnSendCompleted에서 에러 발생 예방을 위해 예외 코드도 넣어준다.

public void Send(List<ArraySegment<byte>> sendBuffList)
{
    if (sendBuffList.Count == 0)
        return; 

    lock (_lock)
    {
        foreach(ArraySegment<byte> sendBuff in sendBuffList)
            _sendQueue.Enqueue(sendBuff);

        if (_pendingList.Count == 0)
            RegisterSend();
    }
}

2.테스트

2_1. 클라이언트 로그를 통해 몇개를 처리 했는지 추적

하기 위해 Session의 PacketSession의 OnRecv에

packetCount 변수를 선언하고

OnRecvPacket 후 packetCount의 수를 늘려주고,

로그를 찍어 packetCount를 출력한다.

public sealed override int OnRecv(ArraySegment<byte> buffer)
{
    int processLen = 0;
    int packetCount = 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));  // 패킷이 해당하는 영역을 다시 찝어서 넘겨 줄거야
        packetCount++; 

        processLen += dataSize;
        buffer = new ArraySegment<byte>(buffer.Array, buffer.Offset + dataSize, buffer.Count - dataSize); // 바뀐 애는 다음 부분을 찝어주는 애가 된다. 
    }

    if(packetCount > 1)
        Console.WriteLine($"패킷 모아보내기 : {packetCount}");

    return processLen;
}

2_2. 기존에 로그가 많으니 DummyClientPacketHandler에 가서 로그를 삭제한다.

class PacketHandler
{
    public static void S_ChatHandler(PacketSession session, IPacket packet)
    {
        S_Chat chatPacket = packet as S_Chat;
        ServerSession serverSession  = session as ServerSession;    

        //if(chatPacket.playerId == 1)
        //    Console.WriteLine(chatPacket.chat);
    }
}

2_3. 버퍼사이즈 늘려 준다.

실행하면 서버는 100개 보내고 있지만 클라는 100개를 못받고 있다.

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

    RecvBuffer _recvBuffer = new RecvBuffer(65535);
public class SendBufferHelper
{
    public static ThreadLocal<SendBuffer> CurrentBuffer = new ThreadLocal<SendBuffer>(() => { return null;  });
    // 전역이지만 나의 쓰레드에서만 고유하게 사용할 수 있는 전역

    public static int ChunkSize { get; set; } = 66535 * 100;

이렇게 늘려주고 실행을 하면 서버도 클라이언트도 100개씩 받고 있다.

100번에 걸쳐가지고 보내던 거를 이제는 모아가지고 한번에 보내는 거.

500명으로 해도 잘 된다.

3.GameRoom에서 하는 일

클라가 요청한 작업 뿐만 아니라 NPC, 몬스터, 펫, 스킬의 움직임도 담당한다.

마스터가 필요

Main에서 클라이언트의 요청을 담은 Room을 Flush하는 역할을 했는데 서버도 마찬가지로 Flush해 갱신해주는 작업이 필요하다.

유저들이 보낸 패킷 뿐만 아니라 AI등의 작업도 잡큐 _pendingList넣어야 한다.

반응형

'Server programming' 카테고리의 다른 글

05_01_유니티 연동_유니티 연동#1  (0) 2023.05.17
04_07_Job Queue_JobTimer  (0) 2023.05.17
04_05_Job Queue_JobQuene#2  (0) 2023.05.16
04_04_JobQueue_JobQuene#1  (0) 2023.05.07
04_03_Job Queue_Command 패턴  (0) 2023.05.05

댓글