📄 Async Session
지난 글과 작동은 같지만, 버퍼를 성공적으로 송신하거나, 수신할 때를 다룹니다. 더욱 효율적인 송신 방법과 각 이벤트가 성공적으로 수행되면 Event를 통해 확인할 수 있도록 학습하였습니다.
📑 Session
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 abstract class Session
{
Socket mSocket;
int mIsDisconnected = 0; //현재 Disconnected 상태인가?
object mSendingLock = new object(); //Send Lock
Queue<byte[]> mSendQueue = new Queue<byte[]>(); //Send 버퍼를 담는 큐
List<ArraySegment<byte>> mSendPendingList;
/// <summary>
/// 비동기 통신 시 특정 행동을 수행하면 호출할 이벤트를 선언
/// </summary>
SocketAsyncEventArgs mSendArgs, mReceiveArgs;
public Session()
{
//Send를 성공적으로 수행했다면, OnSendCompleted를 실행하도록 설정
this.mSendArgs = new SocketAsyncEventArgs();
mSendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnSendCompleted);
mSendPendingList = new List<ArraySegment<byte>>();
//Receive를 성공적으로 수행했다면 OnReceiveCompleted를 실행하도록 설정
this.mReceiveArgs = new SocketAsyncEventArgs();
mReceiveArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnReceiveCompleted);
mReceiveArgs.SetBuffer(new byte[1024], 0, 1024); //1024배열크기를 0부터시작하여 1024개의 사이즈로 생성
}
/// <summary>
/// 소켓을 등록시키고, 활성화
/// </summary>
/// <param name="socket">사용할 소켓</param>
public void Start(Socket socket)
{
this.mSocket = socket;
//최초 1회 등록
RegisterReceive();
}
public abstract void OnConnected(EndPoint endPoint);
public abstract void OnDisconnected(EndPoint endPoint);
public abstract void OnReceive(ArraySegment<byte> buffer);
public abstract void OnSend(int numOfBytes);
/// <summary>
/// 목적지로 버퍼를 송신한다.
/// </summary>
/// <param name="sendBuff"></param>
public void Send(byte[] sendBuff)
{
lock (mSendingLock)
{
mSendQueue.Enqueue(sendBuff); //Send를 할 버퍼들을 SendQueue에 담는다.
if (mSendPendingList.Count == 0) { RegisterSend(); } //현재 대기중인 리스트가 비어있으면, Send를 한다.
}
}
/// <summary>
/// 목적지와의 연결을 해제한다.
/// </summary>
public void Disconnect()
{
//인터락을 사용하여 멀티스레드 환경에서 현재 disconnected인지 확인
//예상치 못한 환경에서 disconnect가 두번이상 호출될경우 리턴하도록 함
if (Interlocked.Exchange(ref mIsDisconnected, 1) == 1) { return; }
this.OnDisconnected(mSocket.RemoteEndPoint); //연결 해제 이벤트 호출
mSocket.Shutdown(SocketShutdown.Both); //연결 해제
mSocket.Close();
}
#region [은닉]네트워크 통신
/// <summary>
/// Send를 비동기로 등록하고 대기하도록 한다.
/// </summary>
private void RegisterSend()
{
while (mSendQueue.Count > 0) //송신 바이트큐가 빌때까지 송신대기열리스트에 모두 집어넣는다.
{
byte[] buffer = mSendQueue.Dequeue(); //큐에 삽입된 순서대로 차례대로 뽑는다(FIFO)
mSendPendingList.Add(new ArraySegment<byte>(buffer, 0, buffer.Length)); //대기열에 형식에 맞게(ArraySegment) 가공하여 삽입
}
mSendArgs.BufferList = mSendPendingList; //BufferList에 replace
bool isPending = mSocket.SendAsync(mSendArgs); //비동기 Send
if (isPending == false) //대기중이 아닌경우?
{
OnSendCompleted(null, mSendArgs);
}
}
/// <summary>
/// 송신이 성공적으로 수행되었다면 호출되는 이벤트
/// </summary>
private void OnSendCompleted(object sender, SocketAsyncEventArgs args)
{
lock (mSendingLock)
{
//전송된 바이트가 1이상이고, Success 상태라면? > 정상 통신
if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
{
try
{
mSendArgs.BufferList = null; //Send가 정상적으로 이루어졌기에 BufferList를 비워준다.
mSendPendingList.Clear(); //Clear을 통하여 대기열 큐를 비워준다.
this.OnSend(mSendArgs.BytesTransferred); //성공적으로 보내기를 했으니 이벤트 호출
if (mSendQueue.Count > 0) //송신을 완료한 시점에서 SendQueue에 새로운 버퍼가 대기중이라면, 송신하도록 한다.
{
RegisterSend();
}
}
catch (Exception e)
{
Console.WriteLine($"OnSendCompleted Failed {e.Message}");
}
}
else
{
Disconnect();
}
}
}
/// <summary>
/// Receive를 위한 비동기 등록 및 대기
/// </summary>
private void RegisterReceive()
{
bool isPending = mSocket.ReceiveAsync(mReceiveArgs);
if (isPending == false) //대기중이 아닌경우?
{
OnReceiveCompleted(null, mReceiveArgs);
}
}
/// <summary>
/// 수신을 성공적으로 수행하면 호출되는 이벤트
/// </summary>
private void OnReceiveCompleted(object sender, SocketAsyncEventArgs args)
{
//전송된 바이트가 1이상이고, Success 상태라면? > 정상 통신
if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
{
try
{
OnReceive(new ArraySegment<byte>(args.Buffer, args.Offset, args.BytesTransferred)); //성공적으로 수신을 하였다면, 이벤트 호출
RegisterReceive();
}
catch (Exception e)
{
Console.WriteLine($"OnReceiveCompleted Failed {e.Message}");
}
}
else
{
Disconnect();
}
}
#endregion
}
}
object mSendingLock = new object(); //Send Lock
Queue<byte[]> mSendQueue = new Queue<byte[]>(); //Send 버퍼를 담는 큐
List<ArraySegment<byte>> mSendPendingList;
- Send와 관련된 멤버변수들입니다.
- Lock을 사용하고, Send를 모아서 보내기 위한 큐와 현재 Send대기상태인 리스트가 있습니다.
SocketAsyncEventArgs mSendArgs, mReceiveArgs;
- 비동기 스레드에서 작업을 완료하면 호출하기위한 기능들을 선언하기위한 이벤트입니다.
public Session()
- 기본 생성자를 오버라이드하여 멤버변수들을 설정해줍니다.
public void Start(Socket socket)
- 외부에서 호출하여 소켓정보를 등록하고 활성화합니다.
public abstract void OnConnected(EndPoint endPoint);
public abstract void OnDisconnected(EndPoint endPoint);
public abstract void OnReceive(ArraySegment<byte> buffer);
public abstract void OnSend(int numOfBytes);
- 각각 연결, 연결해제, 패킷수신, 패킷전송시 호출될 이벤트입니다.
- 오버라이드를 하여 상속하는 여러 디자인에서도 동일한 호출을 하도록 합니다.
public void Send(byte[] sendBuff)
- 버퍼를 소켓정보의 목적지로 송신합니다.
private void RegisterSend()
- 목적지로 송신하기위한 비동기스레드를 활성화합니다.
- 큐에 있는 모든 바이트를 한번에 보내기위헤 BufferList를 사용합니다.
private void OnSendCompleted(object sender, SocketAsyncEventArgs args)
- Send가 성공적으로 수행되면, 호출되는 이벤트입니다.
- this.OnSend(mSendArgs.BytesTransferred);를 통하여 오버라이드한 이벤트를 호출합니다.
private void RegisterReceive()
- 수신을 위한 비동기 스레드를 활성화합니다.
private void OnReceiveCompleted(object sender, SocketAsyncEventArgs args)
- Receive가 성공적으로 수행되면, 호출하는 이벤트입니다.
- OnReceive(new ArraySegment<byte>(args.Buffer, args.Offset, args.BytesTransferred));를 통하여 오버라이드한 이벤트를 호출합니다.
📑 GameSession
public class GameSession : Session
{
public override void OnConnected(EndPoint endPoint)
{
Console.WriteLine($"{endPoint}연결 성공");
//클라이언트에게 송신
byte[] sendBuffer = Encoding.UTF8.GetBytes("Hello World From Server");
Send(sendBuffer);
Thread.Sleep(1000);
Disconnect();
}
public override void OnDisconnected(EndPoint endPoint)
{
Console.WriteLine($"{endPoint} 연결 해제");
}
public override void OnReceive(ArraySegment<byte> buffer)
{
string receiveData = Encoding.UTF8.GetString(buffer.Array, buffer.Offset, buffer.Count); //문자열로 획득
Console.WriteLine($"클라이언트로부터 받음: {receiveData}");
}
public override void OnSend(int numOfBytes)
{
Console.WriteLine($"{numOfBytes}바이트 전송 성공");
}
}
- Session을 상속하여 오버라이드할 함수들을 재정의하여 의도에 맞는 동작을 수행하도록 합니다.
'server > socket server' 카테고리의 다른 글
[C# 서버] RSA(공개키) 방식으로 통신 (0) | 2023.06.07 |
---|---|
[구글 프로토콜 버퍼] 패킷 한글 주석 인코딩 해결 (0) | 2023.05.18 |
[C# 서버] Async Listener (0) | 2023.01.17 |
[C# 서버] 소켓 프로그래밍 (0) | 2023.01.14 |
[C# 서버] Thread Local Storage (0) | 2023.01.12 |