1:1 채팅 프로그램을 만들 수 있을 정도로 발표 준비를 할 것. ---- === 소켓 이해하기 === 소켓은 멀리 떨어진 두개의 호스트(서버와 클라이언트)를 연결하는 매개체이다. 전화기에 비유!!!전화기≒소켓 || 전화기 || 소켓 || || 전화를 받기 위한 '전화 번호' || IP주소 || || 전화선이 연결되어 전화를 받을 수 있는 상태 || 대기상태(Listen) || || 전화가 걸려옴 || 연결 요청(Connect) || || 전화를 받음 || 연결 허락(Accept) || 서버-클라이언트 환경을 만들기 위한 과정을 서버측에서 보자면 다음의 과정을 거치게 된다. {{{~cpp Socket 생성 -> Socket 에 이름연결 (bind) -> 클라이언트의 연결을 기다림(listen) -> 클라이언트를 받아들임 (Accept) -> 클라이언트의 명령을 받아서 적절한 서비스를 수행 }}} 클라이언트측에서 서버에 접근하기 위해서는 단순히 소켓을 생성후 서버에 연결(connect) 하기만 하면 된다. {{{~cpp Socket 생성 -> 서버에 연결 시도(connect) -> 서버에 각종 명령을 전달 }}} === 예제(서버) === [Dialog based] -> [Windows Sockets]를 설정 기초 클래스가 CAsyncSocket인 새로운 클래스 CListenSock, CChildSock을 새로 생성한다. 새로 추가된 두개의 클래스를 다음과 같이 편집한다. [클래스위저드]의 CListenSock에 가상 함수 OnAccept()를 추가한 후 다음 라인을 삽입한다. {{{~cpp ((CServerApp*)AfxGetApp())->Accept(); }}} [클래스위저드]의 CChildSock에 가상 함수 OnReceive()와 OnClose()를 추가한 후 다음 코드를 삽입한다. {{{~cpp ((CServerApp*)AfxGetApp())->CloseChild(); }}} {{{~cpp ((CServerApp*)AfxGetApp())->ReceiveData(); }}} 어플리케이션 클래스에 다음의 멤버변수와 함수 원형을 선언한다. {{{~cpp #include "ChildSock.h" #include "ListenSock.h" }}} {{{~cpp void InitServer(); void Accept(); void SendData(CString& strData); void ReceiveData(); void CloseChild(); void CleanUp(); CListenSock* m_pServer; CChildSock* m_pChild; }}} 어플리케이션 클래스의 생성자에서 추가한 멤버변수를 초기화한다. {{{~cpp m_pServer = NULL; m_pChild = NULL; }}} 그리고 나서 추가한 멤버함수를 다음과 같이 정의한다. {{{~cpp void CServerApp::InitServer() { m_pServer = new CListenSock; m_pServer->Create(7000); m_pServer->Listen(); } void CServerApp::Accept() { AfxMessageBox("접속 허용"); m_pChild = new CChildSock; m_pServer->Accept(*m_pChild); m_pMainWnd->GetDlgItem(IDC_SEND)->EnableWindow(TRUE); } }}} 다음으로 데이터 송수신 작업을 하는 함수를 추가한다. 그 다음, 데이터 송수신 후 마무리 작업을 한다. {{{~cpp void CServerApp::SendData(CString& strData) { m_pChild->Send(LPCSTR(strData), strData.GetLength()+1); CString strText; UINT nPort; m_pChild->GetSockName(strText, nPort); strText = "[" + strText + "]" + strData; ((CListBox*)m_pMainWnd->GetDlgItem(IDC_LIST1))->InsertString(-1, strText); } void CServerApp::ReceiveData() { char temp[1000]; m_pChild->Receive(temp, sizeof(temp)); CString strText; UINT nPort; m_pChild->GetPeerName(strText, nPort); strText = "[" + strText + "]" + temp; ((CListBox*)m_pMainWnd->GetDlgItem(IDC_LIST1))->InsertString(-1, strText); } void CServerApp::CleanUp() { if (m_pChild) delete m_pChild; if (m_pServer) delete m_pServer; } void CServerApp::CloseChild() { AfxMessageBox("종료"); delete m_pChild; m_pMainWnd->GetDlgItem(IDC_SEND)->EnableWindow(FALSE); } }}} [리소스뷰]에서 다이얼로그 항목의 "IDD_SERVER_DIALOG"을 편집한다. {{{~cpp 리스트박스ID : IDC_LIST1 에디트 컨트롤ID : IDC_DATA 버튼 컨트롤ID : IDC_SEND }}} 다이얼로그가 초기화될 때 서버로 작동하도록 CServerDlg 클래스의 OnInit Dialog()함수에 다음 코드를 삽입한다. {{{~cpp ((CServerApp*)AfxGetApp)->InitServer(); GetDlgItem(IDC_SEND)->EnableWindow(FALSE); }}} 그리고 나서 [클래스위저드]의 CServerDlg에 IDC_SEND를 맵핑한 후 다음 코드를 추가한다. {{{~cpp CString strData; GetDlgItemText(IDC_DATA, strData); ((CServerApp*)AfxGetApp())->SendData(strData); SetDlgItemText(IDC_DATA,""); }}} === 예제(클라이언트) === 서버와 동일한 방법으로 클라이언트 프로그램에서 사용할 소켓 클래스 CClientSock을 생성(기초 클래스: CAsyncSocket)한다. 그리고 나서 [클래스위저드]의 CClientSock에 가상함수 OnReceive()와 OnClose()를 추가한 후, 다음 코드를 삽입한다. {{{~cpp ((CClientApp*)AfxGetApp())->CloseChild(); }}} {{{~cpp ((CClientApp*)AfxGetApp())->ReceiveData(); }}} 서버 접속시 필요한 IP 주소를 입력받기 위해 [리소스뷰]에서 다이얼로그를 하나 추가한 후 다음과 같이 편집한다. {{{~cpp IP주소 컨트롤ID : IDC_IPADDRESS1 버튼 ID : IDOK, IDCANCLE }}} [클래스위저드]를 실행하여 CConnectDlg 클래스를 새로 생성한 후 CConnectDlg 클래스에 다음 변수를 추가한다. {{{~cpp CString m_strAddress; }}} 그리고 [클래스위저드]의 CConnectDlg에 IDOK를 맵핑하여 사용자가 입력한 IP 주소를 멤버변수 m_strAddress에 저장한다. {{{~cpp GetDlgItemText(IDC_IPADDRESS1, m_strAddress); }}} 어플리케이션 클래스에 다음과 같이 멤버변수와 함수 원형을 선언한다. {{{~cpp #include "ClientSock.h" }}} {{{~cpp void Connect(); void SendData(CString& strData); void ReceiveData(); void CloseChild(); void CleanUp(); CClientSock* m_pClient; }}} 어플리케이션 클래스의 생성자에서 추가한 멤버변수를 초기화한다. {{{~cpp m_pClient = NULL; }}} 그리고 나서 추가한 멤버함수를 다음과 같이 정의한다. {{{~cpp #include "ConnectDlg.h" void CClientApp::Connect() { CConnectDlg dlg; if (dlg.DoModal() == IDOK) { m_pClient = new CClientSock; m_pClient->Create(); m_pClient->Connect(dlg.m_strAddress, 7000); m_pMainWnd->GetDlgItem(IDC_SEND)->EnableWindow(TRUE); m_pMainWnd->GetDlgItem(IDC_CONNECT)->EnableWindow(FALSE); } } }}} 이때 중요한 것은 서버 프로그램의 포트 번호(7000)와 설정할 포트 번호가 일치해야 한다. 이외의 데이터 송수신 과정과 마무리 작업은 서버 프로그램과 유사하다. {{{~cpp void CClientApp::SendData(CString& strData) { m_pClient->Send(LPCSTR(strData), strData.GetLength()+1); CString strText; UINT nPort; m_pClient->GetSockName(strText, nPort); strText = "[" + strText + "]" + strData; ((CListBox*)m_pMainWnd->GetDlgItem(IDC_LIST1))->InsertString(-1, strText); } void CClientApp::ReceiveData() { char temp[100]; m_pClient->Receive(temp, sizeof(temp)); CString strText; UINT nPort; m_pClient->GetPeerName(strText, nPort); strText = "[" + strText + "]" + temp; ((CListBox*)m_pMainWnd->GetDlgItem(IDC_LIST1))->InsertString(-1, strText); } void CClientApp::CleanUp() { if (m_pClient) delete m_pClient; } void CClientApp::CloseChild() { AfxMessageBox("종료"); m_pMainWnd->GetDlgItem(IDC_SEND)->EnableWindow(FALSE); } }}} [리소스뷰]에서 다이얼로그 항목의 "IDD_CLIENT_DIALOG"을 더블클릭한 후 다음과 같이 편집한다. {{{~cpp 리스트 ID : IDC_LIST1 에디트 박스ID : IDC_DATA 버튼 ID : IDC_SEND, IDC_CONNECT }}} [클래스위저드]의 CClientDlg에 IDC_CONNECT을 맵핑한 후 다음 코드를 삽입한다. {{{~cpp ((CClientApp*)AfxGetApp())->Connect(); }}} [클래스위저드]의 CClientDlg에 IDC_SEND를 맵핑한 후 다음 코드를 삽입한다. {{{~cpp CString strData; GetDlgItemText(IDC_DATA, strData); ((CClientApp*)AfxGetApp())->SendData(strData); SetDlgItemText(IDC_DATA,""); }}} === 참고 === http://www.rein.pe.kr/technote/read.cgi?board=programing&y_number=260&nnew=2 http://www.rein.pe.kr/technote/read.cgi?board=programing&y_number=261&nnew=2 http://www.rein.pe.kr/technote/read.cgi?board=programing&y_number=262&nnew=2 http://www.rein.pe.kr/technote/read.cgi?board=programing&y_number=263&nnew=2 http://165.194.17.15/pub/upload/p2pChattingProgram ---- [5인용C++스터디]