1999년  5월 20일 PM 03:42
                                           by 조경민
Socket 프로그래밍
**********************************************************************

CSocket과 CAsynSOcket이 있는데 CSocket은 안싱크소켓을 상속받은 클래스
이다.

########소켓 프로그램을 하려면 #########

1) 애플리 위자드를 이용하면
  -> 위자드에서 Support Socket을 클릭한다.

2) 직접 소켓 서포팅을 하고 싶으면
  -> StdAfx.h 파일에 다음을 추가한다.
  #include <afxsock.h>                // MFC socket extensions

  -> App::InitInstance() 함수 부분에 다음을 삽입한다.
          if (!AfxSocketInit())
        {
                AfxMessageBox(IDP_SOCKETS_INIT_FAILED);
                return FALSE;
        }

######## 소켓 프로그래밍 서버 구현#########

서버 프로그램은 클라이언트들이 계속 언제든 접속 요청을 하거나
데이타를 받아가거나 하기 때문에 언제나 접속 요청에 대해서 응답할
수 있도록 리슨소켓(Liten Socket)이라는 것과 각 클라이언트들이
접속하여 서로 서버와 데이타를 주고 받기 위해서 각 클라이언트마다
1:1 대화를 위해서 리시브소켓(Recieve Socket)을 마련해야 한다.

리슨 소켓이나 리시브 소켓은 CSocket이나 CAsyncSocket둘증 하나면
된다.

######## 소켓 프로그램 클라이언트 쿠현########

클라이언트 프로그램은 서버에 접속을 위해서 접속되면 서버와
1:1 대화를 위해서 하나의 소켓인 클라이언트 소켓이 필요하다.
클라이언트 소켓은 Connect로 서버에 접속 시돌르 하고, 접속되면
Send나 Recieve로 데이타를 보내거나 얻어낼수 잇다.


CSocket과 CAsyncSocket 안싱크소켓의 차이점은 동기화를 하는가다
*동기화* 기다린다라는 개념으로 두 사람이 작업을 처리할때 한사람이
작업을 다할때까지 기다릴때 그것을 우린 동기화 한다라고 한다.

########### 소켓 클래스 맴버 함수 ###########
CAsyncSocket은 CSocket의 베이스 클래스이므로 안싱크 소켓의 맴버를보자

CAsyncSocket() 생성자 Constructs a CAsyncSocket object.
Create()       소켓 생성자 Creates a socket.

소켓은 실제 API수준에서 SOCKET이라는 핸들이기 때문에 Create()에서
이 소켓 핸들을 생성해 준다.

Attributes 속성들

Attach Attaches a socket handle to a CAsyncSocket object.
Detach Detaches a socket handle from a CAsyncSocket object.
FromHandle Returns a pointer to a CAsyncSocket object, given a socket handle.
GetLastError Gets the error status for the last operation that failed.
GetPeerName Gets the address of the peer socket to which the socket is connected.
GetSockName Gets the local name for a socket.
GetSockOpt Retrieves a socket option.
SetSockOpt Sets a socket option.


Operations

Accept Accepts a connection on the socket.
        서버의 리슨소켓이 할일로 새로이 접속요청한 클라이언트소켓
        (서버입자에선 리시브 소켓)을 인정한다.
AsyncSelect Requests event notification for the socket.
Bind Associates a local address with the socket.
Close Closes the socket.
        클라이언트소켓이나 리시브소켓을 닫는다.
Connect Establishes a connection to a peer socket.
        클라이언트소켓이 할일로 원하는 ip혹은 도메인주소로 서버소켓에
        접속을 시도한다.
IOCtl Controls the mode of the socket.
Listen Establishes a socket to listen for incoming connection requests.
        서버의 리슨소켓이 할일로 리슨을 호출하면 클라이언트소켓의
        접속요청을 기다리는 리슨 상태가 된다.
Receive Receives data from the socket.
        서버의 리시브소켓이나 클라이언트소켓이 소켓 버퍼로부터 데이타를
        받는다.
ReceiveFrom Receives a datagram and stores the source address.
Send Sends data to a connected socket.
        서버의 리시브소켓이나 클라이언트소켓이 소켓 버퍼로 데이타를
        보낸다. 네트웍장비를 통해서 나가게 된다.
SendTo Sends data to a specific destination.
ShutDown Disables Send and/or Receive calls on the socket.
        보내거나 받기를 Disable가능하지 못하게 할 수 잇다.
        그러나 이는 Close처럼 소켓을 닫는것이 아니다.

