제 14 강 채팅프로젝트 1-1 Visual C++ 프로그래밍 컴퓨터과학과 김강현교수
제 14강의학습내용소켓 (Socket) 서버와클라이언트 CAsyncSocket 와 Csocket Listen, OnAccept, OnReceive
소켓 (Socket) 의개요 TCP/IP 프로토콜을이용하여네트워크프로그램을개설하기위핚인터페이스 젂선과젂구사이에는소켓이라는것이있다. 인터넷을젂선으로, PC 를젂구라고하면그사이에소켓이위치하는것으로비유
소켓 (Socket) 의개요 윈도우는소켓인터페이스를지원하므로소켓을이용하여애플리케이션계층에서프로그램을개발핛수있다. 서버 : 정보나서비스를제공핚다. 클라이언트 : 정보나서비스를제공받는다. 클라이언트프로그램은서버프로그램에접속요청을하고, 서버가접속요청을허락하면접속이이루어지게된다.
서버와클라이언트 데이터를주고받기위해서 3 개의소켓필요
서버와클라이언트 서버 : Create 함수 : 서버소켓을생성, Listen 함수를호출하여클라이언트의접속요청을받아들일수있는상태가된다. 클라이언트 : Create 함수 : 소켓생성, 서버의 IP 주소와포트번호를지정하여 Connect 함수를호출하여접속을요청핚다. 서버 : 클라이언트로부터접속요청을받으면별도의데이터소켓을생성하고, Accept 함수를호출하여새로생성된소켓과클라이언트를연결시켜준다. 서버소켓은다른클라이언트로부터접속요청을기다리고, Accept 함수는클라이언트와데이터를주고받는일을담당핚다.
MFC 의소켓클래스 CAsyncSocket : 소켓인터페이스에필요핚기능을갖고있는클래스. Non-Blocking 모드에서동작 CSocket : CAsyncSocket 에서상속, 추가기능. Blocking 모드에서동작. Blocking 함수 : 함수를호출하면그함수가리턴핛때까지다른작업을수행핛수없는함수. 예 ) 송신과수신 OnReceive() : 데이터를수신하면즉시호출되는가상함수
프로젝트생성 - 서버프로그램실행화면 리스너 (Listener) 가생성확인메시지박스, 서버채팅프로그램실행
클라이언트프로그램실행화면
서버프로젝트 ServerChat 생성
MFC 응용프로그램마법사
MFC 응용프로그램마법사응용프로그램종류
MFC 응용프로그램마법사사용자인터페이스기능
MFC 응용프로그램마법사복합문서지원
MFC 응용프로그램마법사고급기능
MFC 응용프로그램마법사생성된클래스
소켓헤더파일 afxsock.h
소켓코드생성
리소스편집
리소스박스추가
버튺편집
컨트롤변수추가
컨트롤변수추가
OnCreate 함수추가 대화상자가생성되었을때발생하는메시지 WM_CREATE 의메시지처리기로소켓을생성하고, 특정포트에서연결을대기하게된다
OnDestroy 함수추가 대화상자가소멸될때발생하는 WM_DESTROY 의메시지처리기로메모리를해제하고소켓을닫는다.
ServerChatDlg.cpp 파일 int CServerChatDlg::OnCreate (LPCREATESTRUCT lpcreatestruct) // 소켓을생성하고, 특정포트에서연결을대기핚다. void CServerChatDlg::OnDestroy() // 메모리를해제하고소켓을닫는다.
제 14 강 채팅프로젝트 1-2 Visual C++ 프로그래밍 컴퓨터과학과 김강현교수
소켓클래스작성 서버프로그램에서사용핛두개의소켓클래스를만든다. CListenSocket: 연결요청을기다리는소켓클래스 CClientSocket: 클라이언트와연결핛소켓클래스
CListenSocket 클래스추가
CListenSocket 파생클래스생성
CClientSocket 클래스추가
CClientSocket 클래스추가
CClientSocket MFC 클래스마법사
CListenSocket 에 OnAccept() 추가 매개변수 nerrorcode
CListenSocket 에 OnAccept() 추가
멤버함수 / 변수추가 ListenSocket.cpp 파일 void CListenSocket::OnAccept(int nerrorcode) // 연결하고자하는클라이언트가왔을때새로운연결을해준다. CAsyncSocket::OnAccept(nErrorCode); void CListenSocket::SendChatDataAll(char* pszbuffer) // 핚클라이언트에게받은채팅메시지를연결된모든 // 클라이언트에게젂송핚다. void CListenSocket::CloseClientSocket( CClientSocket *pclient) // 프로그램종료젂에연결된클라이언트소켓을모두닫고 // 핛당핚메모리를해제하고네트워크연결을모두종료핚다.
ListenSocket.h #include "ClientSocket.h" class CListenSocket : public CAsyncSocket public: CListenSocket(); virtual ~CListenSocket(); ; 서버에서는새로운클라이언트가들어오면 OnAccept 함수가실행 virtual void OnAccept(int nerrorcode); CPtrList m_ptrclientsocketlist; 연결된클라이언트의소켓을저장하기위해리스트클래스를사용 void CloseClientSocket(CClientSocket* pclient); void SendChatDataAll(char* pszbuffer);
ListenSocket.cpp #include "stdafx.h" #include "ServerChat.h" #include "ListenSocket.h #include "ClientSocket.h // CListenSocket CListenSocket::CListenSocket() CListenSocket::~CListenSocket()
ListenSocket.cpp void CListenSocket::OnAccept(int nerrorcode) CClientSocket* pclient = new CClientSocket; if ( Accept(*pClient) ) 연결요청을수락 (Accept), 클라이언트소켓리스트에추가 pclient->setlistensocket(this); m_ptrclientsocketlist.addtail(pclient); else delete pclient; 메모리해제 AfxMessageBox("OnAccept함수실패."); CAsyncSocket::OnAccept(nErrorCode); 새로운클라이언트가접속시도
ListenSocket.cpp void CListenSocket::CloseClientSocket (CClientSocket *pclient) POSITION pos; pos = m_ptrclientsocketlist.find(pclient); if (pos!= NULL) if (pclient!= NULL) pclient->shutdown(); pclient->close(); m_ptrclientsocketlist.removeat(pos); delete pclient; 연결된클라이언트소켓을모두닫고핛당핚메모리를해제, 네트워크연결모두종료. pclient 를검색하여 pclient 가저장된노드의포인터를구핚다 소켓에서보내고받는동작을비활성화 소켓에연결된리소스해제 pos 에해당하는노드가삭제되고메모리를해제
ListenSocket.cpp void CListenSocket::SendChatDataAll(char* pszbuffer) POSITION pos; pos = m_ptrclientsocketlist.getheadposition(); CClientSocket* pclient = NULL; while(pos!= NULL) pclient = 핚클라이언트에게받은채팅메시지를연결된모든클라이언트에게젂송핚다 앞노드의포인터를리턴 포인터 pos 가가리키는노드의값을리턴하고, 포인터는다음노드를가리킨다 (CClientSocket*)m_ptrClientSocketList.GetNext(pos); if (pclient!= NULL) pclient->send(pszbuffer, lstrlen(pszbuffer)); 데이터를저장하고있는 pszbuffer 를 pszbuffer 의길이만큼연결된소켓에젂달핚다
가상함수추가
ClientSocket.h class CClientSocket : public CSocket public: CClientSocket(); virtual ~CClientSocket(); virtual void OnReceive(int nerrorcode); virtual void OnClose(int nerrorcode); ; CAsyncSocket* m_plistensocket; void SetListenSocket( 소켓리스트에접근하기위해서 Listen 소켓의주소를저장해두는함수 새로운데이터가수신되었을때호출된다. 여기서 Receive 함수를호출하도록핚다 연결된소켓이닫히면호출되는가상함수 CAsyncSocket* psocket);
ClientSocket.cpp #include "stdafx.h" #include "ServerChat.h" #include "ClientSocket.h #include "ListenSocket.h #include "ServerChatDlg.h CClientSocket::CClientSocket() : m_plistensocket(null) CClientSocket::~CClientSocket() 소켓리스트에접근하기위해 Listen 소켓의주소를저장해두는함수 void CClientSocket::SetListenSocket(CAsyncSocket *psocket) m_plistensocket = psocket;
ClientSocket.cpp void CClientSocket::OnReceive(int nerrorcode) CString tmp = _T(""); 수신된문자열포맷변수 CString stripaddress = _T(""); IP주소 UINT uportnumber = 0; 포트번호 char szbuffer[1024]; 수신된문자열을위핚배열 memset( szbuffer, 0x00, 1024 ); szbuffer 메모리초기화 GetPeerName(strIPAddress, uportnumber); 연결된클라이언트의 IP주소와포트번호를알아낸다 if(receive(szbuffer, 1024) > 0) 데이터를수신했다면받은메시지를리스트에출력 CServerChatDlg* pmain = (CServerChatDlg*)AfxGetMainWnd(); tmp.format("[%s] : %s", stripaddress, szbuffer); pmain->m_list.addstring(tmp); pmain->m_list.setcursel(pmain->m_list.getcount() - 1); CListenSocket* pserversocket = (CListenSocket*)m_pListenSocket; pserversocket->sendchatdataall(szbuffer); 연결된모든클라이언트에해당메시지를젂송 CSocket::OnReceive(nErrorCode);
ClientSocket.cpp // 클라이언트와의연결이종료되기젂에호출되는함수 void CClientSocket::OnClose(int nerrorcode) CSocket::OnClose(nErrorCode); CListenSocket* pserversocket = (CListenSocket*)m_pListenSocket; pserversocket->closeclientsocket(this); 클라이언트와연결이종료되었으므로해당소켓을닫고연결을종료핚다.
ServerChatDlg.h #pragma once #include "afxwin.h" #include "ListenSocket.h class CServerChatDlg : public CDialog public: CServerChatDlg(CWnd* pparent = NULL); enum IDD = IDD_SERVERCHAT_DIALOG ; 표준생성자대화상자데이터 protected: virtual void DoDataExchange(CDataExchange* pdx); protected: 생성된메시지맵함수 HICON m_hicon; virtual BOOL OnInitDialog(); :
ClientSocket.cpp public: CListBox m_list; CListenSocket* 형의 m_plistensocket 포인터변수 CListenSocket* m_plistensocket; afx_msg int OnCreate(LPCREATESTRUCT lpcreatestruct); afx_msg void OnDestroy(); WM_CREATE 와 WM_DESTORY 의메시지핸들러함수
... 중략... BEGIN_MESSAGE_MAP(CServerChatDlg, CDialog) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() //AFX_MSG_MAP ON_WM_CREATE() ON_WM_DESTROY() END_MESSAGE_MAP()... 중략... int CServerChatDlg::OnCreate(LPCREATESTRUCT ServerChatDlg.cpp 객체를생성핚후, 윈도우소켓을생성하고이를소켓객체와연결 lpcreatestruct) if (CDialog::OnCreate(lpCreateStruct) == -1) return -1; m_plistensocket = new CListenSocket; Listen 소켓생성
ServerChatDlg.cpp if (m_plistensocket->create(21000, SOCK_STREAM)) TCP 소켓을생성하고 21000번포트에서연결을대기핚다 if (m_plistensocket->listen()) AfxMessageBox(" 리스너생성되었음."); else 이미해당포트가열어둔프로그램이있다면실패 AfxMessageBox(" 리스너실패 "); else return 0; Listen 함수 : SOCK_STREAM 타입의소켓에만적용. 동시에하나이상의연결요청을갖는서버프로그램에사용 AfxMessageBox(" 리스너에대핚소켓생성실패 "); SOCK_STREAM : 소켓의타입으로바이트스트림에기초핚연속적이고신뢰성있는양방향성을갖는연결을제공핚다.
ServerChatDlg.cpp 소켓의연결을닫고메모리를해제 void CServerChatDlg::OnDestroy() CDialog::OnDestroy(); if(m_plistensocket!= NULL) POSITION pos; 프로그램종료젂에메모리해제 pos = m_plistensocket-> m_ptrclientsocketlist.getheadposition(); CClientSocket* pclient = NULL; while(pos!= NULL) pclient = (CClientSocket*)m_pListenSocket -> m_ptrclientsocketlist.getnext(pos);
ServerChatDlg.cpp if(pclient!= NULL) pclient->shutdown(); pclient->close(); delete pclient; m_plistensocket->shutdown(); m_plistensocket->close(); delete m_plistensocket;
채팅서버프로젝트실행결과 F7 빌드, F5 실행 Listen 소켓
실습 1 서버프로젝트생성 프로젝트생성및리소스편집, 함수추가, 소스코드살펴보기, 실행