코드를 조금 더 효율적이게 구분하고, 비동기 처리가 될 수 있게 구현을 해보았다. 과정중에서 이벤트 기안을 동작하는 SocketAsyncEventArgs
를 사용하여 아주 편하게 코드 구성을 할 수 있었다.
유저들이 많이 몰릴 경우 처리
-> 해당 부분을 여러개 생성하여 관리하도록 요청하면 된다. 각 독립적으로 행동하기에 문제는 발생하지 않는다.
메인 쓰레드는 잡혀있는데, Accept는 어떻게 실행되었을 까?
-> 테스트를 할 때에 서버 코어 쪽에서 계속 대기 상태를 위해 while로 잡혀있는데, listenr Class에서 Accept는 ThreadPool에서 가져와서 사용한 것 이다. 그렇기에 Accept와 같이 따로 동작할 수 있는 부분은 멀티 쓰레드의 환경 고려해서 제작해야한다.
이번에는 Receive 영역을 따로 Session 구분하여 처리를 할 것이다. Send는 비교적 복잡하기에 다음 시간에 진행 예정
public void Start(Socket socket)
{
// listenr의 Init과 동일
_socket = socket;
SocketAsyncEventArgs recvArgs = new SocketAsyncEventArgs();
recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvCompleted);
recvArgs.SetBuffer(new byte[1024], 0, 1024);
RegisterRecv(recvArgs);
}
SocketAsyncEventArgs를 생성하고, 이벤트 방식을 통해 완료가 되었을 때 동작할 OnRecvCompleted를 담아주는 것 까지는 전반적으로는 같으나
여기서는 시작 시에 receive할 Buff를 따로 세팅 해준다.
즉, 지난 시간에 위와 같이 처리를 했던 부분을 아래 처럼 간단한 코드 한 줄로 처리 할 수 가 있다.
Buffer의 크기, 시작 포인트, 개수 설정
recvArgs.SetBuffer(new byte[1024], 0, 1024)
버퍼를 크게 만들고 나눠서 사용하기도 하기에 버퍼의 시작 포인트는 필요하다.
Start 시에 담아주며 해당 함수 호출이 되고, 검사를 통해 다음으로 넘겨 주는 방식은 지난 시간과 완전히 똑같다.
void RegisterRecv(SocketAsyncEventArgs args)
{
// 비동기
bool pending = _socket.ReceiveAsync(args);
if (pending == false)
{
OnRecvCompleted(null, args);
}
}
위에서 args.Completed 에 등록한 함수로 args가 성공처리 되면 동작하기도 하나, 예외에 경우에도 동작할 수 있다.
if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
{
// todo
try
{
// 위에서 설정한 값을 불러올 수 있음
string recvDa = Encoding.UTF8.GetString(args.Buffer,
args.Offset, args.BytesTransferred);
Console.WriteLine($"[From Client: ] {recvDa}");
RegisterRecv(args);
}
catch (Exception e)
{
Console.WriteLine($"OnRecvCompleted Fail {e}");
}
}
else
{
// todo
}
여기에서 다른 포인트는 Receiv할 buffer가 연결 종료 등의 이유로 0이 올 때도 있기에 예외처리가 추가 된다.
또한 인코딩 과정에서는 args 에 최초에 등록 된 값들을 사용한다.
- args.Buffer = 크기
- args.Offset = 시작 위치
- args.BytesTransferred = 개수
string recvDa = Encoding.UTF8.GetString(args.Buffer, args.Offset, args.BytesTransferred);
다음 시간에 다룰 것이기에 일단 간단하게 처리만 해둔다.
// 샌드는 리시브와 다르게 조금 더 복잡할 수 있음
public void Send(byte[] sendbuff)
{
_socket.Send(sendbuff);
}
연결 종료를 하던 코드또한 Receive, Send하던 소켓을 종료해야하기에 해당 Class에서 함께 처리한다.
다만 이런식으로 간단하게만 처리할 경우 멀티쓰레드 환경에서 두 번 이상 Disconnect에 접속하며 오류가 발생한다. 그렇기에 멀티 쓰레드에서 배운 lock 구조를 처음 사용할 것이다.
- 구분을 위한 flag 선언
int _disconnected = 0;- 활용할 lock
Interlock.Exchange()
매우 간단하게 두 번 처리만 안되면 되기에 Exchange를 활용해 0을 1로 변경시키고 조건 문을 사용할 것이다.
Interlock.Exchange(ref _disconnected, 1)
이렇게만 처리하면 간단하게 Disconnect는 상호배제 된 상태로 만들어 줄 수 있다.
위와 같이 Session 클래스 작업을 마친 후에는 동작하던 곳에서 해당 함수들을 작동 시키는 구조로 변경한다.
좌측이 기존, 우측이 변경한 구조이다.
다음 Ctrl+F5로 실행을 시켜보면 잘 작동하는 것을 확인할 수 있다.