본문 바로가기

전공 과목 시험정리/C 프로그래밍

C++ 에서의 소켓프로그래밍

출처 http://lilyiu.tistory.com/entry/%EC%9C%88%EB%8F%84%EC%9A%B0-%EC%86%8C%EC%BC%93%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-C


// WinServer.cpp : 콘솔 응용 프로그램에 대한 진입점을 정의합니다.
// 

#include "stdafx.h"
#include <stdlib.h> 
#include <string.h>
#include <winsock2.h> 

#define PORT 9999
 
void ErrorHandling(char* message);
 
int _tmain(int argc, char** argv)
{ 
    WSADATA        wsaData; 
    SOCKET        hServSock; 
    SOCKET        hClntSock; 
    SOCKADDR_IN    servAddr; 
    SOCKADDR_IN clntAddr; 
    int    szClntAddr; 
    char message[] = "Hello World\n";
 
    /*Windows Socket API(윈속 - 윈도우에서 TCP/IP기반의 소켓 프로그래밍을 지원하기 위해 만든 소켓 함수들의 모음)
      사용하기 위해서는 winsock.dll이 필요한데 이 dll파일을 로딩하기 위해서 WSAStartup함수를 호출해야한다. 
     
        int WSAStartup( WORD wVersionRequested, LPWSADATA lpWSAData); 
        첫번째 매개변수 - 로드할 윈도우 소켓의 버전 
        두번째 매개변수 - WSAStartup함수가 성공적으로 실행되면 lpWASData에 소켓 정보를 채워서 되돌려준다. 
    */ 
    if(WSAStartup(MAKEWORD(2,2), &wsaData) != 0) 
         ErrorHandling("WSAStartup() error!");
 
    //WORD MAKEWORD(BYTE bLow, BYTE bHigh); 
    //MAKEWORD는 함수가 아닌 매크로다. 입력된 두개의 BYTE값으로 WORD자료구조를 만든다.
 
    //서버 소켓의 생성 
    hServSock = socket(PF_INET, SOCK_STREAM, 0);
    if(hServSock == INVALID_SOCKET) 
        ErrorHandling("socket() error"); 

    memset(&servAddr , 0, sizeof(servAddr));
     servAddr.sin_family = AF_INET; 
    servAddr.sin_addr.S_un.S_addr =htonl(INADDR_ANY);  
    servAddr.sin_port = htons(PORT); 

/*서버 프로그램은 단지 기다리기만 할 뿐 연결 대상이 없어서 INADDR_ANY를 쓴다.
   이 값은 인터넷 주소로 하면 '0.0.0.0'인데, 모든 주소로 대기하겠다는 의미이다.
   모든 주소로부터 대기해야 하는 이유는 하나의 컴퓨터가 여러 인터넷 주소를 가질 수 잇기 때문이다.
   서버 컴퓨터는 둘 이상의 네트워크 인터페이스를 가지는 경우가 흔하다.*/
//소켓에 주소와 Port할당 if(bind(hServSock, (SOCKADDR*) &servAddr,sizeof(servAddr)) == SOCKET_ERROR) ErrorHandling("bind() error"); //'연결 요청 대기 상태'로의 진입 if(listen(hServSock, 5) == SOCKET_ERROR) ErrorHandling("listen() error"); szClntAddr=sizeof(clntAddr); //연결 요청 수락 hClntSock =accept(hServSock, (SOCKADDR*)&clntAddr, &szClntAddr); if(hClntSock == INVALID_SOCKET) ErrorHandling("accept() error"); //데이터를 클라이언트에 전송한다. send(hClntSock, message, sizeof(message), 0); //연결 종료 closesocket(hClntSock); WSACleanup(); return 0; } void ErrorHandling(char* message) { fputs(message, stderr); fputc('\n', stderr); exit(1); } /* sockaddr과 sockaddr_in의 관계 : 모든 소켓 함수들은 소켓의 유형에 관계없이 sockaddr만을 받아들인다. 소켓 유형이 달라지면 구조체 역시 달라진다. 소켓 함수는 이 문제를 해결하기 위해 매개변수를 형변환하고 데이터 크기를 알려준다. struct sockaddr { unsigned short sa_family; char sa_data[14]; } struct sockaddr_in { short sin_family; ->주소 체계를 지정한다. AF_UNIX(시스템 내부영역에서 프로세스와 프로세스간 통신위해 사용) AF_INET(인터넷 영역에서 물리적으로 서로 멀리 떨어진 컴퓨터 사이의 통신 위해 사용) AF_IPX(Novell Internet Protocol..윈도우 비스타부터 지원 안함) AF_INET6(AF_INET과 같으나 IPv6사용) AF_X25(아마추어 라디오용 프로토콜) unsigned short sin_port; ->클라이언트에서는 연결할 서버의 포트번호이고 서버에서는 기다릴 포트 번호이다. struct in_addr sin_addr; ->연결할 인터넷 주소를 지정하기 위해서 사용한다. char sin_zero[8]; } sin_family와 sa_family는 완전히 같은 값이다. socket함수 원형 int socket(int domain, int type, int protocol); 첫번째인자 도메인 : 어떠한 영역에서 통신할 것인지 지정하는 것 두번째인자 타입 : 데이터 통신에서 사용할 프로토콜 유형을 지정하기 위해 사용 SOCK_STREAM - 연결 지향의 TCP/IP기반 통신에서 사용 SOCK_DGRAM - 데이터그램 방식의 UDP/IP기반 통신에서 사용 SOCK_RAW - TCP/IP를 직접 다룰 필요가 있을때 사용하는 프로토콜 세번째인자 프로토콜: 호스트 간 통신에 사용할 프로토콜을 결정 IPPROTO_TCP - TCP프로토콜로 AF_INET도메인과 SOCKET_STREAM 유형과 함께 사용 IPPROTO_UDP - UDP프로토콜로 AF_INET도메인과 SOCKET_DGRAM 유형과 함께 사용 반환값 : -1 실패 0 이상이면 성공 이 int형 값은 '단순한 숫자'가 아닌 소켓 객체를 '가리키는 숫자'다. 때문에 이 반환값을 socket descriptor 혹은 소켓 지정 번호 라고 부른다. 이는 리눅스에서의 socket이고 winsock은 소켓지정번호 대신에 소켓 객체인 SOCKET을 반환하다. */ /*bind함수로 소켓 설정하기 socket함수를 이용해서 만들어진 소켓은 인터넷에서 연결 요청을 받거나 보내기 위한 목적으로 사용한다. 전화기를 전화망에 연결한 상태라고 볼 수 있다. 그런데 아직 전화번호를 할당하지 않았다. 소켓을 생성한 다음 전화번호에 해당하는 IP주소와 서비스를 위한 포트를 할당해야한다. IP주소를 할당해야 원하는 컴퓨터를 찾아갈 수 있고, 포트를 지정해야 원하는 서비스 프로그램에 연결을 시도할 수 있다. bind 함수를 이용해서 소켓에 IP와 포트를 할당할 수 있다. bind함수는 서버 프로그램에 포트를 고정시키기 위해서 사용한다. 클라이언트는 연결할 인터넷 서비스(서버)의 포트 번호만 알고 있으면 된다.(즉, 자신의 포트 번호는 몰라도된다) 그러니 클라이언트에서는 bind함수를 사용할 필요가 없다. int bind(int sockfd, struct sockaddr* my_addr, socklen_t addrlen); 첫번째 인자 sockfd - 앞서 socket함수로 생성된 endpoint(듣기)소켓 두번째 인자 my_addr - IP주소와 port번호를 저장하기 위한 변수가 있는 구조체 세번째 인자 addrlen - 두번째 인자의 데이터 크기 반환값 성공하면 0, 실패하면 -1 */ /*listen함수로 수신 대기열 생성하기. 만약 클라이언트 요청에 대한 처리가 미처 끝나기 전에 새로운 클라이언트가 요청하는 상황이 발생하면 서버는 클라이언트에 연결오류 메시지를 전송하고 연결을 거부해 버릴 것이다. 하지만 요청을 거부하는 것 보다는 앞의 클라이언트의 처리가 끝날때까지 잠시 기다리도록 하는게 나을것이다. 그래서 소켓은 수신 대기열을 만들어서 연결 요청을 관리한다. 클라이언트 요청은 먼저 수신 대기열로 들어가서 잠시 기다리게 된다. 그러면 이전 클라이언트 처리를 끝나친 서버가 대기열의 가장 앞에있는 클라이언트 요청을 가져와서 처리하게 된다. 수신대기열은 FIFO 형태의 자료구조이다. int listen(int sockfd, int backlog) 첫번째 매개변수 sockfd - socket함수를 수행한 결과 얻은 듣기 소켓의 소켓 지정번호이다. 두번째 매개변수 backlog - 수신 대기열의 크기이다. 수신 대기열의 크기는 '이게 정답이다' 라고 정해진 값이 없다.

네트워크 환경에 따라 경험적인 값을 적당히 사용한다. 함수 실행결과 성공하면 0 실패하면 -1을 반환한다. */ /*accept함수로 연결 기다리기 전화기에 해당하는 소켓을 생성했고 IP번호와 포트번호도 할당했다. 거기에 수신 대기열까지 생성했으니 서버는 수신 대기열에 있는 클라이언트 연결 요청만 확인하면 된다. accept함수는 대기중인 연결 요청이 있는지 확인한다. 클라이언트가 연결 요청을 하면 이 연결 요청은 먼저 수신 대기열에 들어가게 되는데 accept함수는 수신 대기열의 가장 앞에있는 연결 요청을 가져온다. int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen); 첫번째 매개변수 sockfd - socket함수를 이용해 생성된 소켓으로 클라이언트의 요청을 받아들이는 듣기 소켓이다. 두번째 매개변수 addr - accept함수가 성공하게 되면 연결된 클라이언트 주소와 포트 정보를 이

구조체에 복사해서 넘겨준다.

addr은 연결한 클라이언트의 정보를 확인하거나 로그를 남기기 위한 목적 등으로 사용할 수 있다. 세번째 매개변수 addrlen - sockaddr구조체의 크기이다. accept함수가 성공적으로 실행되면 0보다 큰 값을 반환한다. 이때 반환값은 '소켓 지정 번호'다. 만약 수신 대기열이 비어있다면, accept함수는 연결 요청이 들어올 때 까지 기다린다. 실제 '클라이언트와의 통신' 은 accept함수를 통해 반환된 이 connected socket(연결소켓)을 통해 이루어진다. 서버 프로그램은 계속해서 클라이언트 요청을 처리하기 때문에 accept함수는 일반적으로 while문 안에 위치한다. */

 

 

-------------------------------------------------------------------------------------------------------------------------------------

 

// WinClient.cpp : 콘솔 응용 프로그램에 대한 진입점을 정의합니다.
// 

#include "stdafx.h"
#include <stdlib.h> 
#include <string.h>
#include <winsock2.h> 

#define PORT 9999
#define IP     "127.0.0.1"
 
/*127.0.0.1은 loopback 네트워크 접속을 위한 표준 IP어드레스 입니다. 
이 말은 127.0.0.1에 접속하고자 할때 바로 자신의 컴퓨터에 loopback 하게 된다는 말 입니다.     
loopback은 자신에게 데이터를 송신하는 것이나 그와 같은 기능입니다. 
네트워크 카드에는 자신을 가르치는 loopback address가 설정되어 있어 이 주소에 송신된 데이터는  
카드내에 수신쪽에 수신되어 집니다. 기기가 정상으로 가동하고 있는지 아닌지를 확인하기 위해  
시험삼아 데이터를 보낼떼 사용되며 이런 것을 loopback device라고 합니다. 
그럼 127.0.0.1은 왜 필요 할까요?? 
127.0.0.1을 사용하면 네트워크 인터페이스(ethernet)을 사용하지 않고 OS(kernel)에서 직접 처리하게 됩니다. 
소켓 프로그램이란 것이 네트워크 카드를 통해서 서로 다른 시스템 간의 통신에도 사용할 수 있지만 
같은 시스템에서 서로 다른 프로세스간의 통신에도 사용할 수 있습니다. 
이때 굳이 네트워크 카드 필요없이 127.0.0.1을 사용할 수 있습니다! 
즉 로컬 시스템 내에서 프로세스간의 소켓 통신을 위한 IPC 수단으로 존재한다는 것 입니다. 
[출처] 네이버 검색 127.0.0.1| 작성자 ahnsh09*/ 

void ErrorHandling(char* message);
 
int _tmain(int argc, char** argv)
 { 
    WSADATA wsaData; 
    SOCKET hSocket; 
    char message[30];
     int    strLen; 
    SOCKADDR_IN    servAddr; 

    if(WSAStartup(MAKEWORD(2,2), &wsaData) !=0)
         ErrorHandling("WSAStartup() error!");
 
    hSocket = socket(PF_INET, SOCK_STREAM, 0);
     if(hSocket == INVALID_SOCKET) 
        ErrorHandling("hSocket() error"); 

    memset(&servAddr, 0 , sizeof(servAddr));
     servAddr.sin_family = AF_INET; 
    servAddr.sin_addr.S_un.S_addr = inet_addr(IP); 
    servAddr.sin_port = htons(PORT); 

    if(connect(hSocket, (SOCKADDR*)&servAddr,
         sizeof(servAddr)) == SOCKET_ERROR) 
        ErrorHandling("connect() error!"); 

    /* 
    윈도우는 소켓을 파일로 보지 않기 때문에 read,write 같은 파일 입출력 함수가 아닌 
    recv, send 함수를 사용한다. 
    int recv(SOCKET s, char* buf, int len, int flags); 
    int send(SOCKET s, char* buf, int len, int flags); 
    */ 

    strLen = recv(hSocket, message, sizeof(message) -1 ,0);
     if(strLen == -1)
         ErrorHandling("read() error!"); 
    message[strLen] = 0; 
    printf("Message from server : %s \n", message);
 
    closesocket(hSocket); 
    WSACleanup(); 
    return 0;
 
} 

void ErrorHandling(char* message)
 { 
    fputs(message, stderr); 
    fputc('\n', stderr); 
    exit(1); 
}



1. 파이썬으로 5000포트 리슨

2. C++ 에서 보내고 싶은 데이터를 file I/O 로 txt 저장 + 로컬 5000 포트로 신호를 보냄

3. 신호를 받은 파이썬에서 txt 파일을 읽어 서버로 보냄. 

4. 서버에서 연산 후 다시 보내줌

5. 파이썬에서 받은 데이터를 txt 파일로 저장

6. 다시 C++ 에게 신호를 보냄.

7. C++ 가 신호를 받으면 해당 txt 파일을 읽고 저장함.





애초에 C++ 을 할 줄 알았으면 좋았는데, 시간이 되면 C++ 를 더 깊게 공부해서 직접 소켓을 붙여보는 방식으로 짜고 싶다.

+) C++ 소켓이랑 C 소켓이랑 사용 함수나, 방식등은 크게 다르지 않은 듯. 

다만 윈도우 소켓은 리눅스 소켓에 WSAStartup() 함수가 붙고, 좀더 복잡한 것 같다.