본문 바로가기
C & C++/MFC Network

[Web] 소켓 프로그래밍

by izen8 2011. 4. 25.
반응형

소켓 프로그래밍

1. Socket

일반적으로 네트워크 프로그래밍이라고 하면 TCP/IP 소켓 프로그래밍을 의미합니다.

소켓은 파일의 개념과 유사합니다.

NIC를 추상화 한 개념이 소켓입니다.

소켓 핸들을 열어서 입출력을 하게 됩니다.

소켓을 사용한 경우에는 반드시 닫아 주어야 합니다.

 

1) Socket관련 API함수

Socket(): 소켓 생성

Htonl(): 바이트 순서 변환(Host -> Network)

ntohl(): 바이트 순서 변환(Network->Host)

bind(): 소켓과 Port번호 연결

listen(): 접속 요청을 대기

accept(): 접속 요청을 받아들이고 새로운 소켓을 생성하여 접속 요청에 연결

send(): 데이터 전손

recv(): 데이터를 수신

connect(): 지정된 접속 정보를 이용하여 접속을 요청

inet_addr(): IP 주소를 인터넷 주소로 변경

inet_ntoa(): 인터넷 주소를 IP주소로 변경

shutdown(): 접속 종료

closesocket(): 소켓 식별자를 해제

 


 

2) 소켓 클래스의 종류

- CAsyncSocket: 비동기 적으로 동작하는 소켓

접속 시점을 알 수 없을 때 사용하는 소켓입니다.

서버는 거의 대부분 CAsyncSocket 왜냐하면 서버는 클라이언트와의 접속 여부에 상관없이 자신의 처리를 수행해야 하기 때문입니다.

채팅이나 메신저 같은 프로그램은 대부분 이 소켓을 이용합니다.

 

 

생성자

CAsyncSocket( ): 디폴트 생성자

 

BOOL Create(UINT nSocketPort = 0, int nSocketType = SOCK_STREAM,

long lEvent = FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE,

LPCTSTR lpszSocketAddress = NULL)

nSocketPort: 포트 번호

nSocketType: 소켓 종류

        SOCK_STREAM: 데이터가 보낸 순서대로 도착

        SOCK_DGRAM: 데이터가 보낸 순서대로 도착하지 안을 수도 있음

lEvent: 사용할 기능에 대한 옵션

lpszSocketAddress: IP주소

 

멤버 변수

m_hSocket: 소켓 핸들

 

멤버 함수

Accept(): 접속 요청을 받아들임

BOOL Attach(SOCKET hSocket, long lEvent = FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE): 소켓을 연결할 때 사용

Bind(): 소켓과 통신 주소 연결

Close(): 소켓 종료

Connect(): 서버에게 접속 요청

Detach(): 소켓을 해제하고자 할 때 사용

FromHandle(): 소켓 핸들 리턴

Listen(): 클라이언트의 접속 요청을 대기

Receive(): 데이터를 수신

ReceiveFrom(): 데이터를 수신

Send(): 데이터를 송신

SendTo(): 데이터를 송신

ShutDown(): 데이터 송수신을 Disable

OnAccept(): 클라이언트의 접속을 받을 수 있는 조건이 될 때 호출

OnClose(): 소켓이 종료되었을 때 호출

OnConnect(): 접속요청이 완료되었을 때 호출

OnReceive(): 데이터가 수신될 때 호출

OnSend(): 데이터가 송신될 때 호출

 

- CSocket

동기적으로 동작하는 소켓으로 CAsyncSocket으로부터 상속됩니다.

접속 시점을 알 때 사용하는 소켓으로 동기화와 같은 문제를 전혀 고려하지 않아도 되는 소켓으로 파일전송 등에 많이 이용

 

 

프로젝트 생성 시 고급 기능에서 Windows 소켓 항목을 체크하면 연결 헤더 파일 인클루드를 AfxSocketInit() 함수로 처리 가능

 


 

간단한 채팅 프로그램 만들기

1. CFormView 기반으로 프로젝트를 생성(고급 기능에서 Windows 소켓 사용) - ChatTest

 

2. 대화상자 디자인

현재 상태 – IDC_STATE

EDIT1: IDC_EDIT_THATIP

EDIT2: IDC_EDIT_SEND