Overridable Notification Functions
  우리가 오버라이드해서 쓰게될 함수들이다.

OnAccept Notifies a listening socket that it can accept pending connection requests by    calling Accept.
         서버의 리슨소켓이 클라이언트 소켓이 접속해서 서버쪽에서 리시브소켓을
         하나 생성해서 이를 인정하려는 시점을 명시한다.
         이 함수를 오버라이드하면 클라이언트의 접속 요청이 오면 이 함수가
         자동 호출이된다.
OnClose Notifies a socket that the socket connected to it has closed.
         서버의 리시브소켓 또는 클라이언트 소켓에서 접속이 끝어지면 호출된다.
OnConnect Notifies a connecting socket that the connection attempt is complete,
whether successfully or in error.
         서버의 리시브소켓이나 클라이언트 소켓에서 접속이 성공되면 호출된다.
OnOutOfBandData Notifies a receiving socket that there is out-of-band data to be read on the socket, usually an urgent message.
         out-of-band로 데이타가 올경우 호출된다.
OnReceive Notifies a listening socket that there is data to be retrieved by calling Receive.
         서버의 리시브소켓이나 클라이언트 소켓에서 데이가 오면 호출된다.
OnSend Notifies a socket that it can send data by calling Send.
        서버의 리시브소켓이나 클라이언트 소켓이 Send함수를 호출하면(데이타보내면)
        자동 호출된다.



############ 소켓 쓰기 ###############

서버의 리슨소켓과 리시브 소켓, 클라이언트의 소켓이 할일 순서
다음의 상황은 서버가 하나의 클라이언트만 받아들이는 단순한 경우이다.

                       [ 소켓 함수 쓰임 순서도 ]
=======================================================================

서버의 상황                              클라이언트 상황
서버 프로그램                            클라이언트 프로그램
CLitenSocket : public CSocket            CClientSocket : public CSocket
CRecieveSocket: public CSocket

CLitenSocket lsck;                       CClientSocket csck;
CRevieveSocket rsck;
-----------------------------------------------------------------------
서버 쪽                                  클라이언트 쪽
-----------------------------------------------------------------------
리슨소켓생성                             //클라이언트소켓 생성
lsck.Create(23);                         csck.Create();
// 포트 23번으로 열었다.
// 이제 클라이언트를 기달린다.
------------------------------------------------------------------------
                                          // 서버에 접속 시도
                                          csck.Connect("xxx.bro.com",23);
                                          // "120.312.33.11" 아이피도 됌
------------------------------------------------------------------------
CRecieveScoket::OnAccept() 함수에서      CClientSocket::OnConnect()함수
// 리시브 소켓을 인정한다.               ::MessageBox(NULL,"접속성공",
this->Accept(rsck);                      "알림",0);

이제 rsck는 csck에 대해서 1:1 대화로
rsck는 서버 , csck는 클라이언트이다.
------------------------------------------------------------------------
[rsck]가 어떤 시점에서
Send("hi",2);
csck로 rsck(서버)가 보낸다.
------------------------------------------------------------------------
                                          CClientSocket::OnRecieve()함수에서
                                          char buf[2];
                                          Recieve( buf , 2);
                                          // 소켓 버퍼에서 2개 얻어온다.
------------------------------------------------------------------------
                                          [csck]가 어떤시점에서
                                          Send("yes",3);
                                          csck가 rsck로 보낸다.
------------------------------------------------------------------------
CRecieveSocket::OnRecieve()함수에서
char buf[3];
Recieve( buf , 3);
------------------------------------------------------------------------
                                          클라이언트 소켓파기
                                          Close시
------------------------------------------------------------------------
CRevieveSocket::OnClose()함수에서        CClientSocket::OnClose()함수
this->Close(); 자기를 닫기               ::MessageBox(NULL,"접속끊김",
                                          "알림",0);
