2.LAN을 위한 소켓 프로그램

본 장에서는 LAN에 연결된 서로 다른 컴퓨터에서 데이터를 전송하고 데이터를 받는 소켓 프로그램을 구현해 보겠습니다. WIN32를 이용하여 소켓 프로그램을 만들 때는 다소 복잡하던 것이 MFC의 CAsyncSocket이 만들어짐으로써 매우 간단하게 구현할 수 있게 되었습니다.

우리나라에서도 현재 2000년 초까지 완결 목표로 초고속 정보통신망을 설치하고 있습니다. 이 망은 광섬유를 이용하여 광통신을 하게 됩니다. 광섬유는 대역폭이 매우 크기 때문에 한 개의 라인으로 보통 10Gbps의 데이터 전송 능력을 가지고 있습니다. 일반 전화선으로 이용한다면 15만 전화 회선이죠. 이런 초고속 정보통신망이 연결된다면 아무래도 프로그래밍 자체가 한 컴퓨터에서 하기보다 분산 처리를 하는 경우가 늘어날 것 같습니다. 그렇기 때문에 네트워킹 프로그램이 매우 중요한 부분을 차지하게 됩니다.

윈도우에서 네트워킹을 할 수 있도록 하는 방법 중에 소켓이 가장 많이 활성화되어 있는 방법입니다. 이 방법을 익히고 나서 그 외 통신 프로토콜에 대하여 공부하면 많은 도움이 될 것 같습니다.

대상: 중급,고급

: 선택

공부방법: LAN을 이용한 프로그래밍을 하고자 할때 그때 예제를 한번 작성해 보세요. 내용이 어렵지 않기 때문에 쉽게 이해할수 있을것입니다.

1.소켓

소켓 프로그램의 실행은 STAND ALONE, 즉 LAN과 연결되지 않은 컴퓨터에서는 실행을 할 수가 없습니다. LAN이 연결된 컴퓨터에서 서로의 IP 주소를 찾아서 연결되기 때문에 소켓 프로그램은 주로 LAN에서나 또는 광역 LAN 즉 WAN에서 서로 다른 컴퓨터에 데이터를 전송하고 송신을 받는 데 사용합니다. 소켓(Socket)으로 데이터를 전송하고 데이터를 받는 데는 2가지 방법이 있습니다.

Stream Socket 방법과 Datagram Socket 방법입니다. Stream Socket 연결 방법은 서버가 실행되면서 클라이언트를 기다립니다. 이 때 클라이언트가 자신의 주소를 서버 프로그램에게 줍니다. 서버는 ID를 받고 클라이언트 컴퓨터에 접근하는 길을 설정합니다. 이렇게 함으로써 서버와 클라이언트가 완전히 온라인을 이루게 되어 통신이 시작되는 것입니다. Datagram Socket 연결 방법은 한 컴퓨터가 어떤 데이터를 담아서 특정 컴퓨터 IP 주소로 무조건 보내는 겁니다. 즉 상호 온라인 연결을 하지 않은 상태에서 무조건 상대 컴퓨터 IP 주소로 데이터가 달려가는 것이지요. 이렇게 하여 상대방 컴퓨터에 도달하는 방법입니다. Datagram Socket 방법은 데이터에 순차성이 없습니다. 그러니까 1번 데이터, 2번 데이터, 3번 데이터를 차례대로 특정 컴퓨터에 보낼 때 들어오는 데이터가 1번, 2번, 3번 이렇게 들어오는 것이 아니고 먼저 도착한 데이터가 먼저 들어오기 때문에 1, 3, 2 또는 3, 2, 1 등 순서가 마구 바뀌어서 들어올 수 있습니다. 그렇기 때문에 Datagram Socket은 데이터 헤더에 보내는 데이터의 몇 번째 블록이라는 것을 설정해 주고 상대 컴퓨터에 전송해야 합니다.

반면 Stream Socket은 온라인으로 연결되어 있는 상태에서 데이터를 보내기 때문에 1, 2, 3 순서로 데이터를 보내면 1, 2, 3 순서로 데이터를 받습니다.

Datagram Socket 방법은 서버가 많은 컴퓨터에 데이터를 전송할 때 사용하기에 편리한 방법입니다. 데이터를 완전하게 그리고 정확하게 보내기 위해서는 Stream Socket 방법이 낫다고 봅니다. 윈도에서 제공하는 Socket은 위의 두 가지 방법을 모두 사용할 수 있습니다. 여기서 사용하는 방법은 Stream Socket 방법입니다. 데이터를 정확하게 보내는 데 중요점을 둔 것입니다.

<1>소켓 프로그래밍 이란

