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 PacketSession : Session
{
public static readonly int HeaderSize = 2;
// sealed : 다른 클래스가 PacketSession을 상속받아 OnRecv를 Override하려는 경우 에러
// [size(2)][packetid(2)][ ... ][size(2)][packetid(2)][ ... ] ...
public sealed override int OnRecv(ArraySegment<byte> buffer)
{
// 내가 몇바이트를 처리했느냐
int processLen = 0;
while (true)
{
// 최소한 헤더는 파싱할 수 있는지 확인
if (buffer.Count < HeaderSize)
break;
// 패킷이 완전체로 도착했는지 확인
ushort dataSize = BitConverter.ToUInt16(buffer.Array, buffer.Offset);
if (buffer.Count < dataSize)
break;
// 여기까지 왔으면 패킷 조립가능
OnRecvPacket(new ArraySegment<byte>(buffer.Array, buffer.Offset, dataSize));
// dataSize만큼 처리했으므로 processLen 증가
processLen += dataSize;
// 처리했으니 처리 안된 부분으로 위치 옮김
buffer = new ArraySegment<byte>(buffer.Array, buffer.Offset + dataSize, buffer.Count - dataSize);
}
return 0;
}
public abstract void OnRecvPacket(ArraySegment<byte> buffer);
}
public abstract class Session
{
Socket _socket;
int _disconnectd = 0;
RecvBuffer _recvBuffer = new RecvBuffer(1024);
// Aysnc 횟수가 늘어날수록 시간이 오래걸리므로(부하가 큼) Queue를 통해 쌓아놨다가 한번에 처리
object _obj = new object();
Queue<ArraySegment<byte>> _sendQueue = new Queue<ArraySegment<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 int 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);
_sendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnSendCompleted);
RegisterReceive();
}
public void Send(ArraySegment<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)
{
ArraySegment<byte> buff = _sendQueue.Dequeue();
_pendingList.Add(buff);
}
// 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의 경우 Send() 함수가 실행되면 처리가 시작됨
_sendArgs.BufferList = null;
_pendingList.Clear();
OnSend(_sendArgs.BytesTransferred);
if (_sendQueue.Count > 0)
RegisterSend();
}
else
{
Disconnect();
}
}
}
void RegisterReceive()
{
_recvBuffer.Clean();
ArraySegment<byte> segment = _recvBuffer.WriteSegment;
_recvArgs.SetBuffer(segment.Array, segment.Offset, segment.Count);
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)
{
// Write 커서 이동
if (_recvBuffer.OnWrite(args.BytesTransferred) == false)
{
Disconnect();
return;
}
// 컨텐츠 쪽으로 데이터를 넘겨주고 얼마나 처리했는지 받는다.
int processLen = OnRecv(_recvBuffer.ReadSegment);
if (processLen < 0 || _recvBuffer.DataSize < processLen)
{
Disconnect();
return;
}
// Read 커서 이동
if(_recvBuffer.OnRead(processLen) == false)
{
Disconnect();
return;
}
// accept와 다르게 if문 밖에서 호출하지 않는 이유는
// accpet는 연결 안되면 걍 버리고 다른 연결 기다리면 되는데
// 얘는 연결 상태로 에러 난거니까 다시 receive하면 안됨
RegisterReceive();
}
else
{
Disconnect();
}
}
#endregion
}
}
using System.Net;
using System.Net.Sockets;
using System.Text;
using ServerCore;
namespace Server
{
class Packet
{
public ushort size;
public ushort id;
}
class GameSession : PacketSession
{
public override void OnConnected(EndPoint endPoint)
{
Console.WriteLine($"OnConnected : {endPoint}");
//Packet packet = new Packet() { size = 100, id = 10 };
//// 최대 얼마만큼의 크기를 사용할 것인가
//ArraySegment<byte> openSegment = SendBufferHelper.Open(4096);
//// 바이트 배열로 바꿔줌
//byte[] buffer = BitConverter.GetBytes(packet.size);
//byte[] buffer2 = BitConverter.GetBytes(packet.id);
//Array.Copy(buffer, 0, openSegment.Array, openSegment.Offset, buffer.Length);
//Array.Copy(buffer2, 0, openSegment.Array, openSegment.Offset + buffer.Length, buffer2.Length);
//ArraySegment<byte> sendBuff = SendBufferHelper.Close(buffer.Length + buffer2.Length);
//Send(sendBuff);
Thread.Sleep(5000);
Disconnect();
}
public override void OnRecvPacket(ArraySegment<byte> buffer)
{
// 하드코딩인데 나중에 고침
ushort size = BitConverter.ToUInt16(buffer.Array, buffer.Offset);
ushort id = BitConverter.ToUInt16(buffer.Array, buffer.Offset + 2);
Console.WriteLine($"ReceivePacketId : {id}, Size : {size}");
}
public override void OnDisconnect(EndPoint endPoint)
{
Console.WriteLine($"OnDisconnected : {endPoint}");
}
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 ServerCore;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace DummyClient
{
class Packet
{
public ushort size;
public ushort id;
}
class GameSession : Session
{
public override void OnConnected(EndPoint endPoint)
{
Console.WriteLine($"OnConnected : {endPoint}");
Packet packet = new Packet() { size = 4, id = 10 };
for (int i = 1; i <= 5; i++)
{
ArraySegment<byte> openSegment = SendBufferHelper.Open(4096);
// 바이트 배열로 바꿔줌
byte[] buffer = BitConverter.GetBytes(packet.size);
byte[] buffer2 = BitConverter.GetBytes(packet.id);
Array.Copy(buffer, 0, openSegment.Array, openSegment.Offset, buffer.Length);
Array.Copy(buffer2, 0, openSegment.Array, openSegment.Offset + buffer.Length, buffer2.Length);
ArraySegment<byte> sendBuff = SendBufferHelper.Close(packet.size);
Send(sendBuff);
}
}
public override void OnDisconnect(EndPoint endPoint)
{
Console.WriteLine($"OnDisconnected : {endPoint}");
}
public override int OnRecv(ArraySegment<byte> buffer)
{
string recvData = Encoding.UTF8.GetString(buffer.Array, buffer.Offset, buffer.Count);
Console.WriteLine($"[From Server] {recvData}");
return buffer.Count;
}
public override void OnSend(int numOfBytes)
{
Console.WriteLine($"Transferred bytes: {numOfBytes}");
}
}
internal 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)
{
Thread.Sleep(1000);
}
}
}
}
'C# > 네트워크 관련' 카테고리의 다른 글
Serialization #2 (0) | 2024.11.26 |
---|---|
Selialization #1 (0) | 2024.11.23 |
SendBuffer (1) | 2024.11.14 |
RecvBuffer (0) | 2024.11.10 |
Connector 및 기타 (0) | 2024.11.09 |