티스토리 뷰

일반적으로 소켓 프로그램은 서비스를 요청하는 클라이언트측과  클라이언트로부터의요청을 받아 서비스하는 서버측, 이렇게 두 곳에 상주하는 프로그램으로 구성된다. 우리가 구현할 소켓 스포르갬은 c언어로 작성했던 한 줄 짜리 문자열 출력 프로그램에 네트워크 기능을 부여하여 호스트사이세서 서로 통신하며 자료를 송수신할 수 있게 한것이다. 즉 클라이언트 프로그램이 네트워크상에서 통신 채널을 통해 서버측에 연결되면 서버 프로그램은 즉시 문자열 hello world를 클라이언트에게 전송하고 클라이언트 프로그램은 전송받은 문자열을 화면에 출력한다.


일반 프로그램에서는 문자열  출력이 한 컴퓨터에서 이루어졌던 것에 비해 소켓프로그램에서는 네트워크상에 있는 호스트 간에 문자열을 받아 출력하기 때문에 소켓과 같은 네트워크 연결 장치가 필요하다. 그리고 호스트 간에 서로 연결된 이후에는 소켓을 통해문자열을 송수신하느 코그다 있어야한다.


1. 연결 요청: 클라이언트 프로그램은 소켓 API 한수를 호출하여 서버 프로그램에 연결을 요청한다.

2. 문자열 전송 : 연결 요청을 받은 서버 프로그램은 클라이언트 프로그램과 연결되자마자 문자열 HELLO, WORLD를 클라이언트에게 전송한다.

3. 화면 출력 : 클라이언트 프로그램은 전송받은  문자열을 자신의 화면에 출력한다.


1번과 3번 과정은 클라이언트가 수행하는 기능이고 서버는 2번을 수행한다.




Windows 기반 MFC Socket 프로그램 구현

MS 윈도우 기반의 소켓 프로그램은 Win32 API 함수를 이용해서 개발하거나 MFC LIBㄹ을 이용해서 개발한다. MFC 라이브러리를 이용해보겠다.


MFC는 소켓과 관련해서 두 개의 클래스를 제공한다.

CObject

-> CAsyncSocket

-> CSocket


CObject 클래스에 상속된 CAsyncSocket 클래스는 비동기 소켓을 지원한다. 여기서 비동기 소켓이란 송수신 함수 등을 호출할 때호출하자마자 바로반환하고 다음 코드를 실행하는 소켓을 말한다. -> 비동기


CSocket은 CAsyncSocet 클래스를 상속한 클래스로 동기 소켓을 지원한다. 비동기 소켓과 달리 동기 소켓은 송수신 함수 등을 호출할 때 함수가 내부 코드를 모두 수행할때까지 반환핮지 않고 기다린다. -> 동기



클라이언트 프로그램

프로젝트명 : HelloClient

대화상자 기반, 고급기능에서 windows 소켓 체크


GUI 설계


컨트롤 

ID 

변수 

Caption 

Static Text 

IDC_STATIC_STATUS 

m_static_status 

서버가 보내준 자료를 출력합니다. 

Button Control 

IDC_BUTTON_CONNECT 

 

연결 




HelloClientDlg.h

#pragma once #include "afxwin.h" // CmfcsocketDlg dialog class CmfcsocketDlg : public CDialogEx { // Construction public: CmfcsocketDlg(CWnd* pParent = NULL); // standard constructor // Dialog Data #ifdef AFX_DESIGN_TIME enum { IDD = IDD_MFCSOCKET_DIALOG }; #endif protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support // Implementation protected: HICON m_hIcon; // Generated message map functions virtual BOOL OnInitDialog(); afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); DECLARE_MESSAGE_MAP() public: afx_msg void OnStnClickedStaticStatus(); CStatic m_static_status; public: afx_msg void OnBnClickedButtonConnect(); };

mfc 마법사를 통해 스태틱 컨트롤과 버튼 컨트롤에 멤버변수와 헨들러함수를 등록한다.






HelloClientDlg.cpp