소켓을 통해서 서로 통신을 하고자 할경우 LAN이 연결되어 있어야 합니다. 또는 전화접속 네트워킹을 통해서 가상적 형태의 LAN 이 연결되었을때만 가능한 프로그래밍입니다. 그럼 도대체 LAN이 연결되었을때가 무엇이며 이때 프로그래밍하는 것이 무엇인가? 이부분을 알기 위해서는 네트워킹에 대한 간단한 이론을 알아야 합니다. 통신이란 상호 대화를 의미합니다. 즉 이쪽에서 말하는 내용을 저쪽에서 듣고 어떤 내용인지 알고 대화하는 것을 통신이라고 합니다. 사람들이 서로 통신할때 언어가 같아야 서로 이해할수 있습니다. 한쪽에서는 한국말을 사용하는데 다른 한쪽에서는 영어를 사용한다면 서로 대화가 통하지 않습니다. 같은 언어를 사용하는 사람들끼리 서로 언어가 통하는 것입니다.  컴퓨터에서도 서로 통신을 할때 서로 같은 언어를 사용해야 합니다. 컴퓨터에서 통신할때 사용하는 언어를 프로토클이라고 합니다. 이프로토클이 같아야 서로 함께 통신을 할수 있는 것입니다. 이런 프로토클이 여러개가 있습니다. 그중에서 광범위하게 많이 쓰이는 프로토클이 TCP/IP 입니다. 이프로토클을 이용하여 FTP,HTTPD,TELNET 같은 상위 프로토클이 만들어진것입니다. TCT/IP는   하부계층부터 물리적계층,인터넷 계층,트랜스포트 계층,어플리케이션 계층으로 4개의 계층으로 나누어져 있습니다. 여기에서 트랜스포트 계층과 인터넷계층, 물리적 계층들은 OS또는 시스템에서 처리해주며 윈도우즈 어플리케이션에서 프로그래밍을 할때는 어플리케이션 계층만 이용합니다.  이 어플리케이션 계층에서 프로그래밍하는 것을 소켓 프로그래밍이라고 합니다. 소켓 프로그래밍을 할때는 통신하고자 하는 상대방에 접근하고자 할때 IP와 포트 번호를 이용합니다. IP는 접속하고자 하는 상대방 컴퓨터의 주소입니다. LAN으로 연결을 하였을경우 각컴퓨터에는 자신의 IP가 할당되어 있습니다. 제어판에서 네트워크를 선택하고 등록정보를 선택하고 프로토클를 선택한후 TCP/IP 항목에 보시면 자신의  IP 주소를 확인하실수 있을것입니다. 이주소를 이용하여 상대방 컴퓨터에 접근하는 것입니다. 상대방 컴퓨터에 접근한후 해당 포트로 가상 통신선을 연결합니다. 만일 현재 연결하고자 하는 포트가 10번이라면 이번호로 접근을 한다는 의미입니다. 그림 5-2-1은 TCP/IP 를 이용하여 서로 통신하는 형태입니다.

(그림 파일)

(그림 5-2-1)TCP/IP 를 이용한 통신

그림 5-2-1은 TCT/IP를 이용하여 통신하는 것중 많이쓰이는 통신 방법중에 FTP 와 텔넷 그리고 사용자가 만든 포트 번호를 이용하여 서버와 클라이언트가 서로 접속하는 형태를 나타낸 것입니다.  TCP/IP를 이용하여 TELNET로 접속하고자 한다면 포트 번호를 23을 사용해야 합니다.그림 5-2-1과 같이  ⓐ라는 클라이언트 컴퓨터가 ⓑ라는 컴퓨터에 TELNET를 이용하여 접속하고자 한다면 두개의 정보를 전달합니다. 첫번째는 자신의 IP이며 두번째는 포트번호입니다. 이때 포트번호는 23이어야 합니다. 국제 규약에서 TELNET는 포트번호를 23으로 설정하였기 때문입니다. 그림 5-2-1에서 텔넷으로 ⓑ컴퓨터에 접속할때 ⓐ컴퓨터는 이렇게 말하는것과 같습니다. □나 210.112.136.3 IP를 가지고 있는 컴퓨터인데 포트 23으로 접속좀 하자!□

이때 ⓑ컴퓨터는 이에 이렇게 응답을 하는 내용을 간단한 예로 이야기 해보겠습니다. □210.112.136.3 컴퓨터 내컴퓨터 IP 210.112.236.1 의 포트 23으로 접속하는 것을 환영한다! 그런데 23포트는 TELNET라는 언어를 쓴다. 이언어에 응답하라 먼저 인삿말을 보낸다. 이인삿말에 대답을 해라! 쑬라쑬라!□  만일 TELNET에서 서로 통신방법이 서버가 □쑬라쑬라!□ 했을경우 클라이언트는 □쌀라쌀라!□ 라고 대답을 해야 한다고 가정을 하였을때 접속하는 클라이언트 컴퓨터는 □쑬라쑬라!□ 라는 내용을 받고 바로 □쌀라쌀라!□ 해주어야 합니다. 이렇게 되면 서버에서 □음 TELNET 언어를 알고 있구나 그럼 서로 통신하자!□ 이렇게 되는 것입니다.

