소켓
OS 내부에는 "프로토콜 스택"이 있는데, Transport 계층이라 불리는 TCP 또는 UDP는 Applicaton 계층에서 송수신할 데이터를 받아서 해당 프로토콜에 맞게 작업을 하고, 인터넷에서 데이터를 송수신하기 때문에 헤더 정보를 다시 Internet 계층인 IP로 묶고, IP 프로토콜이 속한 계층 아래에 있는 데이터 링크 계층인 랜카드의 Mac 주소를 헤더 정보로 다시 묶은 다음 최종적으로 물리 계층에 있는 통신 케이블 같은 장비를 통해서 데이터를 송수신한다.
노드(노트북, 휴대폰 ..등의 전자기기)들이 서로 데이터를 송수신하기 위해서 "소켓"이라는 것을 이용한다.
즉, 소켓을 만든다는 의미는 데이터를 어떻게 송수신할 건지에 대한 프로토콜 종류(TCP, UDP), 송신 측과 수신 측의 IP 주소, 통신 상태, 포트 번호와 같은 정보들을 만드는 작업이다.
멀티 스레드 환경에서 Lock
싱글 스레드 환경이라면 ClientA와 ClientD가 동시에 데이터를 송신해도 하나의 스레드가 모든 작업을 처리하기 때문에 데이터를 송신하는 동안 다른 클라이언트가 공유자원에 접근하여 데이터를 변경하는 상황이 발생하지 않는다.
그러나 멀티 스레드 환경에서는 여러 클라이언트가 동시에 데이터를 송신하는 경우 공유자원은 언제든지 변할 가능성이 있기 때문에 서버에서는 그 공유자원을 사용하는 동안에 변경되어서는 안 됨을 보장받기 위해서 Lock을 사용해야 한다.
예를 들자면, "welcome" 이라는 데이터를 전송 큐에 넣어서 송신하려는 과정에서 갑자기 다른 스레드에서 전송 큐에 다른 데이터를 넣는다던지 그러한 상황이 발생할 수 있다.
멀티 스레드 환경에서는 각 클라이언트가 동시에 데이터를 송신할 때 서버가 이를 안전하게 처리하기 위해 동기화 메커니즘이 필요하다. 가장 일반적으로 사용되는 동기화 방법으로, 공유 자원을 접근할 때 Lock을 획득하고 접근이 끝나면 Lock을 해제하는 방식을 살펴보자
Queue<byte[]> sendQueue = new Queue<byte[]>();
bool sendPending = false;
object Lock = new object();
public void Send(byte[] sendBuffer)
{
lock (Lock)
{
sendQueue.Enqueue(sendBuffer);
if (!sendPending)
RegisterSend();
}
}
void RegisterSend()
{
sendPending = true;
byte[] buffer = sendQueue.Dequeue();
sendArgs.SetBuffer(buffer, 0, buffer.Length);
bool pending = socket.SendAsync(sendArgs);
if (!pending)
OnSendCompleted(null, sendArgs);
}
void OnSendCompleted(object sender, SocketAsyncEventArgs args)
{
lock (Lock)
{
if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
{
try
{
sendPending = false;
if (sendQueue.Count > 0)
RegisterSend();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
else
{
Disconnect();
}
}
}
데이터를 송신할 때 Send 메소드가 호출되며, 락(Lock)이 설정된다.
락이 설정된 동안 큐에 송신할 데이터를 추가하고, "sendPending(데이터 전송 작업이 진행 중인지 여부를 나타내는 플래그)" 상태를 확인하여 필요시 "RegisterSend(데이터 전송)" 메소드를 호출한다.
여기서 락이 해제되는 시점은 lock 블록의 끝에서 자동으로 해제되는데, RegisterSend(); 메소드를 호출해도 락이 바로 해제되는 것은 아니고 lock 블록 내에서 호출된 메소드가 끝날 때까지 락이 유지된다.
비동기 송신을 하고 있는데, 데이터 전송이 끝나면 지정된 콜백 함수(OnSendCompleted)를 불러서 "sendPending" 플래그를 수정함으로써 새로운 전송 작업 처리를 시작할 수 있다.
콜백 함수에도 락을 걸어주는 이유는 여러 스레드가 동시에 콜백 함수에 접근하는 경우 공유 자원을 수정할 수 있기 때문이다.
멀티 스레드 환경에서 lock이 필요한 이유는 알겠는데, 실제로 lock이 필요한 상황을 경험해 본 적이 없으니까 크게 와닿지 않는다..
전송할 데이터가 많은 경우 이를 한 번에 묶어서 전송하는 방식으로 개선
List<ArraySegment<byte>> pendingList = new List<ArraySegment<byte>>();
public void Send(byte[] sendBuffer)
{
lock (Lock)
{
sendQueue.Enqueue(sendBuffer);
if (pendingList.Count == 0)
RegisterSend();
}
}
void RegisterSend()
{
while (sendQueue.Count > 0)
{
byte[] buffer = sendQueue.Dequeue();
pendingList.Add(new ArraySegment<byte>(buffer, 0, buffer.Length));
}
sendArgs.BufferList = pendingList;
bool pending = socket.SendAsync(sendArgs);
if (!pending)
OnSendCompleted(null, sendArgs);
}
void OnSendCompleted(object sender, SocketAsyncEventArgs args)
{
{
if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
{
try
{
pendingList.Clear();
Console.WriteLine($"Transferred bytes :{sendArgs.BytesTransferred}");
if (sendQueue.Count > 0)
RegisterSend();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
else
{
Disconnect();
}
}
}
pendingList.Count == 0 설정하는 이유에 대한 GPT 답변
- 초기 전송:
pendingList.Count == 0
가true
로 평가되면, 이는 현재 진행 중인 전송 작업이 없음을 의미합니다. 이는 이번이 첫 번째 데이터 전송이거나 이전 전송 작업이 완료되어 목록이 비워졌음을 나타냅니다. - 전송 작업 시작:
전송 작업이 진행 중이 아닌 경우 (pendingList.Count == 0
),RegisterSend
메서드를 호출하여 데이터 전송을 시작합니다.
RegisterSend
의 상세 기능
- 대기 목록 비우기
RegisterSend
메서드는pendingList
를 비우고sendQueue
의 모든 데이터를pendingList
로 이동시킵니다. - 전송 시작
그런 다음sendArgs
를pendingList
를 사용하도록 설정하고, 비동기 전송을socket.SendAsync(sendArgs)
를 사용하여 시작합니다. 전송 작업이 대기 상태가 아닌 경우(동기적으로 완료된 경우),OnSendCompleted
를 수동으로 호출합니다
결론
if (pendingList.Count == 0)
라는 조건은 새로운 전송 작업을 시작할 시점을 제어하는 데 중요합니다. 이 조건은 현재 다른 전송 작업이 진행 중인 경우 새로운 전송이 시작되지 않도록 보장합니다. 이를 통해 여러 전송이 동시에 시작되어 발생할 수 있는 경쟁 상태, 데이터 손상 또는 다른 동기화 문제를 방지합니다. 이 조건을 사용하여 각 데이터가 큐에 추가된 순서대로 전송되도록 하여 순차적이고 질서 있는 전송 프로세스를 유지합니다.
'게임개발 > 게임서버' 카테고리의 다른 글
네트워크 기초 (0) | 2024.04.22 |
---|---|
동기화 (2) | 2024.04.18 |
멀티스레드 InterLocked (1) | 2024.04.18 |
메모리 배리어 (0) | 2024.04.18 |
멀티 쓰레드 프로그래밍 (0) | 2024.04.17 |