2014. 9. 23. 15:00

저도 .NET을 초기 버전부터 사용하던 사람이라 초기에 만든 'Client/Server'프로그램을 계속 고쳐서 사용했습니다.

 

그런데 이번에 슈퍼소켓을 사용하여 서버를 다시 만들려고 자료를 찾다 보니 '.NET 3.5' 부터는 'SocketAsyncEventArgs'를 사용하라고 권장하는군요.

그래서 샘플을 찾는데..... 원하는 셈플이 없어서 하나 만들었습니다.

(참고 : 'SocketAsyncEventArgs'를 이용한 채팅(Chatting) Client/Server 예제 0.7)

 

샘플을 만들면서 느낀 점을 간단히 적어 보겠습니다.

 

 

1. 무한 루프여 안녕~

이전에는

1) 서버에 리스너를 동작시킨 후

2) 무한 루프를 만들고

3) 'TcpListener.AcceptSocket()'로 무한 루프를 대기 시키고 있다가

4) 유저가 접속하면 동작시키는 방법을 사용하였습니다.

 

이제는 'Socket.AcceptAsync()'에 이벤트를 연결해 두면 유저가 접속하면 자동으로 함수가 실행됩니다.

// ***** 기존 코드 *****
//유저를 기다린다.
do
{
    //유저를 기다리다가 접속하면 유저클라이언트를 만들어준다.
    //do~while문이 서있는곳
    //유저가 접속해야 동작한다.
    claConnectUser insNewUser = new claConnectUser(tcplistenerServer.AcceptTcpClient());

    //새로 만든 유저클라이언트에 델리게이트를 붙여준다.
    insNewUser.Connected += new dgConnect(OnConnected);         //접속했을때 발생하는 이벤트 연결
    insNewUser.Disconnected += new dgDisconnect(OnDisconnected);    //끊겼을때 발생하는 이벤트 연결
    insNewUser.Messaged += new dgMessage(OnMessaged);           //메시지가 왔을때 발생하는 이벤트 연결
    insNewUser.Loged += new dgLog(OnLog);                               //로그가 왔을때 발생하는 이벤트 연결

    insNewUser.Connect();   //접속을 했으니 접속을 호출

} while (true); //end do





// ***** SocketAsyncEventArgs를 이용한 코드 *****
SocketAsyncEventArgs saeaUser = new SocketAsyncEventArgs();
//유저가 연결되었을때 이벤트
saeaUser.Completed += new EventHandler<SocketAsyncEventArgs>(Accept_Completed);
//유저 접속 대기 시작
socketServer.AcceptAsync(saeaUser);
private void Accept_Completed(object sender, SocketAsyncEventArgs e)
{
    //다시 클라이언트 접속을 기다린다.
    Socket socketServer = (Socket)sender;
    e.AcceptSocket = null;
    socketServer.AcceptAsync(e);
}

 

 

2. 콜백이여 안녕~

이전에는 비동기 메시지를 수신하고 처리하기 위해서 콜백을 사용했습니다.

콜백 자체가 나쁜 건 아니지만, 가독성이 안 좋다는 문제가 있죠.

 

.NET에는 외부에서 함수를 동작시키기 위한 방법으로 콜백말고도 이벤트가 있는데

이걸 콜백대신 사용하면 상황에 따라서는 더 깔끔한 코드가 됩니다.

 

반복되어서 호출되는 함수라면 특히나 이벤트가 더 맞습니다.

// ***** 기존 코드 *****
//비동기 방식으로 메시지 수신 시작
AsyncCallback GetStreamMsgCallback = new AsyncCallback(GetStreamMsg);
MyTcpClient.GetStream().BeginRead(byteStreamData, 0, claGlobal.insNetData.uBufferSize, GetStreamMsgCallback, null);





// ***** 이벤트를 이용한 코드 *****
//데이터 구조 생성
MessageData MsgData = new MessageData();
//리시브용 인스턴스 생성
SocketAsyncEventArgs saeaReceiveArgs = new SocketAsyncEventArgs();
//리시브용 데이터 구조 지정
saeaReceiveArgs.UserToken = MsgData;
//리시브용 데이터버퍼 설정
saeaReceiveArgs.SetBuffer(MsgData.GetBuffer(), 0, 4);
//유저한테서 넘어온 데이터 받음 완료 이벤트 연결
saeaReceiveArgs.Completed += new EventHandler<SocketAsyncEventArgs>(Recieve_Completed);
//데이터 받기 시작
m_socketMe.ReceiveAsync(saeaReceiveArgs);

 

코드만 봐서는 새로운 코드가 더 복잡해 보이지만

실제로는 'SocketAsyncEventArgs'를 생성하기 위한 코드 때문이고 이것을 제외하면 이벤트 연결과 리시브 열기뿐이 없습니다.

코드가 직관적이라 추적할 때 좋습니다.

 

이전코드는 '.BeginRead'를 설정하고 나서 진행이 되면 'GetStreamMsgCallback'에 설정된 함수가 실행됩니다.

새로운 코드는 '.ReceiveAsync'를 호출하고 나면 연결된 이벤트가 실행됩니다.

 

 

3. 이벤트 단위 처리

이벤트 단위로 처리하니 코드가 깔끔해지는 장점이 있습니다.

모든 소켓 비동기 작업은 'SocketAsyncEventArgs'를 통해 이벤트 단위로 처리되기 때문에 코드 추적이 간단합니다.

 

'SocketAsyncEventArgs'를 제대로 사용하려면 개체를 잘 관리해야 합니다.

필요에 따라선 풀(pool)을 만들어 관리해야 합니다.

 

 

마무리

제가 이걸 만들려고 만든 게 아니다 보니 분석을 대충에서 분석이 맞는 건지 알 수 없습니다 ㅎㅎㅎㅎ

 

100명 미만이라면 그냥 써도 되겠지만.....

이런거 쓸 때는 가급적 풀을 만들어서 관리해야 합니다.