결국 처음에는 IP와 포트 번호로 접속을 하고 연결이 되면 또다른 프로토클의 언어로 서로 통신을 해야 한다는 것입니다. TELNET로 통신을 하고자 한다면 TELNET 규약을 알아야 하며, FTP로 통신을 하고자 한다면 FTP언어를 알아야 합니다.

FTP 클라이언트 프로그램이나 또는 TELNET 접속같은 프로그램은 TCP/IP로 서버에 접속한다음 해당 프로토클로 서로 통신을 할수 있는 형태이며 FTP서버나 TELNET 서버 프로그램은 해당 포트를 열고 기다리고 있다가 통신을 하는 프로그램입니다. 이렇게 본다면 소켓 프로그램을 할줄알고 서로 통신하는 규약만 알면 TELNET서버나,클라이언트 또는 FTP서버나 클라이언트 프로그램을 작성할수가 있습니다. 즉 소켓 프로그래밍은 인터넷에서 서로 통신하는 HTTP,FTP,TELNET등의 클라이언트와 서버를 제작할수 있는 가장 기본적인 프로그램이 된는 것입니다.

2.소켓 프로그래밍 클래스 CAsyncSocket

CAsyncSocket는 MFC로 소켓프로그래밍을 할때 가장 베이스 형태의 클래스입니다. 이클래스를 이용하여 소켓 프로그램을 작성할수가 있습니다. CAsyncSocket로 소켓 프로그래밍을 하는 방법은 그렇게 어렵지 않습니다. (과거 SDK로 프로그램 할때에 비해서) 본항목에서는 CAsyncSocket를 이용하여 소켓 프로그램을 작성하는 방법을 보겠습니다.

<1> CAsyncSocket을 이용하여 서버를 만들고 클라이언트를 만드는 OverView

서버와 클라이언트를 연결하는 방법을 총괄적으로 보면 그림 5-2-2과 같습니다.

(그림 파일)

(그림 5-2-2) CAsyncSocket을 이용한 통신구조

위의 그림에서 보듯이 서버는 소켓이 두 개 필요하고 클라이언트에서는 한 개만 필요합니다. 서버 영역에서 보면 제일 먼저 CAsyncSocket이라는 pSocket을 하나 설정합니다.

서버는 바로 정보를 제공하는 쪽입니다. 즉 □나는 이런 정보를 제공하겠다. 그리고 이 정보를 2000번 정보라고 설정하겠다□라고 해주면 2000번 정보가 필요한 클라이언트 컴퓨터가 연결하고자 할 것입니다. 이렇게 번호를 설정하는 것을 포트 번호라고 합니다. 보통 0번∼100번 까지의 몇 개 번호는 많이 사용하는 프로토클에서 사용하였기 때문에 쓰지 않는것이좋습니다.

pSocket에 2000번을 포트 번호로 부여한 다음 pSocket->Create(2000)라고 실행하면 서버가 만들어집니다. 이렇게 서버가 만들어진 다음 pSocket->Listen() 하면 클라이언트가 접속하기를 기다려 줍니다. 클라이언트가 접속을 하면 새로운 CAsyncSocket을 만듭니다. 이렇게 해서 만들어진 Sock에 현재 접속하자고 들어온 클라이언트를 연결시켜 줍니다.

pSocket->Accept(&Sock);

왜 새로운 소켓을 만드는 것일까요? 서버와 클라이언트가 1대 1 통신이 아니라 1대 다 통신을 할 수 있기 때문입니다. 그렇다면 새로운 클라이언트가 들어오나 안 오나를 체크해야 하고 기존에 들어온 클라이언트와는 통신을 해야 하고 이렇게 하기에는 한 클래스로는 힘들다는 것입니다.

메인소켓은 클라이언트가 접근을 하나 안 하나를 기다리고 있다가 새로운 클라이언트가 들어오면 자식 소켓을 만들어서 □너는 지금 들어온 이 클라이언트와 통신을 해라!□ 하고 일임한 후에 다시 새로운 클라이언트가 들어오는 것을 기다려야 하기 때문에 새로운 소켓을 만드는 것이죠.

이제 클라이언트와 연결된 모든 주권자는 Sock입니다. 이 Sock에 데이터가 들어오면

Sock->Receive(data,datalen); 해주어서 데이터를 받고

만약 보낼 데이터가 있으면 Sock->Send(data,datalen); 해주어서 클라이언트에 보내면 됩니다.

클라이언트 영역은 서버보다는 단순합니다. 우선 메인소켓을 하나 만들고

CAsyncSocket pSocket;

자신이 클라이언트라고 설정하여 만듭니다. 이 때는 pSocket->Create()를 하면 됩니다. 이제 서버와 연결을 해야겠지요. 2000번의 포트로 연결을 하고자 할 때 자신의 주소와 함께 포트 번호를 인자에 넣어 서버에 보냅니다. 만일 자신의 주소가 □120.130.32.2□이라고 할 경우

pSocket->Connect("120.130.32.2",2000);

