C#

채팅 테스트 #2

monstro 2025. 1. 21. 21:00
728x90
반응형

이번 포스트에서는 지난 포스트에 이어 클라이언트 단계에서 채팅 프로그램을 만들겠습니다.

 

1) Connector

public class Connector
{
    
	.
	.
	.

    // 연결을 수행하는 함수
    public void Connect(IPEndPoint endPoint, Func<Session> sessionFactory, int connectCount = 1)
    {
        for (int i = 0; i < connectCount; i++)
        {
            // 서버 소켓 생성
            Socket socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            _sessionFactory = sessionFactory;

            SocketAsyncEventArgs args = new SocketAsyncEventArgs();
            args.Completed += OnConnectCompleted;
            args.RemoteEndPoint = endPoint;         
            args.UserToken = socket;                

            RegisterConnect(args);
        }
    }

	.
	.
	.
    
}

 

우선, 기존의 Connector의 Connect 함수를 수정하여

Connect를 connectCount만큼 수행하도록 변경하였습니다.

 

 

2) PacketFormat

    class PacketFormat
    {
        // {0} 패킷 등록
        public static string managerFormat =
@" using ServerCore;
using System;
using System.Collections.Generic;

class PacketManager
{{
    static PacketManager _instance = new PacketManager();
    public static PacketManager Instance {{ get {{ return _instance; }} }}

    PacketManager()
    {{
        Register();
    }}

	.
	.
	.
    
";
	
	.
	.
	.
    
    }

 

또 Client와 Server 별 자동으로 생성되는 PacketManager의 자동생성양식을 위와 같이 변경하였습니다.

프로퍼티인 Instance에서 PacketManager를 생성하여 리턴하고,

PacketManager의 생성자에서 Register를 수행하도록 수정하였습니다.

 

이제 클라이언트 단계로 넘어가보겠습니다.

3) 클라이언트의 PacketHandler

class PacketHandler
{
    // 인자는 어떤 세션에서 , 어떤 패킷이 조립되었는지를 의미함
    public static void S_ChatHandler(PacketSession session, IPacket packet)
    {
        S_Chat chatPacket = packet as S_Chat;
        ServerSession serverSession = session as ServerSession;

        Console.WriteLine(chatPacket.chat);
    }
}

 

클라이언트의 패킷의 경우, 로그를 찍는 작업을 수행합니다.

 

4) 클라이언트의 SessionManager

class SessionManager
{
    static SessionManager _session = new SessionManager();
    public static SessionManager Instance { get { return _session; } }

    List<ServerSession> _sessions = new List<ServerSession>();
    object _lock = new object();

    public ServerSession Generate()
    {
        lock (_lock)
        { 
            ServerSession session = new ServerSession();
            _sessions.Add(session);
            return session;
        }
    }

    public void SendForEach()
    {
        lock (_lock)
        {
            foreach (ServerSession eachSession in _sessions)
            { 
                C_Chat chatPacket = new C_Chat();
                chatPacket.chat = $"Hello Server !";
                ArraySegment<byte> segment = chatPacket.Write();

                eachSession.Send(segment);
            }
        }
    }
}

 

서버의 SessionManager와 비슷하게 클라이언트의 SessionManager도 동작합니다.

Generate에서는 새로운 ServerSession을 생성하고 List에 저장한 후 리턴합니다.

SendForEach에서는 챗 메세지를 다른 ServerSession에게 전송합니다.

 

5) ServerSession

class ServerSession : PacketSession
{
    public override void OnConnected(EndPoint endPoint)
    {
        Console.WriteLine($"OnConnected : {endPoint}");
    }

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

    public override void OnRecvPacket(ArraySegment<byte> buffer)
    {
        PacketManager.Instance.OnRecvPacket(this, buffer);
    }

    public override void OnSend(int numOfBytes)
    {
        
    }
}

 

Send의 경우, 이미 클라이언트의 SessionManager의 SendForEach에서 수행하므로 생략하였습니다.

 

최종적으로 PacketGenerator를 빌드한 후, bat 파일을 실행하여 

자동생성된 파일들로 파일 구성을 변경하였습니다.

이후 Client와 Server의 메인 코드를 변경하겠습니다.

 

6) Client

class Client
{
    static void Main(string[] args)
    {
        // DNS를 사용
        string host = Dns.GetHostName();
        IPHostEntry ipHost = Dns.GetHostEntry(host);
        IPAddress ipAddr = ipHost.AddressList[0];
        IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777);

        Connector connector = new Connector();
        connector.Connect(endPoint, () => { return SessionManager.Instance.Generate(); }, 10);

        while (true)
        {
            try
            {
                SessionManager.Instance.SendForEach();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }

            Thread.Sleep(250);
        }
    }
}

 

Client의 Main 함수에서는 총 10개의 ServerSession을 만들어 수행하도록 하겠습니다.

 

7) Server의 GameRoom

class GameRoom
{
    List<ClientSession> _sessions = new List<ClientSession>();
    object _lock = new object();

    public void Broadcast(ClientSession session, string chat)
    {
        S_Chat packet = new S_Chat();
        packet.playerID = session.clientSessionID;
        packet.chat = $"{chat} I am {packet.playerID}";
        ArraySegment<byte> segment = packet.Write();

        lock (_lock)
        {
            foreach (ClientSession eachSession in _sessions)
                eachSession.Send(segment);
        }
    }
	
	.
	.
	.
    
}

 

GameRoom에서는 ChatPacket의 Chat 프로퍼티를 위와 같이 설정하였습니다.

 

최종 실행결과는 다음과 같습니다.

 

문제없이 동작하는 것을 확인하였지만,

지금까지 작성한 과정에서 스레드의 양을 조금만 늘려도 부하가 발생하게 됩니다.

그 이유는 이어지는 포스트에서 자세히 알아보겠습니다.

728x90
반응형

'C#' 카테고리의 다른 글

JobQueue #1  (0) 2025.01.22
Command 패턴  (0) 2025.01.22
채팅 테스트 #1  (0) 2025.01.21
Packet Generator #6  (0) 2025.01.20
Packet Generator #5  (0) 2025.01.20