이 글에서 소개하고자 하는 AsyncSocketClient와 AsyncSocketServer는 .NET Framework에서 제공하는 비동기 소켓을 좀 더 사용하기 쉽게 만든 일종의 Wrapper 라이브러리라고 할 수 있다. 물론, .NET Framework 자체에서 소켓을 좀 더 고수준화한 TcpClient와 TcpListener란 클래스도 제공하지만; 제약이 많다;
물론, Socket에서 제공하는 모든 부분까지 구현한 것은 아니고; Connect, Close, Send, Receive, Accept 정도만 제공하는 것으로 소스를 공개함으로써 누구나 수정, 재배포가 가능하도록 하였다(다시 말해서; 자기가 필요한 부분은 추가로 구현해서 써도 상관없다.); 하지만, 가급적이면 저의 흔적을 말살하지 않기를..ㅠㅠ;
아, 그리고 이 라이브러리는 100% TCP 기반으로 구현되어 있다; UDP는 제공하지 않는다;
이 라이브러리가 .NET Framework에서 제공하는 소켓 클래스보다 편리한 부분은 소켓에서 발생하는 Connect, Close, Accept, Error, Send, Receive 등을 이벤트화 하였기에; 폼이나 다른 클래스에서 소켓을 핸들링 하는 것이 쉬워졌다; 다시 말해서, 폼이나 클래스에서 이벤트 핸들러만 재정의하면 소켓 이벤트를 처리할 수 있다;

위 그림에서 알 수 있듯이, 본 라이브러리는 기본적으로 AsyncSocketClass란 하나의 부모 클래스와 클라이언트 소켓 파트를 구현한 AsyncSocketClient, 서버 소켓 파트를 구현한 AsyncSocketServer로 이루어져 있다;
자식 클래스들을 좀 더 자세히 살펴보면 다음과 같다;

만약, 데이터 수신을 성공하면, OnReceive 이벤트를 재정의 하여, 이 이벤트 핸들러에서 수신한 데이터를 지지고 볶고 하면 된다.OnConnect, OnClose, OnSend 모두 같다;
아래는 AsyncSocketClient를 이용해 구현한 샘플 프로그램의 코드 일부(이벤트 재정의 부분)이다.

여기서 중요한 것은 AsyncSocketClient 생성자의 인자로 숫자 0이 들어갔는데; 이는 다수의 AsyncSocketClient 인스턴스들을 생성을 가졍했을 때, 각각을 식별하기 위한 ID라고 할 수 있다.
그리고 그 아래 OnConnect 이벤트 핸들러가 있는데;
인자로 object sender와 AsyncSocketConnectionEventArgs e가 들어간다.
sender는 AsyncSocketClient로 캐스팅해서 쓰면, 이벤트가 발생한 소켓 클라이언트 자신이다;
(AsyncSocketClient worker = (AsyncSocketClient)sender;)
AsyncSocketConnectionEventArgs는 이벤트가 발생한 소켓 클라이언트의 ID 등을 포함하고 있다;

OnReceive를 살펴보면 넘어오는 Event 인자가 소켓의 ID, 수신한 바이트 수(ReceivedBytes), 수신한 데이터(ReceivedData)가 포함되어 있음을 알 수 있다. 참고로 위의 코드는 수신한 데이터가 byte[] 타입이기 때문에, string으로 캐스팅하기 위한 Encoding.... 코드가 포함되어 있다.
그 다음 AsyncSocketServer로 넘어가면;
AsyncSocketServer는 public method로 Listen, Stop이 지원된다; 만약 Client의 접근이 Accept 되면, OnAccept 이벤트가 발생하므로, OnAccept 이벤트를 재정의 하여, Client를 처리하면 된다;

AsyncSocketServer에서 가장 중요한 부분은 클라이언트 소켓의 접속을 처리하는 OnAccept 부분이다.
OnAccept에서 넘어오는 Event 인자는 접속이 허가된 소켓(e.Worker) 인스턴스가 포함되어 있다;
따라서, OnAccept 이벤트 핸들러에서는 AsyncSocketClient를 그대로 이용하기 위해, 이벤트 핸들러 내의
첫 줄과 같은 코드가 필요하다; 또한; 클라이언트의 ID가 중복되지 않도록, 새로운 ID도 부여한다.
그리고 그 다음이 앞서 언급한 AsyncSocketClient가 Receive를 호출해야할 특수한 상황이다;
구현 방법에 따라서; 이 부분은 Accept 코드 내에서 처리할 수도 있었지만; 어짜피 개발자가 이벤트 핸들러 연결도 필요하기 때문에; 그냥 이렇게 구현하였다.
끝으로 새로 생성한 클라이언트 소켓들을 미리 정의한 이벤트 핸들러와 연결하는 코드들이 나오고;
끝으로 클라이언트 리스트에 Add한다. (다수의 클라이언트 접속 가능 예를 들기 위해서 이렇게 했음);
좀 더 편리한 개발을 위해서는; MSDN 수준의 Class Specification을 쓰고 싶었지만; 시간 및 기타 여건이 허락되지 않는 관계로 이만 줄임!
대신에 라이브러리 및 예제 소스를 공개하니; 이를 이용하면 되지 않을까... 무책임하게 생각해봅니다;
다운로드: http://www.nohungry.net/Data/AsyncSocket.zip
Trackback
Trackback Address :: http://www.nohungry.net/tt1/trackback/160


















Comments
좋은 정보 감사합니다.
서버/클라에 대한 구현 팁을 좀더 알려주셧으면 하는..
초보자에게 정말 많은 도움이 될거 같습니다.
저도 아는 것이 없지만; 매우 기초적인 내용으로 조금씩 쓰려고 생각 중이었습니다;
날개발자지만; 명색이 직딩이다보니~ 잘 안되지만; 틈틈이 써보도록 하겠습니다~^^;
테스트를 해보왔는데...소켓에 대한 close가 안됩니다..ㅠㅠ
이벤트가 발생을 안하네요
위 코드의 경우,
여러번 Send()를 날렸을 때
onReceive() 이벤트가 byte[]가 중첩되어 발생하는 경우가 있는데요
(접속상태가 좋지 못하거나, 지연되는 네트워크일때 더더욱)
이것은 정상인 것인가요? 아니면 의도되지 않은 오류인가요?
예) 10bytes를 빠르게 여러번 Send() ->
onReceive() 에서 발생되는 이벤트가, 10, 10, 20, 10, 20, 30, 10 등
중첩되어 발생되는 경우가 있음
제가 회사를 옮기고, 회사 보안 정책 상 회사에서 블로그나 데브피아에 글을 적을 수 없어 이제 확인합니다;
소켓 통신에서는 10바이트를 보낸다고 항상 받는 쪽에서도 10바이트를 받는다고 보장할 수 없습니다.
님의 말씀처럼 네트워크 지연이나 기타 원인으로 인해 당연히 중첩되어 받을 수 있습니다. 즉, 정상이라는 얘기입니다.
따라서, 데이터를 받는 쪽은 버퍼(흔히 큐를 많이 이용합니다.)를 이용해서 데이터를 처리하고, 데이터를 보내는 쪽에서는 보낸 데이터들을 구분하기 위해 데이터에 STX, ETX와 같은 구분자를 붙여서 보내고는 합니다.
저도 Close할 때 오류가 나네요.
Close 오류는 해결했는데,
이제는 Close할 때 이전에 보냈던 hello가 또 보내지네요;;