이라고 하여 연결을 합니다. 연결이 끝난 다음 데이터를 받고 데이터를 주는 방법은 Receive와 Send 함수를 사용하면 됩니다. 이렇게 함으로써 네트워킹을 할 수 있게 되는 것이죠. 이것만 설명해도 프로그램하기는 쉽겠지요?

여기서 의문점이 없나요? 지금까지 설명에서 한 가지 의문점이 있을텐데...

<2>이벤트는 어떻게 알 수 있는가?

서버가 Listen() 함수를 실행시켜서 클라이언트를 기다리고 있을 때 클라이언트가 접속을 하면 어떻게 알 수가 있을까요? 또한 본 컴퓨터로 어떤 데이터가 들어왔을 때 어떻게 알 수가 있을까요?

여기에서 CAsyncSocket 클래스의 특정 멤버 함수를 볼까 합니다.

OnAccept : 클라이언트가 Connect를 실행하여 서버에 접속하고자 할 때 서버에서는 새로운 클라이언트가 들어오면  OnAccept 함수가 실행됩니다.

OnReceive : 새로운 데이터가 들어왔을 경우에는 OnReceive라는 함수가 실행됩니다.

즉 새로운 클라이언트가 접속을 실행하면 메시지를 날려 주는 것이 아니라 OnAccept 함수가 실행되는 것입니다. 또한 새로운 데이터가 들어왔을 경우에는 OnReceive 함수가 실행되고요.

그렇다면 어떻게 OnAccept 함수나 OnReceive 함수를 건드릴 수 있을까요? 이 때 객체 지향 언어의 특징인 over ride를 사용합니다.

CMySocket::public CAsyncSocket{

:

public:

virtual void OnAccept( int nErrorCode );

virtual void OnReceive( int nErrorCode );

:

};

CAsyncSocket의 클래스를 상속받은 CMySocket에 virtual 함수로 OnAccept와 OnReceive 함수를 만들어 놓으면 새로운 클라이언트가 들어온 것을 알 수도 있으며 또한 새로운 데이터가 들어온 것을 알 수도 있지요.

void CMySocket::OnAccept(int nErrorCode)

{

//이곳에서 연결하고자 하는 클라이언트가 왔을 때 새로운 연결을 해준다.

}

void CMySocket::OnReceive(int nErrorCode)

{

//이곳에서 데이터가 들어왔을 때 Receive 함수를 실행하거나 다른 방법으로 데이터를 받는다.

}

이와 같이 하여 통지(notification)을 받아서 실행할 수 있게 됩니다.

<3> 서버 클래스와 클라이언트 클래스 만들기

앞의 내용을 보면 소켓은 새로운 클래스를 만들고 상속을 받아서 사용하기가 편합니다. 아니 상속을 받아서 사용을 해야 하겠지요.

여기서는 소켓 프로그램을 이용할 때 단순히 연결하고 데이터를 보내고 받는 부분만을 알아보기로 합니다. 그렇다면 클라이언트 클래스는 어떤 일을 하면 될까요? 연결을 시도하고 나서는 데이터를 받고 보내는 일만 하겠지요. 그런 클라이언트 클래스를 CClientSocket로 만들었습니다.

/////////////////////////////////////////////////////////////////////////////

//clientsok.h

//클라이언트용 소켓 프로그램 헤더

#define WM_RECEIVE_DATA WM_USER+2

class CClientSock : public CAsyncSocket

{

public:

CClientSock();

//메시지를 전달할 HWND를 설정하는 함수

void SetWnd(HWND hwnd);

//새로운 데이터가 들어왔을 때 실행되는 함수

virtual void OnReceive( int nErrorCode );

public:

//메시지를 전달할 HWND

HWND m_pHwnd;

};

/////////////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////////////

//Clientsok.cpp

//클라이언트용 소켓 클래스 함수

#include "stdafx.h"

#include "ClientSok.h"

//생성자는 아무일도 안 하고 모든 생성을 CAsyncSocket에 넘겨준다.

CClientSock::CClientSock()

{

CAsyncSocket::CAsyncSocket();

}

//데이터가 들어왔을 때 현재 hwnd에 새로운 데이터가 들어

//왔다는 메시지를 날려 주기 위한 hwnd를 설정한다.

void CClientSock::SetWnd(HWND hwnd)

{

m_pHwnd=hwnd;

}

//새로운 데이터가 들어왔을 때 현재의 hwnd에

//USER+2인 메시지 WM_RECEIVE를 보낸다.

void CClientSock::OnReceive( int nErrorCode )

{

TRACE("Errocode = %d",nErrorCode);

SendMessage(m_pHwnd,WM_RECEIVE_DATA,0,0);

}

/////////////////////////////////////////////////////////////////////////////

서버 클래스는 제일 먼저 클라언트를 기다린 다음 새로운 클라이언트가 오면 클라이언트와 같은 클래스를 만들어서 링크시켜 주면 되지요. 이런 내용의 클래스를 CSeverSocket으로 만들었습니다.

