using ServerCore;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace DummyClient
{
internal class Program
{
class GameSession : Session
{
public override void OnConnected(EndPoint endPoint)
{
Console.WriteLine($"OnConnected : {endPoint}");
/*
// 임의 수정 파트
Console.WriteLine("Client에게 보낼 메시지를 입력하십시오: ");
string userMessage = Console.ReadLine();
byte[] sendBuff = Encoding.UTF8.GetBytes(userMessage);
Send(sendBuff);
Thread.Sleep(1000);
Disconnect();
*/
for (int i = 1; i <= 5; i++)
{
// 보낸다
byte[] sendBuff = Encoding.UTF8.GetBytes($"Hello World! {i}");
Send(sendBuff);
}
}
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 Server] {recvData}");
}
public override void OnSend(int numOfBytes)
{
Console.WriteLine($"Transferred bytes: {numOfBytes}");
}
}
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)
{
Thread.Sleep(1000);
}
}
}
}
using System.Net;
using System.Net.Sockets;
using System.Text;
using ServerCore;
namespace Server
{
class GameSession : Session
{
public override void OnConnected(EndPoint endPoint)
{
Console.WriteLine($"OnConnected : {endPoint}");
/*
// 임의 수정 파트
Console.WriteLine("Client에게 보낼 메시지를 입력하십시오: ");
string userMessage = Console.ReadLine();
byte[] sendBuff = Encoding.UTF8.GetBytes(userMessage);
Send(sendBuff);
Thread.Sleep(1000);
Disconnect();
*/
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 Data] {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
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(); });
Console.WriteLine("Listening.... ");
while (true)
{
}
}
}
}
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
{
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);
}
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);
}
void OnConnectCompleted(object sender, SocketAsyncEventArgs args)
{
if(args.SocketError == SocketError.Success)
{
Session session = _sessionFactory.Invoke();
// 연결한 소켓(clinet)
session.Start(args.ConnectSocket);
session.OnConnected(args.RemoteEndPoint);
}
else
{
Console.WriteLine($"OnConnectCompleted Fail : {args.SocketError}");
}
}
}
}
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
{
public class Listener
{
Socket _listenSocket;
Func<Session> _sessionFactory;
public void init(IPEndPoint endPoint, Func<Session> sessionFactory)
{
_listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
_sessionFactory += sessionFactory;
// 문지기 교육
_listenSocket.Bind(endPoint);
// 영업 시작
// Backlog : 최대 대기 수
_listenSocket.Listen(10);
// --사실 이 콜백 동작방식이 이해가 잘 안가는데 여기만 이해하면 어찌될듯
// 클라이언트가 연결되면 콜백방식으로 OnAcceptComplete가 실행
SocketAsyncEventArgs args = new SocketAsyncEventArgs();
args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAccpetComplete);
// 직접 등록해줘서 펜딩이 일어나지 않는다면 if문에서 처리될거고
// 펜딩이 일어난다면 바로 위에서 콜백방식으로 실행됨
RegisterAccept(args);
}
void RegisterAccept(SocketAsyncEventArgs args)
{
// 재사용하기때문에 이전것이 남아있으면 안되므로 null로 초기화
args.AcceptSocket = null;
bool pending = _listenSocket.AcceptAsync(args);
// 빨리 처리되어 펜딩이 일어나지 않는다면
if (pending == false)
OnAccpetComplete(null, args);
}
void OnAccpetComplete(object obj, 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);
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http.Headers;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace ServerCore
{
public abstract class Session
{
Socket _socket;
int _disconnectd = 0;
// Aysnc 횟수가 늘어날수록 시간이 오래걸리므로(부하가 큼) Queue를 통해 쌓아놨다가 한번에 처리
object _obj = new object();
Queue<byte[]> _sendQueue = new Queue<byte[]>();
// send할 정보들을 모아서 보내기 위한 list
List<ArraySegment<byte>> _pendingList = new List<ArraySegment<byte>>();
// send 할때마다 _sendArgs를 만들면 매우 부담이 높기에 따로 뺌(재사용)
SocketAsyncEventArgs _sendArgs = new SocketAsyncEventArgs();
SocketAsyncEventArgs _recvArgs = new SocketAsyncEventArgs();
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);
public void Start(Socket socket)
{
_socket = socket;
_recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnReceiveCompleted);
// Accept는 받으면 받은 socket 정보가 args에 남는데
// recv는 없어서 이렇게 따로 설정해주는듯
_recvArgs.SetBuffer(new byte[1024], 0, 1024);
_sendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnSendCompleted);
RegisterReceive();
}
public void Send(byte[] sendBuff)
{
// 여러 스레드에서 동시에 호출 될 수 있으므로 lock 걺
lock (_obj)
{
_sendQueue.Enqueue(sendBuff);
// 이미 send가 진행 중이라면 RegisterSend 하지 않음
if (_pendingList.Count == 0)
RegisterSend();
}
}
public void Disconnect()
{
if (Interlocked.Exchange(ref _disconnectd, 1) == 1)
return;
OnDisconnect(_socket.RemoteEndPoint);
_socket.Shutdown(SocketShutdown.Both);
_socket.Close();
}
#region 네트워크 통신
void RegisterSend()
{
// sendQueue에 넣은 데이터를 모두 빼서 _pendingList에 모음
while (_sendQueue.Count > 0)
{
byte[] buff = _sendQueue.Dequeue();
_pendingList.Add(new ArraySegment<byte>(buff, 0, buff.Length));
}
// BufferList의 경우 각 byte buffer를 add하는 식으로 하면 이상하게 작동
// 반드시 = 연산으로 리스트를 한번에 집어넣어야 함
// BufferList에 sendQueue 내용을 모두 넣어 Aysnc를 걸면
// BufferList에 있는 내용을 한번에 보내게 됨
_sendArgs.BufferList = _pendingList;
bool pending = _socket.SendAsync(_sendArgs);
if (pending == false)
OnSendCompleted(null, _sendArgs);
}
void OnSendCompleted(object sender, SocketAsyncEventArgs args)
{
lock (_obj)
{
if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
{
// SendAysnc가 처리되는 사이에 또 다른 Send()가 있었다면
// 다시 RegisterSend() 실행
// 기존엔 sendQueue가 비어있을 경우 _pending = false(send 진행중이 아님)로 하여 줬음
// sendQueue가 빌때까지 하나씩 Dequeue하여 send를 진행하였기 때문
// 지금은 send 진행 여부를 _pendingList가 비어있냐로 확인 가능
// OnSendCompleted로 들어온 경우 sendQueue는 찼을지 몰라도(새로운 send)
// 기존 send는 마무리 되었으므로 _pendingList를 비워줌
// 사실 비슷하긴한데 비유하자면 현재 send는 박스에 한번에 모아서 전달하니 박스 내용물 있는지 여부로 전송 중 판단하고
// 기존 send는 물품 하나하나 보내니 send 여부를 판별하는 flag가 필요했던 것
// Recv는 RegisterReceive 계속 실행시켜서 recv를 기다리지만
// Send는 딱히 들어오는 걸 기다리는 건 아니니
// Send() 함수가 실행되면 처리가 시작됨
_sendArgs.BufferList = null;
_pendingList.Clear();
OnSend(_sendArgs.BytesTransferred);
if (_sendQueue.Count > 0)
RegisterSend();
}
else
{
Disconnect();
}
}
}
void RegisterReceive()
{
bool pending = _socket.ReceiveAsync(_recvArgs);
if (pending == false)
OnReceiveCompleted(null, _recvArgs);
}
void OnReceiveCompleted(object sender, SocketAsyncEventArgs args)
{
// BytesTransferred -> 몇 byte를 전송 받았는가
// 0인 경우 상대방이 연결을 끊은 것
if(args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
{
OnRecv(new ArraySegment<byte>(args.Buffer, args.Offset, args.BytesTransferred));
// accept와 다르게 if문 밖에서 호출하지 않는 이유는
// accpet는 연결 안되면 걍 버리고 다른 연결 기다리면 되는데
// 얘는 연결 상태로 에러 난거니까 다시 receive하면 안됨
RegisterReceive();
}
else
{
Disconnect();
}
}
#endregion
}
}
'C# > 네트워크 관련' 카테고리의 다른 글
SendBuffer (1) | 2024.11.14 |
---|---|
RecvBuffer (0) | 2024.11.10 |
Session #5 (send 방식 수정) (0) | 2024.10.27 |
Socket 실습 #4 (non-blocking Send) (0) | 2024.10.25 |
Socket 연결 실습 - 3 (2) | 2024.10.05 |