ListBox: IDC_LIST_MES`

 

3. 소켓 클래스로부터 상속받는 3개의 소켓 클래스 추가

CWaitSocket

CServerSocket

CClientSocket

 

4. CWaitSocket의 헤더파일에 Doc 클래스와 View 클래스의 헤더파일 추가하고 변수 선언

#include "ChatTestDoc.h"

#include "ChatTestView.h"

CChatTestView *m_pView;
5. CWaitSocket
클래스의 OnAccept 함수 재정의

m_pView->OnAccept();

 

6. CClientSocket의 헤더파일에 Doc 클래스와 View 클래스의 헤더파일 추가하고 변수 선언

 #include "ChatTestDoc.h"

#include "ChatTestView.h"

CChatTestView *m_pView;

 

7. CClientSocket 클래스의 OnReceive 함수 재정의

m_pView->OnReceive();

 

8. CServerSocket에도 6번과 7번 작업 수행

 

9. View 클래스에 3개의 헤더파일을 인클루드하고 전역 변수 선언

#include "ClientSocket.h"

#include "ServerSocket.h"

#include "WaitSocket.h"

 

CClientSocket *g_pClientSoc;

CWaitSocket *g_pWaitSoc;                    

CServerSocket *g_pServerSoc;         

int g_State;  

 

10. View 클래스의 OnInitialUpdate 함수에서 초기화

SetDlgItemText(IDC_STATE, _T("현재오프라인상태입니다"));

 

11. View 클래스의 리스트 박스에 변수 연결

Control Type으로 m_ScrList

 

12. 접속대기 버튼의 BN_CLICKED 메시지 작성

g_pWaitSoc=new CWaitSocket();

        g_pWaitSoc->m_pView=this;

        g_pWaitSoc->Create(2000);

        g_pWaitSoc->Listen();

        g_State=1;

        SetDlgItemText(IDC_STATE, _T("다른컴퓨터의접속을대기중입니다"));

13.지금 접속 버튼의 BN_CLICKED 메시지 작성

BOOL result;

        TCHAR str[128];

        g_pClientSoc=new CClientSocket();

        g_pClientSoc->m_pView=this;

        g_pClientSoc->Create();

        GetDlgItemText(IDC_EDIT_THATIP,str,128);

        if (lstrlen(str) == 0) {

               MessageBox(_T("접속대상컴퓨터의IP를입력?"),_T("에러"),MB_OK);

               return;

        }

        result=g_pClientSoc->Connect(str,2000);

        if (result==TRUE) {

               SetDlgItemText(IDC_STATE, _T("클라이언트로접속"));

               g_State=3;

        }

        else {

               MessageBox(_T("접속실패"),_T(""),MB_OK);

               SetDlgItemText(IDC_STATE, _T("다른컴퓨터의접속을대기중입니다"));

               g_State=0;

        }

 

14.View 클래스의 OnAccept()함수 선언

void CChatTestView::OnAccept(void)

{

        SetDlgItemText(IDC_STATE, _T("서버로연결되었습니다."));

        g_pServerSoc=new CServerSocket;

        g_pServerSoc->m_pView=this;

        g_pWaitSoc->Accept(*g_pServerSoc);

        g_State=2;

}


 

15. 보내기 버튼의 BN_CLICKED 메시지 작성

TCHAR str[256];

        TCHAR str2[256];

        GetDlgItemText(IDC_EDIT_SEND,str,256);

        if (g_State==2) {

               g_pServerSoc->Send(str,256);

        }

        else {

               g_pClientSoc->Send(str,256);

        }

        wsprintf(str2, _T("보냄: %s"), str);

        m_ScrList.AddString(str2);

 

16.View 클래스에 OnReceive 함수 선언

void CChatTestView::OnReceive(void)

{

       

        TCHAR str[256];

        TCHAR str2[256];

        if (g_State==2) {

               g_pServerSoc->Receive(str,256);

        }

        else if (g_State==3) {

               g_pClientSoc->Receive(str,256);

        }

        wsprintf(str2, _T("받음: %s"), str);

        m_ScrList.AddString(str2);

}

 

17. View 클래스의 WM_DESTROY 메시지 작성

if (g_pWaitSoc)

               delete g_pWaitSoc;

        if (g_pClientSoc)

               delete g_pClientSoc;

        if (g_pServerSoc)

               delete g_pServerSoc;


 

 파일 전송 프로그램 만들기

1. 서버 만들기

1) Dialog 기반으로 프로젝트 생성(FileServer) -  고급 기능에서 Windows sockets에 체크

 

2) 다이얼로그 디자인

 

EDIT – IDC_MESSAGE – Cstring m_Message

서버시작 버튼: IDC_BTN_STARTSERVER

서버중단 버튼:IDC_BTN_STOPSERVER

 

3) 다이얼로그 클래스의 OnInitDialog에 작성(버튼 초기화)

GetDlgItem(IDC_BTN_STARTSERVER)->EnableWindow(TRUE);

GetDlgItem(IDC_BTN_STOPSERVER)->EnableWindow(FALSE);

 

4) 다이얼로그 클래스의 구현 파일 상단에 전역 변수 선언

struct sockaddr_in     svr_myaddr_in;

SOCKET svr_ls;

 


 

5) 서버를 시작하기 위한 멤버 함수 선언

BOOL CFileServer1Dlg::ServerStart(void)

{

        CString msg;

        memset((char *)&svr_myaddr_in,   0, sizeof(struct sockaddr_in));

        svr_myaddr_in.sin_family = AF_INET;

        svr_myaddr_in.sin_addr.s_addr = INADDR_ANY;

        svr_myaddr_in.sin_port = htons(9014);

 

        svr_ls = socket(PF_INET, SOCK_STREAM, 0);

 

        if(svr_ls == -1)

        {

               msg.Format(_T("소켓생성실패."));

               AfxMessageBox(msg);

               return FALSE;

        }

 

        if(bind(svr_ls, (struct sockaddr *)&svr_myaddr_in, sizeof(svr_myaddr_in)) == -1)

        {

               msg.Format(_T("bind() 함수실행실패."));

               AfxMessageBox(msg);

               return FALSE;

        }

 

        m_Message.Format(_T("서버대기중..."));

        UpdateData(FALSE);

 

        m_SvrService = (CSvrService*)AfxBeginThread(RUNTIME_CLASS(CSvrService), THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);

        m_SvrService->SetServerInfo(svr_ls);

        m_SvrService->ResumeThread();

 

        return TRUE;

}

6)CWinThread로부터 상속받는 클래스 생성(CSvrService)

 

7) CSvrService클래스에 변수 선언

SOCKET svr_ls;

SOCKET svr_s;

sockaddr_in svr_peeraddr_in;

 

8) CSvrService 클래스에 서버 정보를 설정할 함수 선언

void CSvrService::SetServerInfo(SOCKET svr_ls)

{

        this->svr_ls = svr_ls;

}

 


 

9) CSvrService 클래스의 Run 함수 재정의

int addrlen;

        CString msg;

        for(;;)

        {

               if(listen(svr_ls, 5) == -1)

               {

                       msg.Format("listen() 함수실행실패.");

                       AfxMessageBox(msg);

                       return CWinThread::Run();

               }

               addrlen = sizeof(struct sockaddr_in);               

        svr_s = accept(svr_ls, (struct sockaddr *)&svr_peeraddr_in, &addrlen);

               if(svr_s == -1)

               {

                       msg.Format("accept() 함수실행실패.");

                       AfxMessageBox(msg);

               }

               else

               {

                       AfxMessageBox("수신성공");

                       Service();

 

                       closesocket(svr_s);

               }

        }

 

        return CWinThread::Run();


 

10) CSvrService 클래스에 실제 데이터 통신을 수행 할 멤버 함수 구현

void CSvrService::Service(void)

{

        CFile* pFile = NULL;

        char recv_msg[1001];

        char tmp[101];

        char filename[257];

        int  filenamelen;

        int  filelen;

        int  recv_cnt;

        char path[256];

        char buf[11];

        recv(svr_s, recv_msg, 256, 0);

        strncpy(filename, recv_msg, 256);

        recv(svr_s, recv_msg, 10, 0);

        strncpy(tmp, recv_msg, 10);

        tmp[10] = '\0';

        filenamelen = atoi(tmp);

        filename[filenamelen] = '\0';

        recv(svr_s, recv_msg, 10, 0);

        strncpy(tmp, recv_msg, 10);

        tmp[10] = '\0';

        filelen = atoi(tmp);

        sprintf(path, "C:\\Temp\\%s", filename);

        pFile = new CFile((TCHAR *)path, CFile::modeCreate | CFile::modeWrite | CFile::shareDenyNone);

        recv_cnt = 10;

        do {

               recv_cnt = recv(svr_s, buf, 10, 0);

               pFile->Write(buf, recv_cnt);

        } while (recv_cnt==10);

 

        pFile->Close();

 

        AfxMessageBox(_T("수신완료"));

}

11) 다이얼로그 클래스에 작성한 클래스의 헤더파일을 인클루드 하고 변수 선언

#include "svrservice.h"

 

CSvrService* m_SvrService;

 

12) 서버 시작 버튼의 BN_CLICKED 작성

if(ServerStart() == FALSE)

        {

               return;

        }

       

        GetDlgItem(IDC_BTN_STARTSERVER)->EnableWindow(FALSE);

        GetDlgItem(IDC_BTN_STOPSERVER)->EnableWindow(TRUE);

 

13) 서버 중단 버튼의 BN_CLICKED 작성

m_SvrService->SuspendThread();

 

        closesocket(svr_ls);

 

        m_Message.Format(_T("서버종료."));

        UpdateData(FALSE);

 

        GetDlgItem(IDC_BTN_STARTSERVER)->EnableWindow(TRUE);

        GetDlgItem(IDC_BTN_STOPSERVER)->EnableWindow(FALSE);


 

2. 클라이언트 만들기

1) 대화상자 기반으로 프로젝트 생성(FileClient)- 고급 기능에서 Windows sockets에 체크

 

2) 대화상자 디자인

 

 

EDIT: IDC_FILENAME : CString m_FullFileName

파일 선택 버튼: IDC_BTN_SELECTFILE

파일 전송 버튼: IDC_BTN_SENDFILE

 

3) 다이얼로그 클래스에 변수 선언

CString m_FileName;

CString m_FileLength;

 

4) 파일 선택 버튼의 BN_CLICKED에 작성

CFileDialog dlg(TRUE);

 

        if(dlg.DoModal() == IDOK)

        {

               m_FullFileName = dlg.GetPathName();

               m_FileName = dlg.GetFileName();

               UpdateData(FALSE);

        }


 

5) 파일 전송 버튼의 BN_CLICKED 메시지 작성

        struct sockaddr_in peeraddr_in;

        SOCKET s;

 

        CString msg;

        char send_msg[1001];

 

        memset((char *)&peeraddr_in, 0, sizeof(struct sockaddr_in));

 

        peeraddr_in.sin_family = AF_INET;

        peeraddr_in.sin_addr.s_addr = inet_addr("127.0.0.1");

        peeraddr_in.sin_port = htons(9014);

 

        s = socket(AF_INET, SOCK_STREAM, 0);

 

        if(s == -1)

        {

                msg.Format(_T("소켓생성실패."));

               AfxMessageBox(msg);

               return;

        }

 

        if(connect(s, (struct sockaddr *)&peeraddr_in, sizeof(struct sockaddr_in)) == -1)

        {

               msg.Format(_T("connect() 함수실행실패."));

               AfxMessageBox(msg);

               return;

        }

 

        // File Transfer Routine

 

        CFile* pFile = NULL;

        char fullfilename[256];

        char filename[256];

        int  filenamelen;

        int  filelen;

        int  send_cnt;

        char buf[11];

 

        strcpy(fullfilename, (LPCSTR)(LPCTSTR)m_FullFileName);

        strcpy(filename, (LPCSTR)(LPCTSTR)m_FileName);

       

        pFile = new CFile((TCHAR *)filename, CFile::modeRead | CFile::shareDenyNone);

 

        filelen = pFile->GetLength();

 

        sprintf(send_msg, "%-256s", filename);

        send(s, send_msg, 256, 0);

 

        filenamelen = strlen(filename);

        sprintf(send_msg, "%10d", filenamelen);

        send(s, send_msg, 10, 0);

 

        sprintf(send_msg, "%10d", filelen);

        send(s, send_msg, 10, 0);

 

        send_cnt = 10;

        do {

               send_cnt = pFile->Read(buf, 10);

               send(s, buf, send_cnt, 0);

        } while (send_cnt == 10);

 

        closesocket(s);

 

        AfxMessageBox(_T("전송완료"));

반응형

'C & C++ > MFC Network' 카테고리의 다른 글

[Web] 웹브라우져 팁  (0) 2011.05.25
[Web] 인터넷 연결여부 확인  (0) 2011.05.25
[Web] 인터넷 프로그래밍  (0) 2011.04.25
[MFC] HTTP 소켓 통신  (0) 2011.04.23
[IP] Lan Card 정보 추출  (0) 2011.04.18

댓글