///////////////////////////////////////////////////////////////////

//sversok.h

//서버용 메인소켓 프로그램

#include "clientsok.h"

#define  WM_ACCEPT_SOCKET WM_USER+1

class CSeverSock : public CAsyncSocket

{

public:

CSeverSock();

//메시지를 전달할 HWND 설정

void SetWnd(HWND hwnd);

//현재 클라이언트와 연결된 소켓을 받는다.

CClientSock* GetAcceptSocket();

//새로운 클라이언트가 연결되었을 때

virtual void OnAccept( int nErrorCode );

public:

//새로운 클라이언트가 연결되었을 때

//Accept할 소켓 클래스 변수

CClientSock  m_pChild;

HWND m_pHwnd;

};

/////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////

//SverSok.cpp

//서버 클래스 함수 소스

#include "stdafx.h"

#include "sversok.h"

//생성자

CSeverSock::CSeverSock()

{

CAsyncSocket::CAsyncSocket();

}

//메시지를 전달할 윈도우 설정

void CSeverSock::SetWnd(HWND hwnd)

{

m_pHwnd=hwnd;

}

//새로운 클라이언트가 연결되었을 때

//실행되는 함수

void CSeverSock::OnAccept( int nErrorCode )

{

TRACE("Errocode = %d",nErrorCode);

//클라이언트 클래스인 m_pChild로 연결시켜 준다.

Accept(m_pChild);

//새로운 클라이언트가 연결되었다는

//WM_ACCEPT_SOCKET 메시지를 전달한다.

SendMessage(m_pHwnd,WM_ACCEPT_SOCKET,0,0);

}

//OnAccept 함수 실행 후 새로운 클라이언트와 연결된

//m_pChild 소켓을 넘겨준다.

CClientSock* CSeverSock::GetAcceptSocket()

{

return &m_pChild;

}

/////////////////////////////////////////////////////////////////////////

<4> 서버 프로그램 Ex3101 만들기

이제 서버 프로그램을 만들어 보겠습니다. 서버 프로그램은 Ex3101입니다. 이 프로그램은 SDI로 CEx3101View가 CFormView 클래스로 설정되어 있습니다. 클라이언트에서 들어온 데이터가 보여지는 에디트 상자와 클라이언트에 보낼 데이터를 기록하는 에디트 상자 2개를 설정해 놓고 보낼 데이터를 기록하는 에디트 상자에 글자를 입력한 후 보내기 버튼을 눌러서 클라이언트에 데이터를 전송하는 프로그램입니다.

이 프로그램에서 위에서 만든 CSeverSocket와 CClientSocket 두 개를 사용했습니다. 뷰가 제일 먼저 업데이트될 때 다음과 같이 하여 클라이언트를 기다립니다.

void CEx3101View::OnInitialUpdate()

{

CFormView::OnInitialUpdate();

//서버 소켓을 만든다.

m_pSeverSock = new CSeverSock;

//서버 소켓의 현재 뷰의 hwnd를 설정한다.

//이렇게 설정해 놓아야 현 뷰로 메시지가 전달됨.

m_pSeverSock->SetWnd(this->m_hWnd);

// 2000번 포트 번호로 소켓을 만든다.

m_pSeverSock->Create(2000);

//클라이언트가 오기를 기다린다.

m_pSeverSock->Listen();

}

새로운 클라이언트가 들어올 때 CSeverSocket은 WM_ACCEPT_SOC- KET(WM_USER／1)인 메시지를 발생시킵니다. 이 메시지가 발생될 때 실행하는 함수 OnAccept가 있다고 한다면 우선 BEGIN_MESSAGE_MAP에 다음과 같이 설정합니다.

ON_MESSAGE(WM_ACCEPT_SOCKET,OnAccept)

헤더에는 다음과 같이 설정합니다.

afx_msg LONG OnAccept(UINT,LONG);

이렇게 설정을 하고 OnAccept 함수를 만듭니다.

//새로운 클라이언트가 접속했을 때

LONG CEx3101View::OnAccept(UINT wParam,LONG lParam)

{

//새로운 클라이언트와 연결할 소켓을 만들고

m_pChildSock = new CClientSock;

//서버 소켓으로부터 현재 연결된 소켓을

//받는다.

m_pChildSock = m_pSeverSock->GetAcceptSocket();

//hwnd 설정

m_pChildSock->SetWnd(this->m_hWnd);

char temp[200];

//□안녕하세요□라는 데이터를 보낸다.

sprintf(temp,"안녕하세요?");

m_pChildSock->Send((LPSTR)temp,200);

return TRUE;

}

위와 마찬가지로 새로운 데이터가 들어왔을 때 발생하는 메시지 WM_RECEIVE_ DATA와 연결될 함수 OnReceive를 만듭니다.

//새로운 데이터가 들어왔을 때

LONG CEx3101View::OnReceive(UINT wParam,LONG lParam)

