이전과 다른 점은
서버에서 클라이언트의 요청을 단방향으로 처리하던 방식을(원래 이건 야매 방식이다)
Listener.cs 클래스를 별개로 관리하여,
SocketAsyncEventArgs() 함수를 통해 비동기 방식으로 요청을 처리하는 것이다.
또한 Session.cs 클래스를 추가하여
ServerCore.cs에 모두 뭉쳐 있던 통신 코드를 분산했다.
▼ ServerCore.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ServerCore
{
class ServerCore
{
static Listener _listener = new Listener();
static void OnAcceptHandler(Socket clientSocket)
{
try
{
Session session = new Session();
session.Start(clientSocket);
//보낸다.
byte[] sendBuff = Encoding.UTF8.GetBytes("Welcome to MMORPG server !!");
session.Send(sendBuff);
Thread.Sleep(1000);
session.Disconnect();
}
catch (Exception exception)
{
Console.WriteLine(exception);
}
}
public 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, 0127);
_listener.Init(endPoint, OnAcceptHandler);
Console.WriteLine("\n*** LUCA SERVER IS RUNNING ***");
Console.WriteLine("Listening...\n");
while (true)
{
}
}
}
}
▼ Listener.cs // 추가됨
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace ServerCore
{
class Listener
{
Socket _listenSocket;
Action<Socket> _onAcceptHandler;
public void Init(IPEndPoint endPoint, Action<Socket> onAcceptHandler)
{
//문지기
_listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
_onAcceptHandler += onAcceptHandler;
//문지기 교육
_listenSocket.Bind(endPoint);
//영업 시작
//Listen(backlog); backlog: 최대 대기수.
_listenSocket.Listen(10);
SocketAsyncEventArgs args = new SocketAsyncEventArgs();
args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);
RegisterAccept(args);
}
void RegisterAccept(SocketAsyncEventArgs args)
{
args.AcceptSocket = null; //중복값 방지 위해 밀어주기.
bool pending = _listenSocket.AcceptAsync(args); //비동기. 동시처리x
if (pending == false) //요청이 바로 올 경우 완료 처리.
OnAcceptCompleted(null, args);
}
void OnAcceptCompleted(object sender, SocketAsyncEventArgs args)
{
if (args.SocketError == SocketError.Success)
{
_onAcceptHandler.Invoke(args.AcceptSocket);
}
else
Console.WriteLine(args.SocketError.ToString());
RegisterAccept(args);
}
public Socket Accept()
{
return _listenSocket.Accept();
}
}
}
RegisterAccept()와 OnAcceptCompleted()가 반복적으로 실행되면서
클라이언트의 요청을 기다린다.
▼ DummyClient.cs // 이전과 동일
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace DummyClient
{
class DummyClient
{
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, 0127);
while (true)
{
//휴대폰 설정
Socket socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
try
{
//문지기한테 입장 문의
socket.Connect(endPoint); //endPoint(상대방 주소)와 연락 가능한지 묻기
Console.WriteLine($"Connected to {socket.RemoteEndPoint}");
//보낸다
byte[] sendBuff = Encoding.UTF8.GetBytes($"사용자{new Random().Next(20)}님이 입장하셨습니다.");
int sentBytes = socket.Send(sendBuff);
//받는다
byte[] receiveBuff = new byte[1024];
int receiveBytes = socket.Receive(receiveBuff);
string receiveData = Encoding.UTF8.GetString(receiveBuff, 0, receiveBytes);
Console.WriteLine($"[From Server] {receiveData}");
//나간다
socket.Shutdown(SocketShutdown.Both);
socket.Close();
}
catch (Exception exception)
{
Console.WriteLine(exception.ToString());
}
Thread.Sleep(500);
}
}
}
}
위의 ServerCore.cs에서 일부를 떼어 와서
Session이라는 새로운 클래스에서 다룬다.
(위의 ServerCore 코드는 이미 떼어 온 상태의 코드이다.)
▼ Session.cs // 추가됨
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ServerCore
{
class Session
{
Socket _socket;
int _disconnected = 0;
public void Start(Socket socket)
{
_socket = socket;
SocketAsyncEventArgs receiveArgs = new SocketAsyncEventArgs();
receiveArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnReceiveCompleted);
receiveArgs.SetBuffer(new byte[1024], 0, 1024);
RegisterReceive(receiveArgs);
}
public void Send(byte[] sendBuff)
{
_socket.Send(sendBuff);
}
public void Disconnect()
{
//세션 내에서는 멀티스레드 환경도 생각해 주어야 함.
//두 명이 동시에 또는 한 명이 여러 번 접속 종료할 수 있음. 이를 방지 -> Interlocked 사용.
if (Interlocked.Exchange(ref _disconnected, 1) == 1)
return;
_socket.Shutdown(SocketShutdown.Both);
_socket.Close();
}
#region 네트워크 통신
void RegisterReceive(SocketAsyncEventArgs args)
{
bool pending = _socket.ReceiveAsync(args);
if (pending == false)
OnReceiveCompleted(null, args);
}
void OnReceiveCompleted(object sender, SocketAsyncEventArgs args)
{
// 접속 종료 또는 오류 시 0이 나올 수도 있음.
if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
{
try
{
string receiveData = Encoding.UTF8.GetString(args.Buffer, args.Offset, args.BytesTransferred);
Console.WriteLine($"[From Client] {receiveData}");
RegisterReceive(args);
}
catch (Exception exception)
{
Console.WriteLine($"OnReceiveCompleted Failed. : {exception}");
}
}
else
{
}
}
#endregion
}
}
▼ DummyClient.cs // for문으로 5개씩 받는다.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace DummyClient
{
class DummyClient
{
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, 0127);
while (true)
{
//휴대폰 설정
Socket socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
try
{
//문지기한테 입장 문의
socket.Connect(endPoint); //endPoint(상대방 주소)와 연락 가능한지 묻기
Console.WriteLine($"Connected to {socket.RemoteEndPoint}");
//보낸다
for (int i=0; i<5; i++)
{
byte[] sendBuff = Encoding.UTF8.GetBytes($"\n사용자{i}님이 입장하셨습니다.");
int sentBytes = socket.Send(sendBuff);
}
//받는다
byte[] receiveBuff = new byte[1024];
int receiveBytes = socket.Receive(receiveBuff);
string receiveData = Encoding.UTF8.GetString(receiveBuff, 0, receiveBytes);
Console.WriteLine($"[From Server] {receiveData}");
//나간다
socket.Shutdown(SocketShutdown.Both);
socket.Close();
}
catch (Exception exception)
{
Console.WriteLine(exception.ToString());
}
Thread.Sleep(500);
}
}
}
}