------------------------------------------------------------------------
=========================================================================


  ######## 다중 접속 서버클라이언트 ###########

  서버 프로그램은 각 여러 클라이언트를 받을 리시브 소켓이 여러개여야 하므로
  링크드리스트구조든 배열이든 여러개의 리시브 소켓을 갖고 있어야 한다.

  CObList가 무난.


  CLitenSocket::OnAccept() 에서

  CRecieveSocket* pRSck = new CRecieveSocket;
  // 리시브 소켓 하나 만들고

  Accept(*pRSck); // 클라이언트 소켓 인정

  m_lstRSck.AddTail(pRSck); // 리시브 소켓 리스트에 저장



  CLitenSocket::OnClose()에서

  //  CRecieveSocket* pRSck 지울 리시브 소켓
  pRSck->Close(); 먼저 소켓을 닫는다.

  // 리스트에서 지우고
  POSITION pos = m_lstRSck.Find(pRSck);

  if( pos )
        m_lstRSck.RemoveAt( pos );
        
  // 실제로 리스브 소켓 객체를 지운다.
  delete pRSck;


  ############# CSocket과 CAsyncSocket의 차이점 ###########

  리슨 소켓이나 리시브소켓, 클라이언트 소켓의 상속클래스를
  CSocket으로 하는가 CAsynSocket으로 하냐에 따라서 동기화된 소켓인가
  아닌가가 결정이 된다.

  CAsyncSocket::Send함수의 내부 코드
  int CAsyncSocket::Send(const void* lpBuf, int nBufLen, int nFlags)
  {
        return send(m_hSocket, (LPSTR)lpBuf, nBufLen, nFlags);
  }

  API 소켓 함수인send를 쓰며 리턴되는 값은 한번 보내기 시도로
  보낸 자료의 크기이다.
  따라서.

  CAsyncSocket::Send() 함수가 호출되었을때 모든 자료가 센드되었다고
  볼수 없다. 그리고 한번 센드 후 다른 센드를 하려 하면  WSAEWOULDBLOCK라는
  API수준의 소켓 에러가 뜨게 된다.
  GetLastError()라는 함수를 호출하면 WSAEWOULDBLOCK인지 알수 있다.

  send 함수에서 에러중 WSAEWOULDBLOCK관련 글
  WSAEWOULDBLOCK The socket is marked as nonblocking and the requested operation would block.

  recv 함수에서 에러중 WSAEWOULDBLOCK관련 글
  WSAEWOULDBLOCK The socket is marked as nonblocking and the receive operation would     block.
  즉 두글은 비동기적인 API 소켓 함수인send ,recv를 호출시 에러가 날수
  있는데 그 중 하나인 WSAEWOULDBLOCK의 에러표시는 현재 send나 recv를
  해서 블러킹 상태는 아닌데 아직 자료를 더 보내거나 읽어야 하기 때문에
  블럭이 될 것이라는 에러 표시 이다. 즉 아직 더 보내거나 더 읽어야할
  자료가 있을때 나오는 에러 표시이다.

  CSocket::Send()함수는 어떻게 호출이 되는지 보자

  int CSocket::Send(const void* lpBuf, int nBufLen, int nFlags)
  {
      if (m_pbBlocking != NULL)
        {
                WSASetLastError(WSAEINPROGRESS);
                return  FALSE;
        }

        int nLeft, nWritten;
        PBYTE pBuf = (PBYTE)lpBuf;
        nLeft = nBufLen;

        while (nLeft > 0) // 남은 량이 없을때 까지 계속 Send한다.        
      {
                nWritten = SendChunk(pBuf, nLeft, nFlags);
                if (nWritten == SOCKET_ERROR)
                        return nWritten;

                nLeft -= nWritten;
                pBuf += nWritten;
        }
        return nBufLen - nLeft;
  }

  int CSocket::SendChunk(const void* lpBuf, int nBufLen, int nFlags)
  {
        int nResult;
      // 한번 CAsyncSocket::Send를 하고 남은 량을 리턴한다.
        while ((nResult = CAsyncSocket::Send(lpBuf, nBufLen, nFlags)) == SOCKET_ERROR)
        {
                if (GetLastError() == WSAEWOULDBLOCK)
                {
                        if (!PumpMessages(FD_WRITE))
                                return SOCKET_ERROR;
                }
                else
                        return SOCKET_ERROR;
        }
        return nResult;
  }

   우리가 의미 있게 봐야 할 것은 다른것이 아니라 CSocket::Send는 자료를
   다 보낼때 까지 while을 돈다는 것 뿐이다. 즉
   CSocket::Send() 함수를 호출하면 자료가 다 보내질때가지 코드 진행이
   일어나지 않는다는 말이다. 반대로 CAsyncSocket::Send()는 자료가 다 보
   내지든 말든 코드가 바로 진행이 되는데 이것을 비동기라고 하고
   CSocket에서 자료 다 보낼때가지 코드 진행을 않고있는것을 동기라고한다.
   따라서 CSocket의 어떤 함수도 절대 WSAEWOULDBLOCK을 반환하지 않는다.


   즉 CAsyncSocket을 이용해서 리슨소켓 리시브소켓,클라이언트 소켓을
   만들었다면 자료를 다 보낼것을 확신하기 위해서 계속 WSAEWOULDBLOCK를
   체크해서 모든 데이타가 다 완전히 온전하게 보내졌는지 받아졌는지를
   위해 Send와 Recieve를 해야 할것이다.  CSocket::Send의 while처럼..

   ############# 소켓과 스레드 ##################

   소켓 프로그래밍에서 스레드를 써야 할 부분이 있다.

   - 만일 클라이언트에서 SQL 질의문을 서버로 보내면 서버가 그 질의를
   처리해야 한다면 그 질의를 실행하는 Excute되는 SQl 엔진은 스레드로
   처리되어야 할것이다. 한 클라이언트에 대한 질의를 하는 동안 다른
   클라이언트에서 SQL질의가 올수 있기 때문이다.

   - 채팅 프로그램이라면 로그작업이나 다른 여타 작업을 스레드로 하여
   부하를 줄일수 있다.

   =>  이런 작업을 인터페이스 작업이라고 하겠다.

   만일 서버에서  리시브 소켓을 CWinThread로 상속받아서 각각의
   소켓이 스레드로 되어 각 스레드가 Send를 하면 네트웍 이점이 있을까?
   즉 인터페이스 작업이 아닌 네트웍 작업 (Send,Recieve)에 이점.

   *이 부분은 내 생각임 *
   내 생각에는 전혀 아무런 이득이 있을수 없다.
   실제로 SPY++로 서버프로그램에 띄어진 스레드를 보면
   Socket Notification Sink라는 모니터링 스레드가 하나 생기는데 이는
   AfxSocketInit()함수에서 생기는 스레드이다.

   다음은 내부 코드이다.

   BOOL AFXAPI AfxSocketInit(WSADATA* lpwsaData)
{
        _AFX_SOCK_STATE* pState = _afxSockState.GetData();
        if (pState->m_pfnSockTerm == NULL)
        {
                // initialize Winsock library
                WSADATA wsaData;
                if (lpwsaData == NULL)
                        lpwsaData = &wsaData;

                WORD wVersionRequested = MAKEWORD(1, 1);
                int nResult = WSAStartup(wVersionRequested, lpwsaData);
                if (nResult != 0)
                        return FALSE;

                if (LOBYTE(lpwsaData->wVersion) != 1 || HIBYTE(lpwsaData->wVersion) != 1)
                {
                        WSACleanup();
                        return FALSE;
                }

                // setup for termination of sockets
                pState->m_pfnSockTerm = &AfxSocketTerm;

#ifndef _AFXDLL
                // setup maps and lists specific to socket state
                _AFX_SOCK_THREAD_STATE* pState = _afxSockThreadState;
                if (pState->m_pmapSocketHandle == NULL)
                        pState->m_pmapSocketHandle = new CMapPtrToPtr;
                if (pState->m_pmapDeadSockets == NULL)
                        pState->m_pmapDeadSockets = new CMapPtrToPtr;
                if (pState->m_plistSocketNotifications == NULL)
                        pState->m_plistSocketNotifications = new CPtrList;
#endif
        }

        return TRUE;
}