{

char temp[200];

//소켓으로부터 데이터를 받은 다음

m_pChildSock->Receive((LPSTR)temp,200);

//화면과 연결된 m_strReceive에 설정한다.

m_strReceive=temp;

UpdateData(FALSE);

return TRUE;

}

<5>프로그램 소스

(프로그램 소스시작)

// Ex3101Vw.h : interface of the CEx3101View class

//

///////////////////////////////////////////////////////////////////////////

class CEx3101View : public CFormView

{

protected: // create from serialization only

CEx3101View();

DECLARE_DYNCREATE(CEx3101View)

public:

//{{AFX_DATA(CEx3101View)

enum{ IDD = IDD_EX3101_FORM };

// NOTE: the ClassWizard will add data members here

//}}AFX_DATA

// Attributes

public:

CEx3101Doc* GetDocument();

CSeverSock *m_pSeverSock;

CClientSock *m_pChildSock;

CString m_strReceive;

CString m_strSend;

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CEx3101View)

public:

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

virtual void OnInitialUpdate();

protected:

virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support

virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);

virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);

virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);

virtual void OnPrint(CDC* pDC, CPrintInfo*);

//}}AFX_VIRTUAL

// Implementation

public:

virtual ~CEx3101View();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:

// Generated message map functions

protected:

//{{AFX_MSG(CEx3101View)

afx_msg void OnSend();

afx_msg LONG OnAccept(UINT,LONG);

afx_msg LONG OnReceive(UINT,LONG);

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

#ifndef _DEBUG  // debug version in Ex3101Vw.cpp

inline CEx3101Doc* CEx3101View::GetDocument()

{ return (CEx3101Doc*)m_pDocument; }

#endif

///////////////////////////////////////////////////////////////////////////

// Ex3101Vw.cpp : implementation of the CEx3101View class

//

#include "stdafx.h"

#include "Ex3101.h"

#include "Ex3101Dc.h"

#include "sversok.h"

#include "Ex3101Vw.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

///////////////////////////////////////////////////////////////////////////

// CEx3101View

IMPLEMENT_DYNCREATE(CEx3101View, CFormView)

BEGIN_MESSAGE_MAP(CEx3101View, CFormView)

//{{AFX_MSG_MAP(CEx3101View)

ON_BN_CLICKED(IDC_SEND, OnSend)

//}}AFX_MSG_MAP

// Standard printing commands

ON_COMMAND(ID_FILE_PRINT, CFormView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_DIRECT, CFormView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_PREVIEW, CFormView::OnFilePrintPreview)

ON_MESSAGE(WM_ACCEPT_SOCKET,OnAccept)

ON_MESSAGE(WM_RECEIVE_DATA,OnReceive)

END_MESSAGE_MAP()

///////////////////////////////////////////////////////////////////////////

// CEx3101View construction/destruction

CEx3101View::CEx3101View()

: CFormView(CEx3101View::IDD)

{

//{{AFX_DATA_INIT(CEx3101View)

// NOTE: the ClassWizard will add member initialization here

//}}AFX_DATA_INIT

// TODO: add construction code here

}

CEx3101View::~CEx3101View()

{

}

void CEx3101View::DoDataExchange(CDataExchange* pDX)

{

CFormView::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CEx3101View)

// NOTE: the ClassWizard will add DDX and DDV calls here

//}}AFX_DATA_MAP

DDX_Text(pDX,IDC_RECEIVE,m_strReceive);

DDX_Text(pDX,IDC_SENDDATA,m_strSend);

}

BOOL CEx3101View::PreCreateWindow(CREATESTRUCT& cs)

{

// TODO: Modify the Window class or styles here by modifying

//  the CREATESTRUCT cs

return CFormView::PreCreateWindow(cs);

}

///////////////////////////////////////////////////////////////////////////

// CEx3101View printing

BOOL CEx3101View::OnPreparePrinting(CPrintInfo* pInfo)

{

// default preparation

return DoPreparePrinting(pInfo);

}

void CEx3101View::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add extra initialization before printing

}

void CEx3101View::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add cleanup after printing

}

void CEx3101View::OnPrint(CDC* pDC, CPrintInfo*)

{

// TODO: add code to print the controls

}

///////////////////////////////////////////////////////////////////////////

// CEx3101View diagnostics

#ifdef _DEBUG

void CEx3101View::AssertValid() const

{

CFormView::AssertValid();

}

void CEx3101View::Dump(CDumpContext& dc) const

{

CFormView::Dump(dc);

}

CEx3101Doc* CEx3101View::GetDocument() // non-debug version is inline

{

ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CEx3101Doc)));

return (CEx3101Doc*)m_pDocument;

}

#endif //_DEBUG

///////////////////////////////////////////////////////////////////////////

// CEx3101View message handlers

//새로운 클라이언트가 접속했을 때

LONG CEx3101View::OnAccept(UINT wParam,LONG lParam)