void CmfcsocketDlg::OnBnClickedButtonConnect()

{

// TODO: Add your control notification handler code here

CSocket socket;

socket.Create();

socket.Connect(_T("127.0.0.1"), 9000);


int cbRcvd;

char buffer[1024];

CString strBuffer = _T("");


if ((cbRcvd = socket.Receive(buffer, 1024)) > 0) {

strBuffer = (LPCSTR)(LPSTR)buffer;

//strBuffer.Format("%s", buffer);

m_static_status.SetWindowText(strBuffer.Left(cbRcvd));

}

socket.Close();

}

등록한 버튼 클릭 함수를 구현한다.


위의 연결 버튼을 클릭하게 되면 OnBnClickedButtonConnect() 가 실행된다. 소켓 프로그램을 위해 CSocket 클래스의 socket 개개체를 생성해서 서버와 연결하고 서버로부터 문자열을 받아온다.


1. socket.create() 함수를 호출해서 소켓을 생성하고

2. socket.connect() 함수를 호출해서 IP주소와 포트를 통해 연결을 시도한다. ( 지금은 로컬호스트 주소 )

3. socket.receive() 함수를 호출해서 서버에서 전달한 문자열을 수신한다.





서버 프로그램

프로젝트명 : HelloServer

대화상자 기반, 고급기능에서 windows 소켓 체크


GUI 설계


 컨트롤

ID 

변수 

CAPTION 

Static Text 

IDC_STATIC_STATUS 

m_static_status 

Server Stop 

Button Control

IDC_BUTTON_START  

m_button_start 

시작 




1. CListenSocket 클래스 생성 (base CSockt 상속)

- 클라이언트의 연결 요청을 받으면 OnAccept 메세지 처리기가 실행되어 연결 요청을 처리한다.


ListenSocket.h

#pragma once #include "afxsock.h" class CHelloServerDlg; class CListenSocket : public CSocket { public: CListenSocket(CHelloServerDlg* pHelloServerDlg); ~CListenSocket(); CHelloServerDlg* m_pHelloServerDlg; public: virtual void OnAccept(int nErrorCode); // 속성창을 이용하면 자동으로 함수 등록가능 };

등록한 버튼 클릭 함수를 구현한다.



ListenSocket.cpp

#include "stdafx.h" #include "ListenSocket.h" #include "HelloServerDlg.h" CListenSocket::CListenSocket(CHelloServerDlg* pHelloServerDlg) { m_pHelloServerDlg = pHelloServerDlg; } CListenSocket::~CListenSocket() { } void CListenSocket::OnAccept(int nErrorCode) { // TODO: Add your specialized code here and/or call the base class m_pHelloServerDlg->ProcessAccept(); CSocket::OnAccept(nErrorCode); }

생성자 파라메터를 바꿔주고 OnAccept() 함수를 통해 인자로 받은 dlg 변수의 주소를 m_pHelloServerDlg 변수에 담는다.

클라이언트로부터 연결 요청이 오면 메시지 처리기 OnAccept가 호출된다. OnAccept 메세지 처리기에서는 연결 처리를 직접 하지 않고 이를 대화 상자 CHelloServerDlg 객체에서 처리하도록 CHelloServerDlg 객체의 메서드 ProcessAccept를 호출한다. 





2. CServiceSocket 클래스 생성 (base CSockt 상속)

- 클라이언트의 연결 요청을 받아들이고 해당 클라이언트에 대한 처리를 수행할 CServiceSocket객체를 생성





ServiceSocket.h

#pragma once #include "afxsock.h" class CHelloServerDlg; class CServiceSocket : public CSocket { public: CServiceSocket(CHelloServerDlg* pHelloServerDlg); ~CServiceSocket(); CHelloServerDlg* m_pHelloServerDlg; };

리슨소켓과 마찬가지로 메인 다이얼로그를 참조하기위해 변수선언 및 생성자 인자 수정을 해준다.




ServiceSocket.cpp

