using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace ServerCore
{
// 왜 커넥터를 만들어서 사용할까
// 컨텐츠를 분할해서 만들 경우
// 메인은 하나로 작동하고
// 다른 서버와 통신을 하기 위해서는 커넥터 개념이 필요함
// 서버들끼리의 통신을 위해서
// 필수적으로 필요하다.
public class Connector
{
private Func<Session> _sessionFactory; // 세션을 생성하는 함수 (Session Factory)
public void Connect(IPEndPoint endPoint, Func<Session> sessionFactory)
{
// 소켓 생성 (TCP 통신)
Socket socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
_sessionFactory = sessionFactory; // 세션을 생성할 함수 저장
// 비동기 연결을 위한 SocketAsyncEventArgs 설정
SocketAsyncEventArgs args = new SocketAsyncEventArgs();
args.Completed += OnConnectCompleted; // 연결 완료 시 실행될 콜백 함수 지정
args.RemoteEndPoint = endPoint; // 서버 주소 설정
// 원하는 정보를 넘겨 줄 수 있음
args.UserToken = socket; // 연결할 소켓을 UserToken에 저장
RegisterConnect(args); // 비동기 연결 요청 실행
}
private void RegisterConnect(SocketAsyncEventArgs args)
{
// 소켓으로 변환해서 받음
// 커넥터도 여러명 받을 수 있기에 이벤트로 인자를 넘겨줌
Socket socket = args.UserToken as Socket; // UserToken에서 Socket 가져오기
if (socket == null) return;
// 비동기 연결 요청 실행
bool pending = socket.ConnectAsync(args);
// ConnectAsync()가 즉시 완료되면 콜백을 직접 실행
if (pending == false)
OnConnectCompleted(null, args);
}
private void OnConnectCompleted(object sender, SocketAsyncEventArgs args)
{
if (args.SocketError == SocketError.Success)
{
// 세션 생성 및 연결 시작
// 컨텐츠에서 요구한 방향으로 만듬
Session session = _sessionFactory.Invoke();
// 인자로 받아준 ConnectSocket통해서 연결
session.Start(args.ConnectSocket);
session.OnConnected(args.RemoteEndPoint);
}
else
{
Console.WriteLine($"OnConnectCompleted Fail: {args.SocketError}");
}
}
}
}
using ServerCore;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace DummyClient
{
class GameSession : Session
{
public override void OnConnected(EndPoint endPoint)
{
Console.WriteLine($"OnConnected: {endPoint}");
// 서버에 5번 메시지를 전송
for (int i = 0; i < 5; i++)
{
byte[] sendBuff = Encoding.UTF8.GetBytes($"Hello World! {i}");
Send(sendBuff);
}
}
public override void OnDisconnected(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 Server: {recvData}");
}
public override void OnSend(int numOfBytes)
{
Console.WriteLine($"Transferred bytes: {numOfBytes}");
}
}
class Program
{
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 new GameSession(); });
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 Server! {i}");
// int sendByte = 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);
}
}
}
}
socket.Connect()를 비동기 방식인 ConnectAsync()로 교체하여,Connector는 Listener와 반대 역할을 수행하는 비동기 연결 담당자입니다.Connector는 내부에서 Func<Session>으로 SessionFactory를 받아 사용하므로,Session.Start()로 비동기 수신을 시작하고, OnConnected()를 호출하여 로직을 트리거합니다.| 용어 | 설명 |
|---|---|
| Connector | 외부 서버에 비동기로 연결을 시도하는 구조 |
| Listener | 외부에서 들어오는 연결 요청을 수락하는 구조 |
| Session | 연결 후 데이터를 송수신하는 추상 클래스 |
| SessionFactory | 어떤 Session을 만들지 결정하는 델리게이트 함수 (Func<Session>) |
| ConnectAsync() | 비동기 연결을 수행하는 Socket 함수 |
| SocketAsyncEventArgs | 비동기 소켓 통신에 필요한 모든 정보가 담긴 구조체 |
| UserToken | 사용자가 원하는 정보를 저장하는 객체 필드 (보통 Socket 객체 저장) |
public class Connector
{
Func<Session> _sessionFactory;
public void Connect(IPEndPoint endPoint, Func<Session> sessionFactory)
{
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);
}
}
📌 주요 포인트
IPEndPoint는 연결할 서버 주소입니다.SocketAsyncEventArgs 객체를 준비합니다.RemoteEndPoint: 연결 대상UserToken: 연결할 소켓 객체Completed: 연결 완료 시 호출될 콜백 함수 (OnConnectCompleted)RegisterConnect()를 호출하여 실제 비동기 연결을 시도합니다.void RegisterConnect(SocketAsyncEventArgs args)
{
Socket socket = args.UserToken as Socket;
if (socket == null) return;
bool pending = socket.ConnectAsync(args);
if (pending == false)
OnConnectCompleted(null, args);
}
📌 주요 포인트
UserToken에 넣어두었던 Socket을 꺼내서 사용합니다.ConnectAsync()는 연결을 비동기적으로 수행합니다.OnConnectCompleted() 호출void OnConnectCompleted(object sender, SocketAsyncEventArgs args)
{
if (args.SocketError == SocketError.Success)
{
Session session = _sessionFactory.Invoke();
session.Start(args.ConnectSocket);
session.OnConnected(args.RemoteEndPoint);
}
else
{
Console.WriteLine($"OnConnectCompleted Fail: {args.SocketError}");
}
}
📌 주요 포인트
Func<Session>을 통해 Session 객체를 생성합니다.Start()를 호출하여 비동기 수신을 시작OnConnected()를 호출하여 접속 후 초기 로직 실행SocketError를 출력만 하고 종료합니다.class GameSession : Session
{
public override void OnConnected(EndPoint endPoint)
{
Console.WriteLine($"OnConnected : {endPoint}");
for (int i = 0; i < 5; i++)
{
byte[] sendBuff = Encoding.UTF8.GetBytes($"Hello World! {i}");
Send(sendBuff);
}
}
public override void OnRecv(ArraySegment<byte> buffer)
{
string recvData = Encoding.UTF8.GetString(buffer.Array, buffer.Offset, buffer.Count);
Console.WriteLine($"[From Server] {recvData}");
}
public override void OnSend(int numOfBytes)
{
Console.WriteLine($"Transferred bytes: {numOfBytes}");
}
public override void OnDisconnected(EndPoint endPoint)
{
Console.WriteLine($"OnDisconnected : {endPoint}");
}
}
📌 분석 포인트
[DummyClient]
→ Connector.Connect()
→ ConnectAsync()
→ 연결 완료 시 OnConnectCompleted()
→ Session 생성 및 Start()
→ OnConnected()
[GameSession]
→ OnConnected() → 메시지 송신
→ OnRecv() → 서버 응답 수신 처리
| 항목 | 설명 |
|---|---|
| Connector 역할 | 서버에 비동기 연결을 시도하고 세션을 시작 |
| Connect() | 세션 생성 방식 지정, 연결 설정 |
| RegisterConnect() | ConnectAsync() 호출로 비동기 연결 시작 |
| OnConnectCompleted() | 연결 성공 시 Session 생성 → Start → OnConnected |
| GameSession | 실제 통신 로직 구현, 연결 시 메시지 송신 등 |