{

//새로운 클라이언트와 연결할 소켓을 만들고

m_pChildSock = new CClientSock;

//서버 소켓으로부터 현재 연결된 소켓을 받는다.

m_pChildSock = m_pSeverSock->GetAcceptSocket();

//hwnd 설정

m_pChildSock->SetWnd(this->m_hWnd);

char temp[200];

//□안녕하세요□라는 데이터를 보낸다.

sprintf(temp,"안녕하세요?");

m_pChildSock->Send((LPSTR)temp,200);

return TRUE;

}

//새로운 데이터가 들어왔을 때

LONG CEx3101View::OnReceive(UINT wParam,LONG lParam)

{

char temp[200];

//소켓으로부터 데이터를 받은 다음

m_pChildSock->Receive((LPSTR)temp,200);

//화면과 연결된 m_strReceive에 설정한다.

m_strReceive=temp;

UpdateData(FALSE);

return TRUE;

}

//데이터 보내기 버튼을 눌렀을 때

void CEx3101View::OnSend()

{

UpdateData(TRUE);

//에디트에 입력한 데이터를 받아서

char temp[200];

lstrcpy((LPSTR)temp,(LPSTR)m_strSend.operator const char*());

//클라이언트에 보낸다.

m_pChildSock->Send(temp,200);

}

void CEx3101View::OnInitialUpdate()

{

CFormView::OnInitialUpdate();

//서버 소켓을 만든다.

m_pSeverSock = new CSeverSock;

//서버 소켓의 현재 뷰의 hwnd를 설정한다.

//이렇게 설정해 놓아야 현 뷰로 메시지가 전달됨.

m_pSeverSock->SetWnd(this->m_hWnd);

// 2000번 포트 번호로 소켓을 만든다.

m_pSeverSock->Create(2000);

//클라이언트가 오기를 기다린다.

m_pSeverSock->Listen();

}

(프로그램 소스끝)

<6>클라이언트 프로그램 Ex3102

클라이언트 프로그램이 서버 프로그램과 다른 면이 있다면 소켓을 만들고 연결을 시도하는 것뿐입니다. 그 외 데이터를 받거나 데이터를 보내는 것은 서버와 같지요. 서버 프로그램은 클라이언트와 접속하기 위해 현재 컴퓨터의 IP 주소를 물어 봅니다. 이 IP 주소를 정확하게 입력하였을 경우에만 서버와 연결됩니다.

void CEx3102View::OnInitialUpdate()

{

CFormView::OnInitialUpdate();

m_pClientSock=new CClientSock;

//IP 주소를 묻는 대화상자가 실행된다.

CSetIp *pDlg = new CSetIp;

if(pDlg->DoModal()==IDOK)

{

char temp[80];

lstrcpy((LPSTR)temp,(LPSTR)pDlg->m_strIp.operator const char*());

//temp에 입력한 IP 주소를 설정한다.

m_pClientSock->Create();

//IP 주소와 함께 포트 번호 2000을 설정하여

//클라이언트에 접속을 시도한다.

m_pClientSock->Connect(temp,2000);

//메시지를 받을 HWND 설정

m_pClientSock->SetWnd(this->m_hWnd);

}

}

<7>프로그램 소스

(프로그램 소스시작)

// Ex3102Vw.h : interface of the CEx3102View class

//

///////////////////////////////////////////////////////////////////////////

class CEx3102View : public CFormView

{

protected: // create from serialization only

CEx3102View();

DECLARE_DYNCREATE(CEx3102View)

public:

//{{AFX_DATA(CEx3102View)

enum { IDD = IDD_EX3102_FORM };

//}}AFX_DATA

// Attributes

public:

CEx3102Doc* GetDocument();

CClientSock *m_pClientSock;

CString m_strReceive;

CString m_strSend;

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CEx3102View)

public:

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

virtual void OnInitialUpdate();

protected:

virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support

virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);

virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);

virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);

virtual void OnPrint(CDC* pDC, CPrintInfo*);

//}}AFX_VIRTUAL

// Implementation

public:

virtual ~CEx3102View();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:

// Generated message map functions

protected:

//{{AFX_MSG(CEx3102View)

afx_msg void OnSend();

afx_msg LONG OnReceive(UINT,LONG);

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

#ifndef _DEBUG  // debug version in Ex3102Vw.cpp

inline CEx3102Doc* CEx3102View::GetDocument()

{ return (CEx3102Doc*)m_pDocument; }

#endif

///////////////////////////////////////////////////////////////////////////

// Ex3102Vw.cpp : implementation of the CEx3102View class

//

#include "stdafx.h"

#include "Ex3102.h"

#include "Ex3102Dc.h"

#include "clientsok.h"

#include "Ex3102Vw.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

///////////////////////////////////////////////////////////////////////////

// CEx3102View

IMPLEMENT_DYNCREATE(CEx3102View, CFormView)

BEGIN_MESSAGE_MAP(CEx3102View, CFormView)

//{{AFX_MSG_MAP(CEx3102View)

