using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ServerCore
{
public class RecvBuffer
{
// [ ][ ][ ][ ][ ][ ][ ][ ][ ][ ]
ArraySegment<Byte> _buffer;
// readPos == writePos면 들어온 data 없음
int _readPos; // 읽기 시작하는 부분
int _writePos; // 쓰기 시작하는 부분
public RecvBuffer(int bufferSize)
{
_buffer = new ArraySegment<byte>(new byte[bufferSize], 0, bufferSize);
}
// 처리되지 않은 데이터 크기
public int DataSize { get { return _writePos - _readPos; } }
// 남은 버퍼 사이즈
public int FreeSize { get { return _buffer.Count - _writePos; } }
// _buffer에서 _readPos 부분에서 시작하여 처리할 데이터 크기만큼 가져옴
public ArraySegment<byte> ReadSegment
{
get { return new ArraySegment<byte>(_buffer.Array, _buffer.Offset + _readPos, DataSize); }
}
// _buffer에서 _writePos 부분에서 시작하여 남은 버퍼 사이즈만큼 가져옴 (다음 Recv의 유효범위)
public ArraySegment<byte> WriteSegment
{
get { return new ArraySegment<byte>(_buffer.Array, _buffer.Offset + _writePos, FreeSize); }
}
// 계속 채우다보면 _readPos와 _writePos가 버퍼 사이즈 이상으로 밀리게 될 수 있으니
// 기존 데이터와 r,w의 위치를 앞으로 끌어당김
public void Clean()
{
int dataSize = DataSize;
if(dataSize == 0)
{
// _readPos와 _writePos가 같은 경우, 클라에서 보낸 모든 데이터를 처리한 경우
// 즉 처리할 데이터가 없는 경우 r,w를 맨앞으로 당김
// 남은 데이터가 없으면 복사하지 않고 커서 위치만 리셋
_readPos = _writePos = 0;
}
else
{
// 남은 데이터가 있으면 시작 위치로 복사
// 버퍼의 _readPos 위치부터 dataSize만큼 버퍼의 오프셋 부분 부터 복사
Array.Copy(_buffer.Array, _buffer.Offset + _readPos, _buffer.Array, _buffer.Offset, dataSize);
_readPos = 0;
_writePos = dataSize;
}
}
public bool OnRead(int numOfBytes)
{
// numOfBytes : 처리에 성공한 데이터 크기
// 처리 가능한 데이터 크기보다 더 큰 데이터를 처리할 수는 없으므로 false
if (numOfBytes > DataSize)
return false;
// numOfBytes 만큼 처리했으므로 _readPos 위치를 옮김
_readPos += numOfBytes;
return true;
}
public bool OnWrite(int numOfBytes)
{
// numOfBytes : 클라이언트로 부터 받은 데이터 크기
// 빈 공간보다 더 큰 데이터를 받을 수는 없으므로 false
if (numOfBytes > FreeSize)
return false;
// numOfBytes 만큼 데이터를 받았으므로 _writePos의 위치를 옮김
_writePos += numOfBytes;
return true;
}
}
}
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;
RecvBuffer _recvBuffer = new RecvBuffer(1024);
// 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 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(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의 경우 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
}
}
'C# > 네트워크 관련' 카테고리의 다른 글
PacketSession (5) | 2024.11.15 |
---|---|
SendBuffer (1) | 2024.11.14 |
Connector 및 기타 (0) | 2024.11.09 |
Session #5 (send 방식 수정) (0) | 2024.10.27 |
Socket 실습 #4 (non-blocking Send) (0) | 2024.10.25 |