#include "stdafx.h" #include "ServiceSocket.h" #include "HelloServerDlg.h" CServiceSocket::CServiceSocket(CHelloServerDlg* pHelloServerDlg) { m_pHelloServerDlg = pHelloServerDlg; } CServiceSocket::~CServiceSocket() { }

ServiceSocket.h

#pragma once #include "afxsock.h" class CHelloServerDlg; class CServiceSocket : public CSocket { public: CServiceSocket(CHelloServerDlg* pHelloServerDlg); ~CServiceSocket(); CHelloServerDlg* m_pHelloServerDlg; };

리슨소켓과 마찬가지로 메인 다이얼로그를 참조하기위해 변수선언 및 생성자 인자 수정을 해준다.




HelloServerDlg.cpp

#pragma once #include "afxwin.h" #include "ListenSocket.h" #include "ServiceSocket.h" // CHelloServerDlg dialog class CHelloServerDlg : public CDialogEx { // Construction public: CHelloServerDlg(CWnd* pParent = NULL); // standard constructor // Dialog Data #ifdef AFX_DESIGN_TIME enum { IDD = IDD_HELLOSERVER_DIALOG }; #endif protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support // Implementation protected: HICON m_hIcon; // Generated message map functions virtual BOOL OnInitDialog(); afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); DECLARE_MESSAGE_MAP() public: CStatic m_static_status; CButton m_button_start; public: CListenSocket* m_pListenSocket; CServiceSocket* m_pServiceSocket; void ProcessAccept(); afx_msg void OnBnClickedButtonsStart(); };

헤더파일에 구현에 필요한 변수와 함수들을 선언해준다. 

OnBnClickButtonStart는 이벤트 처리 마법사를 이용하여 등록한다.



ServiceSocket.cpp

void CHelloServerDlg::ProcessAccept() { m_pServiceSocket = new CServiceSocket(this); char sndBuffer[] = "Hello, world"; if (m_pListenSocket->Accept(*m_pServiceSocket)) { m_static_status.SetWindowText(_T("Acceepted")); m_pServiceSocket->Send(sndBuffer, (int)strlen(sndBuffer)); m_static_status.SetWindowText(_T("Send Hello, World")); } else { delete m_pServiceSocket; } } void CHelloServerDlg::OnBnClickedButtonsStart() { // TODO: Add your control notification handler code here UINT nPort = 9000; m_pListenSocket = new CListenSocket(this); m_static_status.SetWindowText(_T("Create Listen Socket")); if (!m_pListenSocket->Create(nPort)) { m_static_status.SetWindowText(_T("Cannot Create Socket")); return; } else { m_static_status.SetWindowText(_T("Socket Create success")); if (!m_pListenSocket->Listen()) { m_static_status.SetWindowText(_T("can not listen")); return; } } }

위의 두개의 함수를 구현한다.


시작 버튼을 누르게 되면 CListenSocket 객체를 생성하고 CListenSocket 객체의 Create 메서드를 호출해서 클라이언트로부터 연결 요청을 받을 듣기 소켓을 생성한다. 그런 다음 CListenSockt 객체의 Listen 메서드를 호출해서 클라이언트로부터 연결 요청을 받을 수있는 LISTEN 상태가 된다.


클라이언트로부터 연결 요청이 외면 CListenSocket 개개체의 OnAccept 메서드가 실행된다. CListenSocket 객체의 OnAccept 메서드에서 클라이언트와의 연결처리를 수행하지 않고, 대화 상자 CHelloServerDlg 객체에서 일괄 처리할 수 있도록 CHelloServerDlg 객체의 ProcessAccept 메서드를 호출한다. CHelloServerDlg 객체의 ProcessAceept 메서드에서는 클라이언트와 연결하여 자료를 송수신할 CServiceSocket 객체를 생성하고, CListenSocket 객체의 Accept 메서드를 호출해서 CServiceSocket 객체와 클라이언트를 연결한다. 그런다음 CServicesOCKET 객체의 Send 메서드를 통해 즉시 클라이언트로 문자열 hello, word를 전송한다.