ON_BN_CLICKED(IDC_SEND, OnSend)

//}}AFX_MSG_MAP

// Standard printing commands

ON_COMMAND(ID_FILE_PRINT, CFormView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_DIRECT, CFormView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_PREVIEW, CFormView::OnFilePrintPreview)

ON_MESSAGE(WM_RECEIVE_DATA,OnReceive)

END_MESSAGE_MAP()

///////////////////////////////////////////////////////////////////////////

// CEx3102View construction/destruction

CEx3102View::CEx3102View()

: CFormView(CEx3102View::IDD)

{

//{{AFX_DATA_INIT(CEx3102View)

// NOTE: the ClassWizard will add member initialization here

//}}AFX_DATA_INIT

// TODO: add construction code here

}

CEx3102View::~CEx3102View()

{

}

void CEx3102View::DoDataExchange(CDataExchange* pDX)

{

CFormView::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CEx3102View)

// NOTE: the ClassWizard will add DDX and DDV calls here

//}}AFX_DATA_MAP

DDX_Text(pDX,IDC_RECEIVE,m_strReceive);

DDX_Text(pDX,IDC_SENDDATA,m_strSend);

}

BOOL CEx3102View::PreCreateWindow(CREATESTRUCT& cs)

{

// TODO: Modify the Window class or styles here by modifying

//  the CREATESTRUCT cs

return CFormView::PreCreateWindow(cs);

}

///////////////////////////////////////////////////////////////////////////

// CEx3102View printing

BOOL CEx3102View::OnPreparePrinting(CPrintInfo* pInfo)

{

// default preparation

return DoPreparePrinting(pInfo);

}

void CEx3102View::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add extra initialization before printing

}

void CEx3102View::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add cleanup after printing

}

void CEx3102View::OnPrint(CDC* pDC, CPrintInfo*)

{

// TODO: add code to print the controls

}

///////////////////////////////////////////////////////////////////////////

// CEx3102View diagnostics

#ifdef _DEBUG

void CEx3102View::AssertValid() const

{

CFormView::AssertValid();

}

void CEx3102View::Dump(CDumpContext& dc) const

{

CFormView::Dump(dc);

}

CEx3102Doc* CEx3102View::GetDocument() // non-debug version is inline

{

ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CEx3102Doc)));

return (CEx3102Doc*)m_pDocument;

}

#endif //_DEBUG

///////////////////////////////////////////////////////////////////////////

// CEx3102View message handlers

LONG CEx3102View::OnReceive(UINT wParam,LONG lParam)

{

char temp[200];

m_pClientSock->Receive((LPSTR)temp,200);

m_strReceive=temp;

UpdateData(FALSE);

return TRUE;

}

void CEx3102View::OnSend()

{

UpdateData(TRUE);

char temp[200];

lstrcpy((LPSTR)temp,(LPSTR)m_strSend.operator const char*());

m_pClientSock->Send(temp,200);

}

#include "setip.h"

void CEx3102View::OnInitialUpdate()

{

CFormView::OnInitialUpdate();

m_pClientSock=new CClientSock;

//IP 주소를 묻는 대화상자가 실행된다.

CSetIp *pDlg = new CSetIp;

if(pDlg->DoModal()==IDOK)

{

char temp[80];

lstrcpy((LPSTR)temp,(LPSTR)pDlg->m_strIp.operator const char*());

//temp에 입력한 IP 주소를 설정한다.

m_pClientSock->Create();

//IP 주소와 함께 포트 번호 2000을 설정하여

//클라이언트에 접속을 시도한다.

m_pClientSock->Connect(temp,2000);

//메시지를 받을 HWND 설정

m_pClientSock->SetWnd(this->m_hWnd);

}

}

(프로그램 소스끝)

(그림 파일)

(그림 5-2-3)Ex3101과 Ex3102 프로그램 실행결과

(낙서)

90년대 초에 우리나라에서 광통신이 활성화되어 한국통신 산하 모연구소에서 광섬유를 이용하여 데이터를 전송하고 받는 시스템을  연구하고 있었습니다. 그러나 광섬유 연구에 대한 정부의 견해가 반반이었습니다. 즉 광섬유 연구를 하는 데 비용이 너무 많이 든다! 보류하자 하는 쪽과 초고속 정보통신망을 하는 데 필수적이기 때문에 해야 한다 하는 쪽이죠. 정부에서는 결론을 내리기 전에 우선 연구소를 시찰하자고 결론냈습니다.

해서 정부는 대통령을 수반하고 그 연구소를 시찰나왔습니다. 광섬유 개발에 자금을 지원해야 하는가 마는가를 결론짓기 위해서죠.

연구소 소장은 목에 핏줄이 서도록 자신의 연구소에서 하는 일과 광통신의 중요성 그리고 광섬유로 하면 왜 좋은지를 설명했습니다. 한참 듣고 있던 모대통령이 한마디 했다고 합니다.

□그거! 염색하기 힘들지?□