ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 소켓과 MFC를 이용한 채팅 프로그램 개발하기 - 서버편
    Windows 프로그래밍 2019. 3. 6. 10:28

    소켓은 내 컴퓨터가 상대방과 통신하기 위해 사용하는 통로(?) 라고 볼 수 있다.

    소켓은 있는데 왜 말켓, 소Dog은 없지


    이런 소켓을 이용한 채팅 프로그램을 만들어 볼 것이다.


    <소켓 서버 완성본>


    <소켓 클라이언트 완성본>

    레알 16강탈락 경★축☆


    전체적인 흐름은 소켓 서버와 소켓 클라이언트가 존재하고, 서버는 클라이언트가 보내는 메시지를 다른

    클라이언트들에게 모두 전달하는 역할을 수행하게 할 것이다.


    먼저 소켓 서버를 만들어보자.

    소켓 서버에는 클라이언트의 메시지를 기다리며 계속 대기하는 CListenSocket 클래스, 클라이언트의 추가 등

    클라이언트와 관련된 작업을 수행하는 CClientSocket 클래스 이렇게 2가지 클래스를 생성한다.


    그리고 이 클래스들을 생성할 때는 아래와 같이 CSocket 클래스를 기본 클래스에 추가해주어야 한다.


    먼저 CListenSocket 클래스의 코드를 보자.

    #include "stdafx.h" #include "SocketServer.h" #include "CListenSocket.h" #include "CClientSocket.h" #include "SocketServerDlg.h" CListenSocket::CListenSocket() { } CListenSocket::~CListenSocket() { } void CListenSocket::OnAccept(int nErrorCode) // 사용자가 추가되었을때 { CClientSocket* pClient = new CClientSocket; CString str; if (Accept(*pClient)) { pClient->SetListenSocket(this); m_ptrClientSocketList.AddTail(pClient); CSocketServerDlg* pMain = (CSocketServerDlg*)AfxGetMainWnd(); str.Format(_T("Client (%d)"), (int)m_ptrClientSocketList.Find(pClient));

    pMain->clientList->AddString(str);

    // Client 리스트에 추가 } else { delete pClient; AfxMessageBox(_T("ERROR : Failed can't accept new Client!")); } CAsyncSocket::OnAccept(nErrorCode); } void CListenSocket::CloseClientSocket(CSocket* pClient) // Client 접속이 끊겼을때 { POSITION pos; pos = m_ptrClientSocketList.Find(pClient); if (pos != NULL) { if (pClient != NULL) { pClient->ShutDown(); pClient->Close(); } CSocketServerDlg* pMain = (CSocketServerDlg*)AfxGetMainWnd(); CString str1, str2; UINT indx = 0, posNum; pMain->clientList->SetCurSel(0); while (indx < pMain->clientList->GetCount()) { posNum = (int)m_ptrClientSocketList.Find(pClient); pMain->clientList->GetText(indx, str1); str2.Format(_T("%d"), posNum);

    if (str1.Find(str2) != -1) {

    // 리스트에서 연결종료된 client와 고유번호가 같은 항목을 찾아

    AfxMessageBox(str1 + str2); pMain->clientList->DeleteString(indx);

    // Client 리스트에서 삭제 break; } indx++; } m_ptrClientSocketList.RemoveAt(pos); delete pClient; } } void CListenSocket::SendAllMessage(TCHAR* pszMessage) // Client가 보낸 메시지가 있을 때 { POSITION pos; pos = m_ptrClientSocketList.GetHeadPosition(); CClientSocket* pClient = NULL; while (pos != NULL) { pClient = (CClientSocket*)m_ptrClientSocketList.GetNext(pos); if (pClient != NULL) { int checkLenOfData = pClient->Send(pszMessage, lstrlen(pszMessage) * 2);

    // 클라이언트들에게 메시지 전송

    if (checkLenOfData != lstrlen(pszMessage) * 2) { AfxMessageBox(_T("일부 데이터가 정상적으로 전송되지 못했습니다!")); } } } }

    뭐 웬만한 설명은 주석에 있으니 하지 않아도 될 것 같다.

    아 맞다. 여기서 쓰인 멤버함수들은 꼭 헤더파일에 선언해주는걸 잊으면 안된다.


    그럼 이제 CClientSocket 클래스를 보자.

    // ClientSocket.cpp : 구현 파일입니다. // #include "stdafx.h" #include "SocketServer.h" #include "CClientSocket.h" #include "CListenSocket.h" #include "SocketServerDlg.h" // CClientSocket int index = 0; CString alias[100][2]; CClientSocket::CClientSocket() { } CClientSocket::~CClientSocket() { } // CClientSocket 멤버 함수 void CClientSocket::SetListenSocket(CAsyncSocket* pSocket) // 새로 접속한 소켓 값을 넣는다 { m_pListenSocket = pSocket; } // CClientSocket 멤버 함수 void CClientSocket::OnClose(int nErrorCode) // 연결이 종료되었을때 소켓을 닫는다 { CSocket::OnClose(nErrorCode); CListenSocket* pServerSocket = (CListenSocket*)m_pListenSocket; pServerSocket->CloseClientSocket(this); } void CClientSocket::OnReceive(int nErrorCode) // 소켓에 데이터가 들어왔을 때 { int i, check = 0; CString strTmp = _T(""), strIPAddress = _T(""); UINT uPortNumber = 0; TCHAR strBuffer[1024]; ::ZeroMemory(strBuffer, sizeof(strBuffer)); GetPeerName(strIPAddress, uPortNumber);

    // ip주소와 포트번호를 받고

    if (Receive(strBuffer, sizeof(strBuffer)) > 0) { CSocketServerDlg* pMain = (CSocketServerDlg*)AfxGetMainWnd(); CString portStr; portStr.Format(L"%d", uPortNumber); for (i = index - 1; i >= 0; i--) { if (portStr == alias[i][0]) { // 사용자가 지정한 이름과 매칭 strTmp.Format(_T("[%s]: %s"), alias[i][1], strBuffer);

    break; } } if (i == -1) { strTmp.Format(_T("[%s:%d]: %s"), strIPAddress, uPortNumber, strBuffer); }

    // 출력하고 싶은 모양대로 가공하고 if (strTmp.Find(L"alias:") != -1) { // 이름 설정하는 신호였다면 alias[index][0] = portStr; // 이름 저장 CString temp = strBuffer; temp.Delete(0, 6); alias[index][1] = temp; index++; check = 1; } if (check == 0) { // 이름 설정하는 신호가 아니라면 (메시지 전송) int cnt = pMain->m_List.GetCount(); pMain->m_List.InsertString(cnt, strTmp); CListenSocket* pServerSocket = (CListenSocket*)m_pListenSocket; TCHAR *tChr = (TCHAR*)(LPCTSTR)strTmp; pServerSocket->SendAllMessage(tChr);

    // CListenSocket의 SendAllMessage 함수 call } else { check = 0; } } CSocket::OnReceive(nErrorCode); }

    이제 메인 Dialog에서 소켓 생성만 해주면 된다.

    clientList = (CListBox*)GetDlgItem(IDC_CLIENT_LIST); if (m_ListenSocket.Create(21000, SOCK_STREAM)) { // 소켓생성 if (!m_ListenSocket.Listen()) { AfxMessageBox(_T("ERROR:Listen() return False")); } } else { AfxMessageBox(_T("ERROR:Failed to create server socket!")); }

    아 그리고 Dialog를 종료할때 Socket을 삭제해 주는것도 잊으면 안된다. WM_DESTROY 메시지 처리기인 OnDestroy 함수를

    만든 후 아래와 같은 코드를 추가하면 된다.

    void CSocketServerDlg::OnDestroy()
    {
    	CDialogEx::OnDestroy();
    
    	POSITION pos;
    
    	pos = m_ListenSocket.m_ptrClientSocketList.GetHeadPosition();
    	CClientSocket* pClient = NULL;
    
    	while (pos != NULL) {
    		pClient = (CClientSocket*)m_ListenSocket.m_ptrClientSocketList.GetNext(pos);
    		if (pClient != NULL) {
    			pClient->ShutDown();
    			pClient->Close();
    
    			delete pClient;
    		}
    	}
    	m_ListenSocket.ShutDown();
    	m_ListenSocket.Close();
    	// TODO: 여기에 메시지 처리기 코드를 추가합니다.
    }

    서버 부분 구현은 이걸로 끝이다. 다음 글에선 클라이언트를 구현해보도록 하자!!


    --- 헤더파일 ---

    SocketServer.zip


    댓글

Designed by Tistory.