윈도우소켓프로그래밍 _A_2015 버전 IT CookBook, 윈도우 API 프로그래밍 한빛미디어의윈도우 API 프로그래밍, 윈도우네트워크프로그래밍, 혜지원 API programming 을참조함! Updated 2015.11.22 1
학습목표 TCP/IP 프로토콜의개념을이해하고, 윈도우프로그래밍을이용한간단한채팅프로그램을작성하여이해도를높인다. 내용 소켓연결 메시지교환 논블록킹통신 채팅프로그램 2/55
1 절. 소켓연결 네트워크응용프로그램 두개의독립된프로그램이서로정보를교환 소켓통신방법 정보를교환하기위한일반적으로사용하는방법 소켓은 IP주소와포트번호로이루어진컴퓨터통신의끝점으로표준화된인터페이스를제공 윈도우즈소켓을이용을위한환경설정 윈도우소켓을윈속 (Winsock) 이라부름 프로젝트환경에라이브러리 ws2_32.lib 를추가해주어야함 3
TCP 서버 / 클라이언트동작원리 (1/3) TCP 서버 / 클라이언트예 GET / HTTP/1.1 Accept: image/gif,... 웹서버 <HTML> <HEAD>...</HEAD>... TCP 서버 / 클라이언트동작방식 웹클라이언트 웹클라이언트 TCP 서버 TCP 클라이언트 listen accept connect recv send 네트워크 send recv 4
TCP 서버 / 클라이언트동작원리 (2/3) TCP 서버 / 클라이언트동작방식 (cont d) 1 서버는먼저실행하여클라이언트가접속하기를기다린다 (listen). 2 클라이언트가서버에게접속 (connect) 하여데이터를보낸다 (send). 3 서버는클라이언트접속을수용하고 (accept), 클라이언트가보낸데이터를받아서 (recv) 처리한다. 4 서버는처리한데이터를클라이언트에게보낸다 (send). 5 클라이언트는서버가보낸데이터를받아서 (recv) 자신의목적에맞게사용한다. 5
TCP 서버 / 클라이언트동작원리 (3/3) TCP 서버 / 클라이언트동작원리 TCP 서버 TCP 서버 TCP 서버 TCP 서버 대기 클라이언트접속 통신 대기 통신 대기 통신 TCP 클라이언트 #1 TCP 클라이언트 #1 TCP 클라이언트 #1 TCP 클라이언트 #2 fgets() send() recv() printf() TCP 클라이언트 TCP 서버 printf() recv() send() 6
TCP 서버 / 클라이언트분석 소켓데이터구조체 애플리케이션 서버 네트워크 클라이언트 지역 IP 주소 지역 IP 주소 지역포트번호 지역포트번호 운영체제 원격 IP 주소 원격 IP 주소 원격포트번호 원격포트번호 7
TCP 서버함수 (1/4) TCP 서버함수 TCP 서버 socket() TCP 클라이언트 socket() bind() listen() accept() connect() recv() 네트워크 send() send() recv() closesocket() closesocket() 8
TCP 서버함수 (2/4) bind() 함수 서버의지역 IP 주소와지역포트번호를결정 int bind ( SOCKET s, const struct sockaddr* name, int namelen ) ; 성공 : 0, 실패 : SOCKET_ERROR 9
TCP 서버함수 (3/4) listen() 함수 소켓과결합된 TCP 포트상태를 LISTENING 으로변경 int listen ( SOCKET s, int backlog ) ; 성공 : 0, 실패 : SOCKET_ERROR 10
TCP 서버함수 (4/4) accept() 함수 접속한클라이언트와통신할수있도록새로운소켓을생성하여리턴 접속한클라이언트의 IP 주소와포트번호를알려줌 SOCKET accept ( SOCKET s, struct sockaddr* addr, int* addrlen ) ; 성공 : 새로운소켓, 실패 : INVALID_SOCKET 11
TCP 클라이언트함수 (1/2) TCP 클라이언트함수 TCP 서버 socket() 서버소켓생성 TCP 클라이언트 socket() bind() IP, Port 할당 listen() 클라이언트접속대기 접속허용 accept() connect() 접속시도 recv() 응답 네트워크 send() 요청 send() recv() closesocket() 클라이언트접속종류 listen( ) 소켓종료 closesocket() 클라이언트소켓종료 12
TCP 클라이언트함수 (2/2) connect() 함수 서버에게접속하여 TCP 프로토콜수준의연결설정 int connect ( SOCKET s, const struct sockaddr* name, int namelen ) ; 성공 : 0, 실패 : SOCKET_ERROR 13
데이터전송함수 (1/4) 소켓데이터구조체 애플리케이션 서버 네트워크 클라이언트 운영체제 지역 IP 주소지역 IP 주소지역포트번호지역포트번호원격 IP 주소원격 IP 주소원격포트번호원격포트번호수신버퍼송신버퍼 14
데이터전송함수 (2/4) send() 함수 애플리케이션데이터를송신버퍼에복사함으로써궁극적으로하부프로토콜 ( 예를들면, TCP/IP) 에의해데이터가전송되도록함 int send ( SOCKET s, const char* buf, int len, int flags ); 성공 : 보낸바이트수, 실패 : SOCKET_ERROR 15
데이터전송함수 (3/4) recv() 함수 수신버퍼에도착한데이터를애플리케이션버퍼로복사 int recv ( SOCKET s, char* buf, int len, int flags ); 성공 : 받은바이트수또는 0( 연결종료시 ), 실패 : SOCKET_ERROR 16
데이터전송함수 (4/4) recv() 함수동작원리 buf len ptr left buf len 읽은데이터 ptr left 17
소켓통신을위한자료구조 (1/2) 윈도우소켓초기화정보를가지고있는구조체 ( 정보얻어옴 ) struct WSAData { }; WORD wversion; // 윈도우소켓버전 WORD whighversion; // 윈도우소켓의가장높은버전, 통상적으로 wversion 과같음 char szdescription[wsadescription_len+1]; // NULL 로끝나는문자열. // 적재된 WS2_32.dll 에서소켓에관련된설명문자열을카피 char szsystemstatus[wsasysstatus_len+1]; // 시스템의각종상태를알수있게해줌 unsigned short imaxsockets; // 어플리케이션에서사용할소켓의최대수리턴. Version 2 부터무시 unsigned short imaxudpdg; // 전송할수있는데이터그램최대크기리턴. Version 2 부터무시 char FAR * lpvendorinfo; // Version 2 부터무시 소켓을연결할상대방주소를쓰는구조체 struct sockaddr_in{ short sin_family; unsigned short sin_port; struct in_addr sin_addr; char sin_zero[8]; }; 18
소켓통신을위한자료구조사용예 (2/2) Winsock 버전 2.2 정보를 wsadata 에얻어옴 // WSAStartup( ) 함수는윈속관련라이브러리를초기화하고, 그데이터를 WSADATA 에할당 // 1: 사용할윈도우소켓버전, 2: 소켓정보저장공간 소켓의통신방법, 주소, 포트설정 127.0.0.1 은 loopback 네트웤접속을위한표준 IP 어드레스이다. 127.0.0.1 에접속하고자할때바로자신의컴퓨터에 loopback 하게된다. 자신에게데이터를송신하는것 19
소켓통신을위한함수 (1/3) Winsock 사용시작 int WSAStartup( WORD wversionrequested, // 사용할윈도우즈소켓버전 ( 윈속버전정보전달받음 ) LPWSADATA lpwsadata // 소켓정보저장공간 (WSADATA 구조체변수의주소값전달 ); 소켓생성 SOCKET WSAAPI socket( int af, int type, int protocol // 0을입력 ); //address family 를명시함, AF_INET 이용 // 소켓의유형으로 SOCK_STREAM 이용 주소와소켓을연결하는함수 BOOL bind ( SOCKET s, // 연결할소켓 const SOCKADDR* lpsockaddr, int nsockaddrlen ); // 소켓에지정될주소와 // 포트번호를포함하는 SOCKASSR구조체주소 // 주소구조체 SOCKASSR 의크기 20
소켓통신을위한함수 (2/3) 연결요구를기다리는함수 BOOL listen( SOCKET s, // 기다릴소켓 ); int nconnectionbacklog // 대기할수있는요구최대개수로 1~5 연결을요구하는함수 BOOL connect( SOCKET s, // 연결에사용할소켓 const SOCKADDR* lpsockaddr, // 상대방의주소및포트번호 ); int nsockaddrlen // 주소구조체 SOCKASSR 의크기 21
소켓통신을위한함수 (3/3) 연결요구를받아들이는함수 SOCKET accept( SOCKET s, // 소켓에대한요구를받아들임 SOCKADDR* lpsockaddr, // 접속하는상대방의주소를저장할 SOCKASSR구조체주소 int* lpsockaddrlen // 주소구조체 SOCKASSR의크기 ); 소켓과관련된리소스를해제하는함수 int closesocket(socket s); Winsock 사용끝 int WSACleanup( ); 22
소켓프로그래밍환경설정 프로젝트생성직후 : 1) [ 프로젝트 ] Properties [ 속성 ], 팝업창에서 : 1) Linker Command Line ( 명령줄 ), 2)) Additional Options 에 ws2_32.lib 추가 23
실습 1: 서버 ) 서버프로그램작성. 소켓을이용하여서버와클라이언트를연결하는코드를작성한다. 이때, 서버용프로그램과클라이언트용프로그램은각각작성함! 1. VS 2015 수행후, Win32 Project 생성 프로젝트네임 : socket_server_1 Empty project 선택 ws2_32.lib 옵션설정 ( 강의자료 p.23 참조 ) 2. socket_server_1.cpp 파일을생성한다. 3. socket_server_1.cpp 파일에강의자료 25~26 페이지내용을작성 4. 빌드까지만수행한다. 24
실습 1: socket_server_1) 접속을기다리는서버 (1/2) 25
실습 1: socket_server_1) 접속을기다리는서버 (2/2) 26
실습 1: 클라이언트 ) 클라이언트프로그램작성. 1. VS 2010 수행후, Win32 Application 프로젝트생성 프로젝트네임 : socket_client_1 Empty project 선택 ws2_32.lib 옵션추가및멀티바이트설정 ( 강의자료 p.23 참조 ) 2. socket_client_1.cpp 파일을추가한다. 3. socket_client_1.cpp 에강의자료 28 페이지내용을작성 4. 빌드까지만수행한다. 27
실습 1: socket_client_1) 접속하는클라이언트 28
실습 1 실행 ) Server_1 과 Client_1 실행 - 코딩을완료한후다음과정을수행! 1) 서버실행파일인 socket_server_1.exe 를실행한다. 서버는클라이언트접속을대기중인상태가됨! 2) 클라이언트실행파일인 socket_client_1.exe 를실행한다. 다음과같은화면이나오는지확인. 클라이언트가접속했을때서버에서띄우는메시지박스 현재는접속만하도록했기때문에, 접속확인메시지박스만출력됨! 29
2 절. 메시지교환 (1/4) 메시지전송함수 int send( SOCKET s, // 연결된소켓 const void* lpbuf, // 보낼메시지가저장된버퍼 int nbuflen, // 메시지의길이 int nflags // 0을이용 ); 메시지수신함수 int recv( SOCKET s, // 연결된소켓 void* lpbuf, // 받는메시지가저장될버퍼 int nbuflen, // 버퍼의길이 int nflags // 0을이용 ); 30
2 절. 메시지교환 (2/4) 소켓통신 : ANSI (multi-byte) 코드로인코딩되어있음 - 즉, unicode 모드로작성되어, 유니코드로정보를전달받게되면문제발생 - 따라서, Unicode 정보를 Multi-byte로변경하여사용해야함! - 보낼때 Multi-byte로변환 : WideCharToMultiByte( ) - 받고나서는 Unicode로변환 : MultiByteToWideChar( ) 31
2 절. 메시지교환 (3/4) WideCharToMultiByte( ): Unicode Multi-byte // ANSI 문자열에대한언어, CP_ACP // 사용하지않음, 0 // 변환하려는유니코드문자열 // 문자열의길이, -1이면함수가자동계산 // 멀티바이트로변환후저장될공간 // 변환후문자열의길이 // NULL // NULL Unicode Multi-byte 예제 int msglen; WCHAR str[] = _T( 유니코드문자열 ); msglen = WideCharToMultiByte(CP_ACP, 0, str, -1, NULL, 0, NULL, NULL); char buffer[100]; WideCharToMultiByte(CP_ACP, 0, str, -1, buffer, msglen, NULL, NULL); https://msdn.microsoft.com/ko-kr/library/windows/apps/xaml/dd374130.aspx 32
2 절. 메시지교환 (4/4) MultiByteToWideChar( ): Multi-byte Unicode // ANSI 문자열에대한언어, CP_ACP // 사용하지않음, 0 // 변환하려는멀티바이트문자열 // 문자열의길이, -1이면함수가자동계산 // 유니코드로변환후저장될공간 // 유니코드로변환후문자열의길이 Multi-byte Unicode 예제 int msglen; char buffer[] = 멀티바이트문자열 ; msglen = MultiByteToWideChar(CP_ACP, 0, buffer, strlen(buffer), NULL, NULL); WCHAR wbuffer[100]; MultiByteToWideChar(CP_ACP, 0, buffer, strlen(buffer), wbuffer, msglen); https://technet.microsoft.com/ko-kr/dd319072 33
실습 2: 서버 ) 서버프로그램작성. 소켓을이용하여서버와클라이언트를연결하는코드를작성한다. 이때, 서버용프로그램과클라이언트용프로그램은각각작성함! 1. VS 2015 수행후, Win32 Project 생성 프로젝트네임 : socket_server_2 Empty project 선택 ws2_32.lib 옵션설정 ( 강의자료 p.23 참조 ) 2. socket_server_2.cpp 파일을생성한다. 3. socket_server_2.cpp 파일에강의자료 32~33 페이지내용을작성 socket_server_1.cpp 를이용해도됨. 4. 빌드까지만수행한다. 34
실습 2: socket_server_2) 메시지수신추가 (1/3) 35
실습 2: socket_server_2) 메시지수신추가 (2/3) 36
실습 2: socket_server_2) 메시지수신추가 (3/3) 37
실습 2: 클라이언트 ) 클라이언트프로그램작성. 클라이언트에서서버로메시지를보내는코드를작성한다. ( 서버용프로그램과클라이언트용프로그램은각각작성함!) 1. VS 2015 수행후, Win32 Application 프로젝트생성 프로젝트네임 : socket_client_2 Empty project 선택 ws2_32.lib 옵션설정 ( 강의자료 p.23 참조 ) 2. socket_client_2.cpp 파일을추가한다. 3. socket_client_2.cpp 에강의자료 36 페이지내용을작성 4. 빌드까지만수행한다. 38
실습 2: socket_client_2) 서버접속후메시지송신 39
실습 2 실행 ) Server_2 와 Client_2 실행 - 코딩을완료한후다음과정을수행! 1) 서버실행파일인 socket_server_2.exe 를실행한다. 서버는클라이언트접속을대기중인상태가됨! 2) 클라이언트실행파일인 socket_client_2.exe 를실행한다. 다음과같은화면이나오는지확인. 클라이언트가서버에게보낸메시지 안녕하세요 Server! 가메시지박스에나온다. 현재는접속만하도록했기때문에, 접속확인메시지박스만출력됨! 40
3 절. 실제윈도우화면에서메시지교환 2 절 -1 에서작성한코드는윈도우화면이아닌, 서버측에서메시지박스로그 내용을출력! 본 3 절에서는해당메시지를서버의윈도우화면에출력하고자한다!!! 클라이언트윈도우에서 키보드 ( 아무 ) 키를누름 메시지서버에전송 41
실습 3: 서버 ) 서버프로그램작성. 클라이언트에서서버로보낸메시지를서버의화면 ( 윈도우 ) 에출력!!! 이때, 서버용프로그램과클라이언트용프로그램은각각작성함! 1. VS 2015 수행후, Win32 Project 생성 프로젝트네임 : socket_server_3 Empty project 선택 ws2_32.lib 옵션설정 ( 강의자료 p.23 참조 ) 2. socket_server_3.cpp 파일을생성. 3. 3_server_txt.txt 파일의내용을 socket_server_3.cpp 에복사 4. socket_server_3.cpp 파일에강의자료 43~45 페이지내용을추가로작성 5. 빌드까지만수행한다. 42
실습 3: socket_server_3) 윈도우에메시지출력 (1/3) 43
실습 3: socket_server_3) 윈도우에메시지출력 (2/3) // 윈도우가생성되면 // 서버용소켓을만들고 // 서버주소는 127.0.0.1 // 포트번호는 20에서클라이언트접속대기 // 주의 : 클라이언트가접속하지않으면 // 서버소켓은기다리고, 윈도우를화면에 // 출력하지않음 // 클라이언트가접속하면, accept( ) 함수가 // INVALID_SOCKET인지검사 // INVALID가아니면도착한데이터를 // 배열buffer에저장 44
실습 3: socket_server_3) 윈도우에메시지출력 (3/3) // buffer 도착한정보가 // multi-byte이면그대로진행하고 // unicode정보였다면 // unicode로변환하여저장 // 받은메시지를윈도우화면 // 0,0 위치에출력 45
실습 3: 클라이언트 ) 클라이언트프로그램작성. 클라이언트에서서버로보낸메시지를서버의화면 ( 윈도우 ) 에출력!!! 이때, 서버용프로그램과클라이언트용프로그램은각각작성함! 1. VS 2015 수행후, Win32 Application 프로젝트생성 프로젝트네임 : socket_client_3 Empty project 선택 ws2_32.lib 옵션추가설정 ( 강의자료 p.23 참조 ) 2. socket_client_3.cpp 파일을추가한다. 3. 강의자료에첨부한 3_client_txt.txt 의내용을 socket_client_3.cpp 로복사한다. 4. socket_client_3.cpp 에강의자료 47-48페이지내용을작성 5. 빌드까지만수행한다. 46
실습 3: socket_client_3) 윈도우환경메시지송신 (1/2) 47
실습 3: socket_client_3) 윈도우환경메시지송신 (2/2) 48
실습 3 실행 ) Server_3 와 Client_3 실행 1) 서버실행파일인 socket_server_3.exe 를실행한다. 2) 클라이언트실행파일인 socket_client_3.exe 를실행한다. Client 3 이라는윈도우창이팝업됨. 팝업된윈도우가선택된상태에서키보드의어떤키든누른다. 3) 클라이언트에서키보드를누르면메시지 Hello Server 를서버에게보냄 화면에 Hello Server! 를출력하는서버창이팝업됨! 49
지금까지통신의문제점 클라이언트측에서접속을할때까지서버측프로그램은무한정기다림 접속이되었다하더라도클라이언트측에서메시지를보내기까지메시지를받는서버측은무한정기다림 기다리는동안서버는아무런일도할수없음 논블록킹통신으로문제해결 50
4 절. 논블록킹통신 1. 네트워크이벤트발생시사용자 ( 프로그래머 ) 가정의한메시지를사용할수있도록 #define 추가 #define WM_ASYNC WM_USER+2 2. 논블록킹으로처리할윈도우메시지나네트워크이벤트를 WSAAsyncSelect() 를이용하여등록 int WSAAsyncSelect( SOCKET s, // 연결된소켓 HWND hwnd, // 메시지가발생하는윈도우의핸들 unsigned int imsg, // 등록될윈도우메시지 ); long IEvent // 등록될네트워크이벤트 51
4 절. 네트워크이벤트 WM_ASYNC 는특정네트워크이벤트발생시 윈도우메시지 로사용할수 있도록한다. 사용가능한네트워크이벤트는다음과같다. FD_READ: read를할준비가되면발생시키는이벤트 FD_WRITE: read될데이터가사용가능하면발생시키는이벤트 FD_OOB: out-of-band 데이터가도착하면발생시키는이벤트 FD_ACCEPT: 접속을요구하는신호가오면발생시키는이벤트 FD_CONNECT: 접속이완료되면발생시키는이벤트 FD_CLOSE: 상대방이접속을종료하면발생시키는이벤트 52
실습 4: 서버 ) 논블럭킹수신용서버프로그램 서버가클라이언트의접속및메시지를받기위해, 계속대기하지않는다!!! 이때, 서버용프로그램과클라이언트용프로그램은각각작성함! 1. VS 2015 수행후, Win32 Project 생성 프로젝트네임 : socket_server_4 Empty project 선택 ws2_32.lib 옵션설정 ( 강의자료 p.23 참조 ) 2. socket_server_4.cpp 파일을생성한다. 3. 강의자료에첨부한 4_sever_txt.txt 의내용을 socket_server_4.cpp 로복사한다. 4. socket_server_4.cpp 파일에강의자료 54~56페이지내용을추가로작성 5. 빌드까지만수행한다. 클라이언트프로그램은이전 socket_client_3 과동일하다. 따라서, 프로젝트를 socket_client_4 로생성한후 ws2_32.lib 옵션설정, socket_client_3.cpp 파일을해당프로젝트에복사후 existing Item 선택 이름 변경 프로젝트클래스이름변경 빌드까지만수행! 후이용! 53
실습 4: socket_server_4) 논블록킹수신용서버프로그램 (1/3) // define 문추가 54
실습 4: socket_server_4) 논블록킹수신용서버프로그램 (2/3) 55
실습 4: socket_server_4) 논블록킹수신용서버프로그램 (3/3) 56
실습 4 실행 ) Server_4 와 Client_4 실행 1) socket_server_4.exe 를실행한다. Server 4 윈도우팝업 2) socket_client_4.exe 를실행한다. Client 4 윈도우팝업 3) 클라이언트창에서키보드누름 서버윈도우화면에문장출력됨. 눈으로보이는것은이전실습 3 의결과와다르지않지만, 서버가한프로그램 을위해서계속대기하지않는다! 라는것은중요한사실! 실습 3) 의결과와비슷해보이지만, 실습 3) 은클라이언트가접속해올때까지서버는기다려야한다. 따라서, 블록된상태에서멈추므로서버윈도우가나타나지않는다. 반면, 실습 4) 에서는클라이언트가접속해오면통지를하라고설정해두고서버자신은다른일을처리할수있다. 따라서, 처음부터서버윈도우가나타난다. 57