(C#) Session [2]
포스트
취소

(C#) Session [2]

지난 글(Session [1])에 이어서 이번에는 Send를 비동기로 만들어준다.

💻 코드

Send는 여러 클라이언트가 _socket.SendAsync를 접근했을 때 느리고 부하가생긴다.

이 때문에 생성 후 사용보다 미리 만들어 놓고 재사용하는 것이 더 좋을 것이다.

  • 여러 클라이언트들이 접근하므로 쓰레드 사용을 위해 _lock을 생성해준다.
  • 버퍼 값을 저장해 한번에 보내줘야 부하가 덜하기 때문에 이를 관리해줄 Queue도 생성해준다.
  • args도 재사용을 위해 멤버변수로 선언해준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
object _lock = new object();

Queue<byte[]> _sendQueue= new Queue<byte[]>();
bool _pending = false;
SocketAsyncEventArgs _sendArgs = new SocketAsyncEventArgs();

public void Start()
{
    _sendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnSendCompleted);
}

// Send 등록
void RegisterSend()
{

}

// Send 성공
void OnSendCompleted(object sender, SocketAsyncEventArgs args)
{

}


메시지를 전송하기 위해 제일 첫번째로 오는 메소드다.

만약 이곳에서 버퍼를 생성하고(args.SetBuffer()) 한다면

버퍼 생성 -> 등록을 반복하기 때문에 이것만으로 여러 클라이언트가 몰린다면 부하가 많이 생길수 있다.

그러므로 받은 버퍼를 Queue에 담아주고 한번 등록할 때 한꺼번에 처리하는 방식으로 진행한다.

Send()는 여러 클라이언트가 접근하기 때문에 lock을 걸어주도록 한다.

1
2
3
4
5
6
7
8
9
10
11
12
public void Send(byte[] sendBuff)
{
    // 계속 바이트를 만들어 주기에는 부담이 너무 크다.
    // 그러므로 큐로 보내줄 바이트들을 받아준 다음
    // 보낼준비가 되면 그때 한번에 보낸다.
    lock (_lock)
    {
        _sendQueue.Enqueue(sendBuff);
        if (_pending == false)
            RegisterSend();
    }
}


Send를 등록한다면 다음 Send가 못들어오도록 _pendingtrue로 전환해준다.

이곳에서 Queue로 받은 버퍼를 Dequeue하여 버퍼를 뽑고 생성 후 SendAsync를 진행한다.

1
2
3
4
5
6
7
8
9
10
void RegisterSend()
{
    _pending = true;
    byte[] buff = _sendQueue.Dequeue();
    _sendArgs.SetBuffer(buff, 0, buff.Length);

    bool pending = _socket.SendAsync(_sendArgs);
    if (pending == false)
        OnSendCompleted(null, _sendArgs);
}


Send는 Recv와 다르게 보내기에 성공했을 때 추가로 처리해줄만한 것이 없다.

그러므로 만약 보내기에 성공하고 그 사이에 다른 클라이언트가 Send() 했다면

다시 ReigsterSend()를 호출하고 그게 아니라면 _pendingfalse로 전환시켜준다.

이곳도 마찬가지로 RegisterSend()에서 접근할뿐만 아니라 EventHandler

성공한다면 접근하는 곳이기 때문에 lock을 걸어주도록 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void OnSendCompleted(object sender, SocketAsyncEventArgs args)
{
    lock (_lock)
    {
        if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
        {
            try
            {
                // 끝내기 전 Send할게 있다면 이곳에서 처리해주기.
                if (_sendQueue.Count > 0)
                    RegisterSend();
                else
                    _pending = false;
            }
            catch (Exception ex)
            {
                Console.WriteLine($"OnRecvCompleted Failed : {ex}");
            }
        }
        else
        {
            Disconnect();
        }
    }
}


여러 클라이언트의 접근을 생각하여 구현하니 부하에 관련해서 도움이 되었다.

1000명 이상의 클라이언트가 접근한다 생각하며 구현하는 습관을 가져야겠다.


💡 참고

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.