๐ ๋์๋๋ฅผ ์ฌ๋ฌ๊ฐ๋ฅผ ๋์
class Listener
{
Socket _listenerSocket;
Action<Socket> _onAcceptHandler;
public void Init(IPEndPoint endPoint, Action<Socket> onAcceptHandler)
{
//๋ฌธ์ง๊ธฐ
_listenerSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
_onAcceptHandler += onAcceptHandler;
//๋ฌธ์ง๊ธฐ ๊ต์ก
_listenerSocket.Bind(endPoint);
//์์
์์
//backlog : ์ต๋ ๋๊ธฐ์
_listenerSocket.Listen(10);
for(int i = 0; i<10;i++)
{
SocketAsyncEventArgs args = new SocketAsyncEventArgs();
args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);
RegisterAccept(args);
}
}
SocketAsyncEventArgs๋ฅผ ์ฌ๋ฌ๊ฐ ๋ง๋ค์ด ์ฃผ๊ณ , RegisterAccept๋ฅผ ์ฌ๋ฌ๋ฒ ์คํํ๋ค.
๐ ๋น๋๊ธฐ๋ ์ด๋ป๊ฒ ์ผ์ด๋ฌ๋๊ฐ?
์์ ์ ์ค๋ ๋์ ์์ฑ์ด ํด๋ต์ด๋ค.
OnAcceptCompleted์ Break Point๋ฅผ ๋๊ณ , ๋๋ฒ๊ทธ๋ฅผ ์คํ ํ ๊ฒฝ์ฐ ์ ์ฌ์ง๊ณผ ๊ฐ์ด '์ฃผ ์ค๋ ๋' ์ด์ธ์ ์๋ก์ด '์์
์ ์ค๋ ๋' ๊ฐ ๋ฐ์ํ๋ ๊ฒ์ ๋ณผ ์ ์๋ค. ์ด๋ ๋ฏ AcceptAsync ๋ฉ์๋์ ๋ฐ๋ผ SocketAsyncEventArgs์ ์ฝ๋ฐฑ ํจ์๋ ์๋ก์ด ์ค๋ ๋์์ ์คํ ๋๋ค๋ ๊ฒ์ ์ ์ ์๋ค.
OnAcceptCompleted ๋ถ๋ถ์ red Zone ์ฆ ๋ฉํฐ์ค๋ ๋๋ฅผ ์ผ๋ํ๊ณ ์ฝ๋ฉ์ ํด์ผํ๋ ๋ถ๋ถ์ผ๋ก ์๊ฐ์ ํ ์ํ์์ ์ฝ๋๋ฅผ ์์ฑํด์ผ ํ ๊ฒ์ด๋ค.
๐ Server์ Receive๋ฅผ ๋น๋๊ธฐ๋ก
using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
namespace ServerCore
{
class Session
{
Socket _socket;
public void Init(Socket socket)
{
_socket = socket;
SocketAsyncEventArgs recvArgs = new SocketAsyncEventArgs();
recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvCompleted);
recvArgs.SetBuffer(new byte[1024],0,1024);
RegisterRecv(recvArgs);
}
void RegisterRecv(SocketAsyncEventArgs args)
{
bool pending = _socket.ReceiveAsync(args);
if (pending == false)
OnRecvCompleted(null, args);
}
void OnRecvCompleted(object obj, SocketAsyncEventArgs args)
{
if(args.BytesTransferred != 0 && args.SocketError==SocketError.Success)
{
try
{
string recvData = Encoding.UTF8.GetString(args.Buffer, args.Offset, args.BytesTransferred);
Console.WriteLine($"[From Client] {recvData}");
RegisterRecv(args);
}
catch(Exception e)
{
Console.WriteLine($"OnRecvCompleted Failed {e}");
}
}
else
{
//TODO Disconect
}
}
}
}
์์ ์์
์์ ํ๋ Accept๋ฅผ ํ๋ ๋ถ๋ถ๊ณผ ๋น์ทํ๋ค.
SocketAsyncEventArgs ์ ์ธ, Completed ์ถ๊ฐ, RegisterAsync๋ฅผ ์คํํด์ฃผ๋ Init
ReceiveAsync์ฒ๋ฆฌ๋ฅผ ํด์ฃผ๋ RegisterRecv
Completed์ ์ถ๊ฐ ๋ OnRecvCompleted
๋ค๋ฅธ ์ ์ด๋ผ๊ณ ํ๋ฉด, Args์ Data๋ฅผ ๋ฐ์ ์ค Buffer๋ฅผ ์ถ๊ฐํ ์ ์ด๋ค.
๊ทธ๋ฆฌ๊ณ OnRecvCompleted ๋ถ๋ถ์์ args.BytesTransferred > 0 ๋ณด๋ค ํฐ ๊ฒฝ์ฐ์๋ง ์กฐ๊ฑด๋ฌธ์ ํต๊ณผํ๊ฒ ํ๋๋ฐ ๊ทธ ์ด์ ๋ Disconnect์ ๊ฐ์ ๊ฒ์ Receive๋ฅผ ํ๋ฉด 0 byte๋ฅผ ๋ฐ์ ์ ์๊ธฐ ๋๋ฌธ์ ์ด๋ ํต๊ณผ ์์ผ์ฃผ์ง ์๊ธฐ ์ํด์ ๋ฃ๋๋ค.
๐ ์์ Disconnect์ Send
public void Send(byte[] sendBuff)
{
_socket.Send(sendBuff);
}
public void Disconnect()
{
_socket.Shutdown(SocketShutdown.Both);
_socket.Close();
}
public void Disconnect()
{
if (Interlocked.Exchange(ref _disconnected, 1) == 1)
return;
_socket.Shutdown(SocketShutdown.Both);
_socket.Close();
}
๐ Send๋ฅผ ๋น๋๊ธฐ์์ผ๋ก
Send์ ๋น๋๊ธฐ์์ ๊ธฐ์กด ์์์ ํ๋ ๋ฐฉ์๊ณผ๋ ์กฐ๊ธ ์ฐจ์ด๊ฐ ์๋ค.
์๋ํ๋ฉด, ์์ ํ๋ Accept์ Receive์ ๊ฒฝ์ฐ Client์์ ์ ํธ๊ฐ ์ค๋ ๊ฒ์ ๋ฐ๋ ๊ฒ์ด๋ผ๋ฉด, Send๋ ๋ฐ๋๋ก Server์์ ์ ํธ๋ฅผ ๋ณด๋ด๋ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ด๋ค.
using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace ServerCore
{
class Session
{
Socket _socket;
int _disconnected = 0;
SocketAsyncEventArgs _sendArgs = new SocketAsyncEventArgs();
Queue<byte[]> sendQueue = new Queue<byte[]>();
bool _pending = false;
object _lock = new object();
public void Start(Socket socket)
{
_socket = socket;
SocketAsyncEventArgs recvArgs = new SocketAsyncEventArgs();
recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvCompleted);
recvArgs.SetBuffer(new byte[1024], 0, 1024);
_sendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnSendCompleted);
RegisterRecv(recvArgs);
}
#region ๋คํธ์ํฌํต์
public void Send(byte[] sendBuff)
{
lock(_lock)
{
sendQueue.Enqueue(sendBuff);
if (_pending == false)
RegisterSend();
}
}
void OnSendCompleted(object obj, SocketAsyncEventArgs args)
{
lock (_lock)
{
if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
{
try
{
_pending = false;
if (sendQueue.Count > 0)
RegisterSend();
else
_pending = false;
}
catch (Exception e)
{
Console.WriteLine($"OnSendCompleted Failed {e}");
}
}
else
{
Disconnect();
}
}
}
void RegisterSend()
{
_pending = true;
byte[] buff = sendQueue.Dequeue();
_sendArgs.SetBuffer(buff, 0, buff.Length);
bool pending = _socket.SendAsync(_sendArgs);
if (pending == false)
OnSendCompleted(null, _sendArgs);
}
public void Disconnect()
{
if (Interlocked.Exchange(ref _disconnected, 1) == 1)
return;
_socket.Shutdown(SocketShutdown.Both);
_socket.Close();
}
void RegisterRecv(SocketAsyncEventArgs args)
{
bool pending = _socket.ReceiveAsync(args);
if (pending == false)
OnRecvCompleted(null, args);
}
void OnRecvCompleted(object obj, SocketAsyncEventArgs args)
{
if(args.BytesTransferred != 0 && args.SocketError==SocketError.Success)
{
try
{
string recvData = Encoding.UTF8.GetString(args.Buffer, args.Offset, args.BytesTransferred);
Console.WriteLine($"[From Client] {recvData}");
RegisterRecv(args);
}
catch(Exception e)
{
Console.WriteLine($"OnRecvCompleted Failed {e}");
}
}
else
{
Disconnect();
}
}
}
#endregion
}
Send๋ฅผ ๋น๋๊ธฐ์์ผ๋ก ์์ฑํ๊ธฐ ์ํด์ ๊ธฐ์กด์ Accept์ Async์ ๋ฐฉ์์ ์ฌ์ฉํ๊ฒ ๋๋ฉด, SocketAsyncEventArgs๋ฅผ ์ฌ์ฌ์ฉ ํ ์ ์๊ณ Send๋ฅผ ํธ์ถ ๋ฐ์ ๋๋ง๋ค ์๋ก์ด ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํ ๋นํ๊ฒ ๋๋ค. ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ๋จผ์ SocketAsyncEventArgs๋ฅผ ์ธ์คํด์ค ๋ณ์๋ก ์ ์ธ์ ํ๊ณ ์ด๋ฅผ ์ด๊ธฐํํ๋ ๋ถ๋ถ์ Start ๋ฉ์๋์ ๋ฃ์ด์ฃผ์๋ค.
๊ทธ๋ฆฌ๊ณ Async ํจ์์ ๊ฒฝ์ฐ ์ค๋ ๋๋ฅผ ๋ค๋ฃจ๋ ๋ถ๋ถ์ด๊ธฐ ๋๋ฌธ์ ์ ์ ๋จ์์ ๋ช ๋ น์ ํ ๋ฟ ์๋๋ผ OS๋จ์์๋ ๋ช ๋ น์ ํ๊ฒ ๋๋ค. ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ๋ฆฌ์์ค ๋ํ ๋ง์ด ๋จน๊ฒ ๋๋๋ฐ, ๊ธฐ์กด์ Accept ์ Receive์ ๊ฒฝ์ฐ ๊ฐฏ์๋ฅผ ๋๋ ค๋ด์ผ ๋ช๊ฐ ๋์ง ์์์ง๋ง, Send์ ๊ฒฝ์ฐ ๋ง์ฝ์ ์ ์ ๊ฐ ์ฒ๋ช , ๋ง๋ช ์ด ๋์ด์ ๊ทธ ๋ชจ๋ ์ฌ๋๋ค์ด SendAsync๋ฅผ ํ๊ฒ ๋๋ค๋ฉด ์๋ฒ์ ๋ถํ๊ฐ ์์ฒญ๋๊ฒ ๋ค์ด์จ๋ค. ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ์ด๋ฅผ ์กฐ์ ํ๊ธฐ ์ํด์ ์ค์ ๋ก ์คํ๋๋ SendAsync๋ ํ๋์์ ์ ์งํ๋ฉด์ ๋ค๋ฅธ ์ค๋ ๋์์ Send๊ฐ ๋ค์ด์ค๋ฉด Send์ ์๊ท๋จผํธ ๊ฐ์ธ sendBuff๋ฅผ sendQueue์ ์ ์ฅํ์ฌ ๋์ค์ ์๋ก์ด SendAsync์์ ์ฒ๋ฆฌํด์ฃผ๊ฒ๋ ๋ง๋ค์ด์ฃผ์๋ค.
ํ์ง๋ง ์์ ๋ฐฉ๋ฒ์ ์ค์ ๋ก ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ค๊ณ ๋ณด๊ธฐ๋ ์ด๋ ต๋ค. ์ด์ฐจํผ ๊ฒฐ๊ตญ์ SendAsync๋ฅผ Send๊ฐ ๋ค์ด์จ ๋งํผ ์ํํด์ผ ํ๊ธฐ ๋๋ฌธ์ด๋ค. ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ๋ฒ์ ์๋์์ ์์๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
๐ Send๋ฅผ ์ข ๋ Compactํ๊ฒ
void RegisterSend()
{
_pending = true;
List<ArraySegment<byte>> list = new List<ArraySegment<byte>>();
while(_sendQueue.Count > 0)
{
byte[] buff = _sendQueue.Dequeue();
list.Add(new ArraySegment<byte>(buff, 0, buff.Length));
}
_sendArgs.BufferList = list;
bool pending = _socket.SendAsync(_sendArgs);
if (pending == false)
OnSendCompleted(null, _sendArgs);
}
๊ธฐ์กด์ ์๋ Dequeue ๋ถ๋ถ์ sendQueue์์ ๊ฐ๋ค์ ํ๋์ฉ ๋นผ์คฌ๋ค. ์ด๋ฒ์๋ BufferList๋ฅผ ์ด์ฉ ํ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ ์ด BufferList์ sendQueue ๋ด๋ถ์ ์๋ ๋ชจ๋ ๊ฐ๋ค์ ์ฎ๊ธธ ๊ฒ์ด๋ค. ๊ทธ๋ ๊ธฐ ๋๋ฌธ์ while๋ฌธ์ ๋๋ฉด์ sendQueue์ Count๊ฐ 0์ด ๋ ๋๊น์ง ์์์ buff ๋ฆฌ์คํธ๋ฅผ ๋์ด ๊ทธ์ชฝ์ ์ ์ฅ์ ํ ๊ฒ์ด๋ค.
์ ๋ฐ๋ก BufferList์ Add ํ์ง ์๊ณ , ์์์ BufferList๋ฅผ ๋๋๊ฐ?
ํ์ฌ List๋ ๋จ์ํ byteํ Buffer๋ฅผ ๋ฐ๊ณ ์์ง ์๋ค. ์ด๊ฒ์ BufferList ๋ํ ๊ฐ์๋ฐ, ArraySegment๋ ๋ฐฐ์ด ๋ด๋ถ์ ๋ถ๋ถ์ ๋ด๋ ๊ตฌ์กฐ์ฒด์ด๋ค. ๊ทธ๋ ๋ค๋ฉด ๊ธฐ์กด์ ์๋ byteํ List์๋ ๋ค๋ฅผ ์๋ ์๋ค๋ ๊ฒ์ด๊ณ , ์ค์ ๋ก ๋ค๋ฅด๋ค.
์ด BufferList์ ๊ฒฝ์ฐ '=' ์ฐ์ฐ์๋ฅผ ํตํด์๋ง ๊ฐ๋ค์ ์ ๋๋ก ๋ณต์ฌ ํ ์ ์๋ค. ๋ฐ๋ผ์ ์์ ๊ฐ์ด ์์์ BufferList๋ฅผ ๋๋ ์์
์ ํ๋ ๊ฒ์ด๋ค.
using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace ServerCore
{
class Session
{
Socket _socket;
int _disconnected = 0;
SocketAsyncEventArgs _sendArgs = new SocketAsyncEventArgs();
SocketAsyncEventArgs _recvArgs = new SocketAsyncEventArgs();
List<ArraySegment<byte>> _pendingList = new List<ArraySegment<byte>>();
Queue<byte[]> _sendQueue = new Queue<byte[]>();
bool _pending = false;
object _lock = new object();
public void Start(Socket socket)
{
_socket = socket;
_recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvCompleted);
_recvArgs.SetBuffer(new byte[1024], 0, 1024);
_sendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnSendCompleted);
RegisterRecv();
}
#region ๋คํธ์ํฌํต์
public void Send(byte[] sendBuff)
{
lock(_lock)
{
_sendQueue.Enqueue(sendBuff);
if (_pendingList.Count == 0)
RegisterSend();
}
}
void OnSendCompleted(object obj, SocketAsyncEventArgs args)
{
lock (_lock)
{
if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
{
try
{
_sendArgs.BufferList = null;
_pendingList.Clear();
Console.WriteLine($"Transferred bytes : {_sendArgs.BytesTransferred}");
if (_sendQueue.Count > 0)
RegisterSend();
}
catch (Exception e)
{
Console.WriteLine($"OnSendCompleted Failed {e}");
}
}
else
{
Disconnect();
}
}
}
void RegisterSend()
{
_pendingList.Clear();
while(_sendQueue.Count > 0)
{
byte[] buff = _sendQueue.Dequeue();
_pendingList.Add(new ArraySegment<byte>(buff, 0, buff.Length));
}
_sendArgs.BufferList = _pendingList;
bool pending = _socket.SendAsync(_sendArgs);
if (pending == false)
OnSendCompleted(null, _sendArgs);
}
public void Disconnect()
{
if (Interlocked.Exchange(ref _disconnected, 1) == 1)
return;
_socket.Shutdown(SocketShutdown.Both);
_socket.Close();
}
void RegisterRecv()
{
bool pending = _socket.ReceiveAsync(_recvArgs);
if (pending == false)
OnRecvCompleted(null, _recvArgs);
}
void OnRecvCompleted(object obj, SocketAsyncEventArgs args)
{
if(args.BytesTransferred != 0 && args.SocketError==SocketError.Success)
{
try
{
string recvData = Encoding.UTF8.GetString(args.Buffer, args.Offset, args.BytesTransferred);
Console.WriteLine($"[From Client] {recvData}");
RegisterRecv();
}
catch(Exception e)
{
Console.WriteLine($"OnRecvCompleted Failed {e}");
}
}
else
{
Disconnect();
}
}
}
#endregion
}
๐ SessionHandler
Handler๋ฅผ ์ถ๊ฐํ์ฌ, Engine๊ณผ ์ปจํ ์ธ ๋ฅผ ๋ถ๋ฆฌ์ํค์
Session์์์ ํ๋์ ํ callback์ ๋ด๋นํ๋ ๋ถ๋ถ์ธ SessionHandler๋ฅผ ๋ง๋ค์ด๋ณด์. ์ ์ Accept์ ๊ฒฝ์ฐ์๋ OnAcceptHandler๋ฅผ ๋ง๋ค์ด์ฃผ์ด์, Accept๊ฐ ์ผ์ด๋์ ์๋ฃ๋ ๊ฒฝ์ฐ์ callback์ผ๋ก ํ ์ผ์ ์คํํด์ฃผ๋ ๊ฒ๊ณผ ๊ฐ์ ๊ฒ์ด๋ค.
Handler๋ฅผ ๋ง๋ค์ด์ฃผ๋ ๋ฐฉ๋ฒ์๋ ๋๊ฐ์ง๊ฐ ์๋ค.
์ฒซ๋ฒ์งธ๋ class๋ฅผ ํ๋ ์ ์ธํ์ฌ ๊ฑฐ๊ธฐ์ Handler๋ฅผ ๋ชจ์์ ๊ด๋ฆฌํ๋ ๋ฒ
๋๋ฒ์จฐ๋ Session์ ์์๋ฐ๋ ๋ฐฉ๋ฒ์ด๋ค.
์ฌ๊ธฐ์๋ ๋๋ฒ์งธ ๋ฐฉ๋ฒ์ผ๋ก ํด๋ณด๊ฒ ๋ค.
namespace ServerCore
{
abstract class Session
{
public abstract void OnConnected(EndPoint endPoint);
public abstract void OnRecv(ArraySegment<byte> buffer);
public abstract void OnSend(int numOfBytes);
public abstract void OnDisconnect(EndPoint endPoint);
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace ServerCore
{
class GameSession : Session
{
public override void OnConnected(EndPoint endPoint)
{
Console.WriteLine($"OnConnected : {endPoint}");
byte[] sendBuff = Encoding.UTF8.GetBytes("Welcome to MMORPG Server !");
Send(sendBuff);
Thread.Sleep(1000);
Disconnect();
}
public override void OnDisconnect(EndPoint endPoint)
{
Console.WriteLine($"OnDisconnected : {endPoint}");
}
public override void OnRecv(ArraySegment<byte> buffer)
{
string recvData = Encoding.UTF8.GetString(buffer.Array, buffer.Offset, buffer.Count);
Console.WriteLine($"[From Client] {recvData}");
}
public override void OnSend(int numOfBytes)
{
Console.WriteLine($"Transferred bytes : {numOfBytes}");
}
}
class Program
{
static Listener _listener = new Listener();
static void Main(string[] args)
{
//DNS(Domain Name Service
string host = Dns.GetHostName();
IPHostEntry ipHost = Dns.GetHostEntry(host);
IPAddress ipAddr = ipHost.AddressList[0];
IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777);
_listener.Init(endPoint, () => { return new GameSession(); });
while (true)
{
;
}
}
}
}
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace ServerCore
{
class Listener
{
Socket _listenerSocket;
Func<Session> _sessionFactory;
public void Init(IPEndPoint endPoint, Func<Session> sessionFactory)
{
//๋ฌธ์ง๊ธฐ
_listenerSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
_sessionFactory += sessionFactory;
//๋ฌธ์ง๊ธฐ ๊ต์ก
_listenerSocket.Bind(endPoint);
//์์
์์
//backlog : ์ต๋ ๋๊ธฐ์
_listenerSocket.Listen(10);
for(int i = 0; i<10;i++)
{
SocketAsyncEventArgs args = new SocketAsyncEventArgs();
args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);
RegisterAccept(args);
}
}
void RegisterAccept(SocketAsyncEventArgs args)
{
args.AcceptSocket = null;
bool pending = _listenerSocket.AcceptAsync(args);
if (pending == false)
OnAcceptCompleted(null, args);
}
void OnAcceptCompleted(object sender, SocketAsyncEventArgs args)
{
if (args.SocketError == SocketError.Success)
{
Session session = _sessionFactory.Invoke();
session.Start(args.AcceptSocket);
session.OnConnected(args.AcceptSocket.RemoteEndPoint);
}
else
Console.WriteLine(args.SocketError.ToString());
RegisterAccept(args);
}
public Socket Accept()
{
return _listenerSocket.Accept();
}
}
}
์ฌ๊ธฐ์ ํ๋ ์๋ฌธ์ธ ์ ์ด์๋ ๋ถ๋ถ์ SessionFactory ๋ถ๋ถ์ด์๋๋ฐ, Func ๋๋ฆฌ์๋ฅผ ํตํด์ SessionFactory์ GameSession์ ๋์
ํ๋ ์ฐ์ฐ์ ํ์๋ค. ๊ทผ๋ฐ ์ด ๋ถ๋ถ์์ Session ํด๋์ค์ SessionFactory๊ฐ ๋ค์ด๊ฐ ์๊ฐ ์๋? ๋ผ๋ ์๊ฐ์ ํ๋๋ฐ,
์ถ์ํด๋์ค์ ๊ฒฝ์ฐ ์๋ Session์ผ๋ก ์ ์ธํด์ ์ฌ์ฉ ํ ์๊ฐ ์์ง๋ง, ์
์บ์คํ
์ ํตํด์๋ ์ฌ์ฉ์ด ๊ฐ๋ฅํ๋ค๋ ๊ฒ์ ์๊ฒ๋๋ค.