.net에서 소켓 프로그래밍을 하려면 몇 가지 선택사항이 있는데 그중 하나가 'SocketAsyncEventArgs'입니다.
전에 '[.Net] SocketAsyncEventArgs - 큰 데이터(Large Data) 전송할 때 생기는 문제'라는 글에서 버퍼크기보다 큰 데이터가 오는 경우 어떤 일이 발생하는지 다루었습니다.
(참고 - [.Net] SocketAsyncEventArgs - 큰 데이터(Large Data) 전송할때 생기는 문제 )
그렇다면 'SocketAsyncEventArgs'를 이용하여 큰 데이터(Large Data)나 연속 메시지(Continuous Receive)에 의한 리시브데이터 뭉침 현상을 어떻게 처리해야 하는지를 알아보겠습니다.
이 포스팅의 완성된 코드는 공개프로그램에서 확인할 수 있습니다.
참고 - SocketAsync Chatting 0.83.5 - 'SocketAsyncEventArgs'를 이용한 Client/Server
MSDN - SocketAsyncEventArgs 클래스
MSDN의 설명을 보면 무슨 말인지 모르겠습니다 ㅎㅎㅎ
간단하게 설명하면 소켓처리를 지원(assist)하기 위한 클래스입니다.
'SocketAsyncEventArgs'를 사용할 때는 웬만하면 연결된 'Socket'객체에 직접 접근하지 않는 것이 좋습니다.
'SocketAsyncEventArgs'의 목적이 소켓 응용프로그램에서 사용하는 비동기 패턴을 제공하는 데 있기 때문입니다.
(그렇다고 무조건 쓰지 말라는 건 아니죠 ㅎㅎㅎ)
'SocketAsyncEventArgs'는 주고(Send)/받기(Receive)에 사용할 수 있고 풀링도 가능합니다.
(풀링하는건 다음에 다루도록 하죠.)
'SocketAsyncEventArgs.Completed'이벤트는 주고받을 때 모두 발생하며 'SocketAsyncEventArgs.LastOperation'속성을 통해 샌드에서 온 건지 리시브에서 온 건지 구분할 수 있습니다.
(보통은 샌드용 리시브용을 나눠서 사용하긴 합니다. 하지만 하나로 사용하는 것도 가능합니다.)
'SocketAsyncEventArgs.SetBuffer'를 통해 버퍼를 지정할 수 있습니다.
이때 지정된 버퍼는 'Socket'에 세팅된 버퍼와 같을 필요는 없습니다.
간단하게 코드를 만들어 'SocketAsyncEventArgs'를 어떻게 사용하는지 알아봅시다.
데이터를 받으려면 보내기(Send)전에 받을(Receive) 준비가 되어있어야 합니다.
받을 준비는 'SocketAsyncEventArgs.SetBuffer'를 통해 버퍼를 지정한 후 'Socket.ReceiveAsync'에 생성한 'SocketAsyncEventArgs'를 전달하여 받을 준비를 합니다.
//받기용 'SocketAsyncEventArgs'생성
SocketAsyncEventArgs saeaReceiveArgs = new SocketAsyncEventArgs();
//받음 완료 이벤트 연결
saeaReceiveArgs.Completed += new EventHandler<SocketAsyncEventArgs>(SaeaReceiveArgs_Completed);
//기본버퍼 세팅
saeaReceiveArgs.SetBuffer(DGU_CSocket.Util_Buffer.Buffer_GetButter , 0 , DGU_CSocket.Util_Buffer.Buffer_GetButterSize);
//받음 보냄
m_socketMe.ReceiveAsync(saeaReceiveArgs);
만약 해당 소켓에서 오는 데이터가 있다면 연결된 '.Completed'이벤트가 발생합니다.
'e.Buffer'를 통해 보낸 데이터를 받을 수 있습니다.
보내는 거야 그냥 보내면 됩니다.
소켓에 세팅된 버퍼보다 큰 데이터를 보내도 상관없이 잘 갑니다.
'SocketAsyncEventArgs.SetBuffer'를 호출 할 때 전송할 데이터를 전달할 수 있습니다.
버퍼가 세팅된 다음 'Socket.SendAsync'에 세팅한 'SocketAsyncEventArgs'를 매개변수로 전달하여 보내기를 완료합니다.
byte[] byteAllBady = doSendMsg.AllBady_Get();
//서버에 보낼 객체를 만든다.
SocketAsyncEventArgs saeaServer = new SocketAsyncEventArgs();
//데이터 길이 세팅
saeaServer.SetBuffer(byteAllBady, 0, byteAllBady.Length);
보내기 완료 이벤트 연결 saeaServer.Completed += new EventHandler<SocketAsyncEventArgs>(Send_Completed);
//보내기가 완료되었는지 여부
m_socketMe.SendAsync(saeaServer);
//비동기로 보낼꺼니까 리턴은 항상 true다.
데이터가 모두 전송되면 연결된 '.Completed'이벤트가 발생합니다.
위 코드만 가지고도 데이터를 주고받을 수 있습니다.
하.지.만.
우리에게 있는 문제는 주고받는 게 아니죠 ㅎㅎㅎ
큰 데이터를 보내게 되면 어떻게 되느냐입니다.
이 문제는 전에 다른 포스팅에서도 다루었습니다.
(참고 - [.Net] SocketAsyncEventArgs - 큰 데이터(Large Data) 전송할때 생기는 문제 )
여기서 다시 정리하고 가도록 하죠.
위 코드로 보내기를 하게 되면 보내기용 버퍼를 그때그때 데이터 크기만큼 생성하므로 소켓의 버퍼와 상관없이 모든 데이터가 전송되게 됩니다.
받을 때는 소켓의 버퍼만큼 데이터가 순차적으로 들어오게 됩니다.
'SocketAsyncEventArgs'에서 소켓에 있는 데이터를 받아가면 받아간만큼 데이터가 잘려나갑니다.
(이때 소켓에 있는 데이터를 직접 읽어오면 'SocketAsyncEventArgs'에서 가져간만큼 잘려서 옵니다.)
남은 데이터는 다음 'SocketAsyncEventArgs.Completed'이벤트를 통해 옵니다.
만약 버퍼가 4byte인데 데이터가 16byte라면 완료 이벤트(.Completed)가 4번 오게 됩니다.
비동기로 데이터를 전송하면 완료 이벤트는 데이터를 모두 받은 시점이 아니라 소켓에 버퍼를 채우기 시작한 시점에 발생합니다.
그러므로 버퍼만큼 데이터가 들어오리라는 보장이 없습니다.
이때 'SocketAsyncEventArgs'가 읽어 들인 버퍼의 양은 '.BytesTransferred'를 통해 알 수 있습니다.
소켓에 버퍼가 채우기 시작하면 발생하기 때문에 몇 번의 완료 이벤트가 발생할지는 알 수 없습니다.
그래서 보통은
보내는 쪽에서는 헤더를 만들어 보내는 데이터의 크기를 기록하여 보내고
받는 쪽에서는 헤더가 완성되면 데이터의 크기를 가지고 있다가 받은 데이터에서 크기만큼 잘라서 쓰게 됩니다.
'4.'와는 반대의 경우로 여러 데이터가 하나의 'SocketAsyncEventArgs'로 올 수도 있습니다.
이런경우
1) 헤더에서 데이터의 크기를 읽어
2) 크기만큼 자르고
3) 남은 데이터에 해더가 있는지 확인하고
4) 다시 크기만큼 자르고...
이렇게 여러번 잘라서 써야합니다.
이제 'SocketAsyncEventArgs'가 어떻게 동작하는지 간단하게 알아보았습니다.
하지만 우리가 중요한건 '저렇게온 데이터를 어떻게 사용하느냐?'죠.
다음 포스팅에서는 이 내용을 가지고 큰 데이터(Large Data)를 처리해 보도록 하겠습니다.