다음은 _AFX_SOCK_STATE형 선언부다. <sockimpl.h>

class _AFX_SOCK_STATE : public CNoTrackObject
{
public:
        DWORD m_dwReserved1;    // reserved for version 4.1 only
        HINSTANCE m_hInstSOCK;      // handle of WSOCK32.DLL
        void (AFXAPI *m_pfnSockTerm)(void); // set once initialized
        virtual ~_AFX_SOCK_STATE();
};

EXTERN_PROCESS_LOCAL(_AFX_SOCK_STATE, _afxSockState)



그리고 이 모니터링 스레드는 윈도처럼 메세지맵을 같고 있다.
서버에서 보면 각 클라이언트로 부터 오는 문자열이 있다고 볼때,
각 리시브소켓에게 오는 문자열은 처음에 결국 이 모니터링 스레드에게
메세지로 가게 된다.
이는 _afxSockThreadState->m_hSocketWindow 라는 윈도우 핸들이다.
WM_SOCKET_NOTIFY라는 메세지는 이 스레드로 가게 되고

'KB > tutorial' 카테고리의 다른 글

ATL 조금 하기  (0) 2004.03.19
VB With AutoCAD R14  (1) 2004.03.19
[com] ATL COM 쓰기  (0) 2004.03.19
[ActiveX] 디지털서명  (0) 2004.03.19
Active X 프로그램 짜기  (0) 2004.03.19

+ Recent posts