Q1. 지난 시간 코드에서 반복되는 구간이 있음에도 스택 오버플로우를 크게 걱정 안해도 되는 이유는?
Q2. 이용자가 늘 것을 대비하여 Listener의 Init에서 어떤 수정을 해주었는가?
Q3. OnAcceptCompleted는 왜 위험존인가?
Q4. Session의 Start, RegisterRecv, OnRecvComplated가 어떤 흐름으로 recieve를 비동기로 움직이게 하고 있나요?
Q5. Listener에서 AcceptAsync가 완료 되면 args.AcceptSocket에 연결된 새로 만들어준 소켓을 뱉어 주고 있었다. Recieve의 경우는 이 작업이 없는 대신 무엇을 만들어줘야 하는가?
Q6. 이번 시간에 한 Recieve를 분리한 작업 과정을 말해 보세요
답
- 최대 대기수를 뒀기 때문에
- Listener의 Init에서 args가 여러개 생성되게 한다
- OnAcceptCompleted는 Main스레드와 별도의 스레드에서 동시에 실행되므로
- Start에서 한번 RegisterRecv(recvArgs); 등록을 해주고, RegisterRecv에서 pending이 아니면 OnRecvCompleted을 호출해 주고 pending이면 나중에 Recv가 끝났을 때 콜백으로 호출해 준다. OnRecvCompleted에서 일이 끝나면 다시 RegisterRecv(recvArgs); 를 해준다.
- Listener에선 AcceptAsync가 완료가 된면 args.AcceptSocket에다가 연결된 새로 만들어준 소켓을 뱉어 주고 있었는데 Recive의 경우는 SetBuffer를 이용해 Buffer를 만들어줘야 한다.
Recieve 뺴기 Session이라는 클래스를 만든다 -> Session 클래스 안에 Socket _socket을 선언 -> Init(Socket socket) 함수를 만든다 -> RegisterRecv와 OnRecvCompleted함수를 만든다 -> RegisterRecv()에서 _socket.RevieveAsync를 하면 SocketAsyncEventArgs를 인자로 받으니까 Init에 SocketAsyncEventArgs변수를 선언해 인스턴스화하고 RegisterRecv할 때 인자로 SocketAsyncEventArgs args를 받게 한다. RegisterRecv의 _slocket.ReceiveAsync의 인자로 args를 넣는다 -> Init에서 recvArgs.Completed에 OnRecvCompleted를 연결한다. OnRecvCompleted도 형식을 맞춰준다 -> 여기까지 Listener와 비슷해
-> Init에서 SetBuffer를 이용해 버퍼를 만든다.-> Init에서 RegisterRecieve(recvArgs)를 호출한다 -> RegisterRecv에 가서 _socket.ReceiveAsync(args)가 뱉어주는 pending 판정에 따라 OnRecvCompleted를 호출한다. -> OnRecvCompleted에서 데이터 크기랑 Socket에러 체크 하고 원래 Main에서 [받는다]에 해당하는 코드 넣어준다.일단 테스트니까 매개변수로 받은 SocketAsyncEventArgs args내용 string으로 받아 출력한다. 그리고 RegistRecv(args)를 마지막에 해준다. 실패 대비해서 try, catch로 감싸준다
-> Send는 일단 블로킹 버전으로 인터페이스만 맞춰준다 -> RegisterRecv, OnRecvCompleted는 내부에서만 하는 거니까 #region으로 감싸준다 -> Disconnect도 Shutdown이랑 Close만 하는 인터페이스를 만들어 준다 -> Program으로 돌아가서 OnAcceptHandler의 sendBuff부분만 남기고 [받는다, 보낸다, 쫒아낸다] 부분을 삭제한다. Listener가 성공적으로 Accept해서 OnAcceptHandler가 호출이 되면 OnAcceptHandler에서 Session을 new로 만들어 준다. session.Init()에 clientSocket을 넘겨준다. Session은 Socket 빼고 준비가 되있는건데 Init에 Socket socket을 넣어주면 연동이 이루어진거다. Init을 하면 RegisterRecv까지 호출이 되는거. Init이라 하면 실행되는 느낌이 없으니까 Session의 Init을 Start로 이름을 바꿔준다. 보내는 부분은 session.Send(sendBuff);를 넣어주면 된다. 1초 정도만 쉬다가 session.Disconnect로 쫒아낸다.
-> DummyClient에 가서 5번씩 보내게 수정하고, 메시지도 몇번째 보내는 애인지 나오게 한다. -> 실행하면 서버에서 잘 받고 있고, 클라이언트도 보내고 있는거 볼 수 있다.
-> Disconnect가 두번 들어가도 한번만 실행되게 하기 위해 Session에 int _disconnect=0를 선언한다. DisConnect에서 Interlocked.Exchange로 1로 바꾸고 뱉어 주는 값이 1이면 return하게 한다.
recieve랑 send하는 부분을 마찬가지로 비동기로 바꿔 볼거야.
void RegisterAccept(SocketAsyncEventArgs args)
{
args.AcceptSocket = null;
bool pending = _listenSocket.AcceptAsync(args);
**if (pending == false)** // 운 좋게 바로 클라이언트가 접속했을 경우
OnAcceptCompleted(null, args);
}
void OnAcceptCompleted(object sender, SocketAsyncEventArgs args)
{
if(args.SocketError == SocketError.Success) // 모든 게 잘 처리 됐다는 뜻
{
// 유저가 커넥트 요청 해서 Accept 했으면 여기서 뭔가를 해주고
// TODO
_OnAcceptHandler.Invoke(args.AcceptSocket);
}
else
Console.WriteLine(args.SocketError.ToString());
**RegistterAccept(args);** // 다음 아이를 위해서 또 한번 등록을 해주는 거
}
지난 시간에 했던 것 중에 pending이 계속 false가 되면 Register와 Completed가 재귀적으로 뺑뻉이를 면 스택 오버플로우가 발생할 수 있지 않을까 의문이 들 수 있다. 이론상으로는 가능하다 .
근데 pending이 false가 됐다는 건 이미 클라이언트가 커넥트 요청을 해서 바로 걔가 입장을 할 수 있는 상황이었을거야. 애당초 _listenSocket을 보면 _listenSocket.Listen(10); 이렇게 backlog라고 최대 대기수를 뒀기 때문에 현실적으로 다발적으로 pending이 계속 false만 뜨는 경우가 일어날 수 없다. 그래도 정 불안하다 싶으면 코드를 수정해야 한다.
두번째는 Listner의 Init에서
public void Init(IPEndPoint endPoint, Action<Socket> onAcceptHandler)
{
// 문지기(가 들고있는 휴대폰)
_listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); // TCP로 할 때 설정
_OnAcceptHandler += onAcceptHandler;
// 문지기 교육
_listenSocket.Bind(endPoint); // 식당 주소와 후문인지 정문인지 기입을 해준 것
// 영업 시작
// backlog : 최대 대기수
_listenSocket.Listen(10);
**SocketAsyncEventArgs args = new SocketAsyncEventArgs();
args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);**
RegistterAccept(args);
}
SocketAsyncEventArgs를 하나만 만들었었어. 문지기를 만들었는데 딱 하나만 만든 셈이 된다. 결국 유저들이 어마어마하게 몰린다면 처리하는게 조금 오래 걸릴 수 있으니까 게임이 대박 쳐서 동시 다발적으로 많은 유저들을 받을 수 있어야 한다고 가정을 하면 이 부분들 늘려주면 된다.
public void Init(IPEndPoint endPoint, Action<Socket> onAcceptHandler)
{
// 문지기(가 들고있는 휴대폰)
_listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); // TCP로 할 때 설정
_OnAcceptHandler += onAcceptHandler;
// 문지기 교육
_listenSocket.Bind(endPoint); // 식당 주소와 후문인지 정문인지 기입을 해준 것
// 영업 시작
// backlog : 최대 대기수
_listenSocket.Listen(10);
**for(int i = 0; i < 10; i++)**
{
SocketAsyncEventArgs args = new SocketAsyncEventArgs();
args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);
RegistterAccept(args);
}
}
이런 식으로 여러개를 걸어 두면 된다.
얘네들은 독립적인 거야. 한번 던져 두면 알아서 돌아가는 거다.
물고기가 너무 많으면 이런식으로 낚시대를 여러개 던져놔도 상관 없다.
또 하나 집고 넘어가면 Program의 Main을 보면
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, OnAcceptHandler);
Console.WriteLine("Listening...");
while (true)
{
;
}
}
Main thread는 while(true)를 계속 하고 있다.
그럼 OnAcceptCompleted라는 콜백 함수가 실행이 될 때 while(true)를 실행하고 있었는데OnAcceptCompleted 부분도 실행이 됐다는 얘기야 그러면 어떻게 알고 OnAcceptCompleted가 끼어든 것인지 이상할 수 있다.
여기에 break point를 잡고 실행을 해보자.
MainThread를 보면
while(true)에 잡혀 있는 걸 볼 수 있다.
딱히 task나 thread를 만든 기억이 없지만
**args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);**
이런식으로 AsyncEvntArgs를 넣어 준 경우 콜백 함수는 별도의 스레드에서 실행 되었다는 걸 알 수 있다.
만약에 OnAcceptCompleted 부분과 Main 부분 두개의 스레드에서 동시 다발적으로 같은 데이터를 건드린다고 하면? race conidition 문제가 일어난다.
OnAcceptCompleted이 부분은 이제 위험존이라 생각하고 항상 멀티스레드로 실행될 수 있다는 것을 염두해 두고 코딩을 해야 한다.
지금은 OnAcceptCompleted가 자기에 소속된 애들만 건드려서 아무런 문제가 없었지만 나중에 recieve나 send를 하거나 좀 더 복잡한 걸 하면 락을 걸거나 멀티 스레드에서 할 수 있는 동기화 문제를 해결하면서 코딩을 해야 한다.
여기까지가 _listen의 얘기였고 오늘 해야 할 것은 ****
Recieve하는 부분을 따로 빼주도록 할거야.
send는 다음 시간에
이전 시간 까지의 코드
Listener
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace ServerCore
{
internal class Listener
{
Socket _listenSocket;
Action<Socket> _OnAcceptHandler;
public void Init(IPEndPoint endPoint, Action<Socket> onAcceptHandler)
{
// 문지기(가 들고있는 휴대폰)
_listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); // TCP로 할 때 설정
_OnAcceptHandler += onAcceptHandler;
// 문지기 교육
_listenSocket.Bind(endPoint); // 식당 주소와 후문인지 정문인지 기입을 해준 것
// 영업 시작
// backlog : 최대 대기수
_listenSocket.Listen(10);
SocketAsyncEventArgs args = new SocketAsyncEventArgs();
args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);
RegistterAccept(args);
}
void RegistterAccept(SocketAsyncEventArgs args)
{
args.AcceptSocket = null;
bool pending = **_listenSocket.AcceptAsync(args);**
if (pending == false) // 운 좋게 바로 클라이언트가 접속했을 경우
OnAcceptCompleted(null, args);
}
void OnAcceptCompleted(object sender, SocketAsyncEventArgs args)
{
if(args.SocketError == SocketError.Success) // 모든 게 잘 처리 됐다는 뜻
{
// 유저가 커넥트 요청 해서 Accept 했으면 여기서 뭔가를 해주고
// TODO
**_OnAcceptHandler**.**Invoke(args.AcceptSocket);**
}
else
Console.WriteLine(args.SocketError.ToString());
RegistterAccept(args); // 다음 아이를 위해서 또 한번 등록을 해주는 거
}
public Socket Accept()
{
return _listenSocket.Accept();
}
}
}
Program
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ServerCore
{
class Program
{
static Listener _listener = new Listener();
static void OnAcceptHandler(Socket clientSocket)
{
try
{
// 받는다
byte[] recvBuff = new byte[1024];
int recvBytes = clientSocket.Receive(recvBuff);
string recvData = Encoding.UTF8.GetString(recvBuff, 0, recvBytes);
Console.WriteLine($"[FromClient] {recvData}");
// 보낸다
byte[] sendBuff = Encoding.UTF8.GetBytes("Welcome to MMORPG Server !");
clientSocket.Send(sendBuff);
// 쫒아낸다
clientSocket.Shutdown(SocketShutdown.Both);
clientSocket.Close();
}
catch(Exception e)
{
Console.WriteLine(e);
}
}
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, OnAcceptHandler);
Console.WriteLine("Listening...");
while (true)
{
;
}
}
}
}
ServerCore에 Session이라는 클래스를 하나 만들어 준다
뭐가 필요할지 생각해 보면 OnAcceptHandler에서 Listener가 손님을 영입을 했으면 소켓을 뱉어주는 부분이 있을텐데 그 부분을 이어서 만들어 주면 된다.
OnAcceptHandler(Socket clientSocket) 이렇게 OnAcceptHandler에 clientSocket을 넘겨주고, 얘를 이용해서 받고, 보내는 작업을 했으니까 Session안에는 소켓이 일단 있어야 한다. Socket을 일단 넣어주자.
그리고 Init을 만들어 주고 Listener에서 했던 방식대로 필요한 것들을 연결해주자
namespace ServerCore
{
internal class Session
{
Socket _socket;
public void Init(Socket socket)
{
_socket = socket;
}
}
}
Recieve는
namespace ServerCore
{
class Program
{
static Listener _listener = new Listener();
static void OnAcceptHandler(Socket clientSocket)
{
try
{
**// 받는다**
**byte[] recvBuff = new byte[1024];**
**int recvBytes = clientSocket.Receive(recvBuff);
string recvData = Encoding.UTF8.GetString(recvBuff, 0, recvBytes);
Console.WriteLine($"[FromClient] {recvData}");**
이부분 인데 Accept처럼 비동기 방식으로 해주면 된다.
비동기 방식으로 한다는 것은 두 단계로 나뉜다.
RegisterRecv(). OnRecvCompleted()를 만들어 주자.
using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
namespace ServerCore
{
internal class Session
{
Socket _socket;
public void Start(Socket socket)
{
_socket = socket;
SocketAsyncEventArgs recvArgs = new SocketAsyncEventArgs();
recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvCompleted);
recvArgs.SetBuffer(new byte[1024], 0, 1024);
// Listener에선 AcceptAsync가 완료가 된면 args.AcceptSocket에 연결된 새로 만들어준 소켓을 뱉어 주고 있었는데 Recive의 경우는 SetBuffer를 이용해 Buffer를 만들어줘야 한다.
// 3번째 버전 사용. 바이트 배열, offset 0부터, 사이즈는 1024. 경우에 따라 쪼개서 사용할 경우 offset 0 아닐 수 있어.
//recvArgs.UserToken = this; 이렇게 정보를 넣어 줄 수도 있는데 굳이 안해도 된다.
RegisterRecv(recvArgs);
}
public void Send(byte[] sendBuff) // 일단은 블로킹 버전으로 인터페이스만 맞춰준
{
_socket.Send(sendBuff);
}
public void Disconnect() // OnAcceptHandle의 쫒아낸다에 해당하는 내용을 넣어준다
{
_socket.Shutdown(SocketShutdown.Both);
_socket.Close();
}
#region 네트워크 통신
void RegisterRecv(SocketAsyncEventArgs args)
{
bool pending = _socket.ReceiveAsync(args);
if (pending == false)
OnRecvCompleted(null, args);
}
void OnRecvCompleted(object sender, SocketAsyncEventArgs args)
{
// 상대방이 연결 끊거나 할 때 0으로 올 수도 있어.
if(args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
{
// TODO
try
{
string recvData = Encoding.UTF8.GetString(args.Buffer, args.Offset, args.BytesTransferred);
Console.WriteLine($"[FromClient] {recvData}");
RegisterRecv(args);
}
catch (Exception e)
{
Console.WriteLine($"OnRecvCompletedFailed {e}");
}
}
else
{
Disconnect();
}
}
#endregion
}
}
이렇게 session의 기본을 완성하고
Program으로 돌아가서 수정이 필요하다.
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ServerCore
{
class Program
{
static Listener _listener = new Listener();
static void OnAcceptHandler(Socket clientSocket)
{
try
{
// 받는다
byte[] recvBuff = new byte[1024];
int recvBytes = clientSocket.Receive(recvBuff);
string recvData = Encoding.UTF8.GetString(recvBuff, 0, recvBytes);
Console.WriteLine($"[FromClient] {recvData}");
// 보낸다
byte[] sendBuff = Encoding.UTF8.GetBytes("Welcome to MMORPG Server !");
clientSocket.Send(sendBuff);
// 쫒아낸다
clientSocket.Shutdown(SocketShutdown.Both);
clientSocket.Close();
}
catch(Exception e)
{
Console.WriteLine(e);
}
}
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, OnAcceptHandler);
Console.WriteLine("Listening...");
while (true)
{
;
}
}
}
}
에서 다음과 같이 코드 수정한다.
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ServerCore
{
class Program
{
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 e)
{
Console.WriteLine(e);
}
}
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, OnAcceptHandler);
Console.WriteLine("Listening...");
while (true)
{
;
}
}
}
}
이렇게 수정하고
DummyClinent도
한번만 보내는 게 아니게
// 보낸다
for(int i=0; i<5; i++)
{
byte[] sendBuff = Encoding.UTF8.GetBytes($"Hello World! {i}");
int sendBytes = socket.Send(sendBuff);
}
5번 보내게 수정해준다.
실행해 보면 잘 보내고 잘 받고 있는 걸 볼 수 있다.
여기까지 비동기로 만들었다.
여기서 조금 더 꼼꼼하게 살펴보기 위해서 세션 안에는 멀티 스레드로 실행될 수 있다는 걸 염두하고 만들어야 한다고 했어.
OnRecvCompleted는 내부의 것만 사용해서 문제가 없어 보이고, Disconnect를 만들었는데 만약 동시 다발적으로 Disconnect를 한다거나 같은 애가 두번하면 어떻게 될지 궁금해
ServerCore의 Program의 OnAcceptHandler에서 Disconnect를 두번 입력해보자. 실행해 보면
뭔가 문제가 일어나는 걸 볼 수 있다.
Disconnect를 한번만 하게 맞춰줘야 하는데
Session에
int _disconnect = 0;
flag를 하나 둔다.
public void Disconnect()
{
if (Interlocked.Exchange(ref _disconnect, 1) == 1)
return;
_socket.Shutdown(SocketShutdown.Both);
_socket.Close();
}
이렇게 하고 실행해 보면
disconnect를 2번 했지만 문제 없이 잘 동작하는 거 볼 수 있다.
지난 시간에 한 Listen을 한 방식이랑 비슷했다.
특이사항은 Session의 Start에서 RegisterRecv를 한번 등록한 다음에
OnRecvCompleted가 실행이 되어야지만 실제로 한번 받는 동작이 끝나야지만 RegisterRecv(args) 다음 등록을 하는 이 흐름은 중요하다 했어.
public void Start(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 sender, SocketAsyncEventArgs args)
{
if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success) // 모든 게 잘 처리 됐다는 뜻
{
// TODO
try
{
string recvData = Encoding.UTF8.GetString(args.Buffer, args.Offset, args.BytesTransferred);
Console.WriteLine($"[FromClient] {recvData}");
**RegisterRecv(args);**
}
catch(Exception e)
{
Console.WriteLine($"OnRecvCompletedFailed {e}");
}
}
else
{
Disconnect();
}
}
recv는 간단하지만 send는 간단하지 않아. Send는 예약을 하는게 아니라서. 그 때 마다 원하는 타이밍에 send를 호출하는 것이기에 까다롭다.
작업 결과 코드
Session
using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
namespace ServerCore
{
internal class Session
{
Socket _socket;
int _disconnect = 0;
public void Start(Socket socket)
{
_socket = socket;
SocketAsyncEventArgs recvArgs = new SocketAsyncEventArgs();
recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvCompleted);
recvArgs.SetBuffer(new byte[1024], 0, 1024);
// Listener에선 AcceptAsync가 완료가 된면 args.AcceptSocket에 연결된 새로 만들어준 소켓을 뱉어 주고 있었는데 Recive의 경우는 SetBuffer를 이용해 Buffer를 만들어줘야 한다.
// 3번째 버전 사용. 바이트 배열, offset 0부터, 사이즈는 1024. 경우에 따라 쪼개서 사용할 경우 offset 0 아닐 수 있어.
//recvArgs.UserToken = this; 이렇게 정보를 넣어 줄 수도 있는데 굳이 안해도 된다.
RegisterRecv(recvArgs);
}
public void Send(byte[] sendBuff) // 일단은 블로킹 버전으로 인터페이스만 맞춰준
{
_socket.Send(sendBuff);
}
public void Disconnect()
{
if (Interlocked.Exchange(ref _disconnect, 1) == 1)
return;
_socket.Shutdown(SocketShutdown.Both);
_socket.Close();
}
#region 네트워크 통신
void RegisterRecv(SocketAsyncEventArgs args)
{
bool pending = _socket.ReceiveAsync(args);
if (pending == false)
OnRecvCompleted(null, args);
}
void OnRecvCompleted(object sender, SocketAsyncEventArgs args)
{
// 상대방이 연결 끊거나 할 때 0으로 올 수도 있어.
if(args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
{
// TODO
try
{
string recvData = Encoding.UTF8.GetString(args.Buffer, args.Offset, args.BytesTransferred);
Console.WriteLine($"[FromClient] {recvData}");
RegisterRecv(args);
}
catch (Exception e)
{
Console.WriteLine($"OnRecvCompletedFailed {e}");
}
}
else
{
Disconnect();
}
}
#endregion
}
}
Program
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ServerCore
{
class Program
{
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 e)
{
Console.WriteLine(e);
}
}
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, OnAcceptHandler);
Console.WriteLine("Listening...");
while (true)
{
;
}
}
}
}
DummyClient
namespace DummyClient
{
internal class Program
{
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은 식당 정문인지 후문인지 문의 번호 }
// 식당 주소 찾는 부분은 똑같을 거야.
while(true)
{
// 휴대폰 설정
Socket socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
try
{
// 문지기한테 입장 문의
socket.Connect(endPoint);
Console.WriteLine($"Connected To {socket.RemoteEndPoint.ToString()}");
**// 보낸다
for(int i=0; i<5; i++)
{
byte[] sendBuff = Encoding.UTF8.GetBytes($"Hello World! {i}");
int sendBytes = socket.Send(sendBuff);
}**
// 받는다
byte[] recvBuff = new byte[1024];
int recvBytes = socket.Receive(recvBuff);
string recvData = Encoding.UTF8.GetString(recvBuff, 0, recvBytes);
Console.WriteLine($"[From Server] {recvData}");
// 나간다
socket.Shutdown(SocketShutdown.Both);
socket.Close();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
Thread.Sleep(100);// 너무 많이 보내도 문제 있으니
}
}
}
}
'Server programming' 카테고리의 다른 글
02_08_네트워크 프로그래밍_Session #3_BufferList (0) | 2023.04.04 |
---|---|
02_07_네트워크 프로그래밍_Session #2_Send (0) | 2023.04.04 |
02_05_네트워크 프로그래밍_Listener (0) | 2023.04.04 |
02_04_네트워크 프로그래밍_소켓 프로그래밍 입문 #2 (0) | 2023.04.03 |
02_03_네트워크 프로그래밍_소켓 프로그래밍 입문 #1 (0) | 2023.04.03 |
댓글