네트워크 및 보안/윈도우즈 소켓 통신 프로그램

3. TCP 에코 서버/클라이언트 만들기 [TCP/IP 소켓 통신 프로그래밍 with 윈도우즈]

언제나휴일 2016. 4. 7. 12:27
반응형

3. TCP 에코 서버/클라이언트 만들기

 이번 장에서는 TCP 프로토콜을 이용하여 간단한 에코 서버와 에코 클라이언트를 만들어 봅시다. 이를 통해 여러분은 윈속 라이브러리에서 제공하는 기본 함수들의 사용법을 살펴볼 수 있습니다.

 

 에코 서버란 클라이언트에서 수신한 데이터를 클라이언트에게 재전송하는 서버를 말합니다.

 

3.1TCP 통신 절차

 TCP 프로토콜을 이용한 통신은 서버와 클라이언트 사이에 연결을 형성한 후에 스트림 방식으로 패킷을 송수신합니다. 스트림 방식의 통신에서는 전송한 패킷의 순서대로 도착하며 패킷 전송 중에 전송이 실패하면 다시 전송하여 신뢰성을 보장하는 방식입니다.

 

 TCP 통신에서 서버는 대기 소켓을 생성하고 로컬 소켓 주소로 네트워크 인터페이스와 결합한 후에 백 로그 큐를 설정합니다. 이 상태에서 클라이언트 측의 연결 요청이 오면 이를 수락하여 송수신에 사용할 소켓을 만듭니다. 이 후 송수신 소켓으로 클라이언트와 패킷을 주고 받는 작업을 수행하며 더 이상 송수신할 패킷이 없으면 소켓을 닫습니다.

 

 클라이언트 측은 소켓을 생성한 후에 로켓 소켓 주소로 네트워크 인터페이스와 결합을 선택적으로 수행할 수 있습니다. 그리고 서버의 소켓 주소로 연결 요청하여 성공하면 패킷을 송수신합니다.


TCP 통신 절차

[그림 3.1] TCP 통신 절차

 

3.2 TCP 에코 서버 구현

 먼저 클라이언트와 서버 사이에 약속할 부분을 정의합시다. TCP 에코 서버는 10200 포트를 bind 하여 사용하고 listen 함수에 설정할 백 로그 큐의 크기는 5로 설정할게요. 그리고 송수신 메시지 크기를 256으로 약속합시다.

#define PORT_NUM      10200

#define BLOG_SIZE       5

#define MAX_MSG_LEN 256

 

 진입점에서는 윈속을 초기화한 후에 대기 소켓을 설정합니다. 대기 소켓 설정이 성공하면 클라이언트 연결 요청을 수락하는 작업을 반복하는 Accept Loop을 수행합니다. 그리고 모든 작업이 끝나면 윈속을 해제화합니다.

int main()

{

    WSADATA wsadata;

    WSAStartup(MAKEWORD(2,2),&wsadata);//윈속 초기화      

    SOCKET sock = SetTCPServer(PORT_NUM,BLOG_SIZE);//대기 소켓 설정

    if(sock == -1)

    {

        perror("대기 소켓 오류");

        WSACleanup();

        return 0;

    }

    AcceptLoop(sock);//Accept Loop

    WSACleanup();//윈속 해제화

    return 0;

}

 

 대기 소켓을 설정하는 함수를 작성합시다. 먼저 소켓을 생성합니다.

SOCKET SetTCPServer(short pnum,int blog)

{

    SOCKET sock;

    sock = socket(AF_INET, SOCK_STREAM,IPPROTO_TCP);//소켓 생성

    if(sock == -1){    return -1;    }

 

 그리고 소켓 주소를 설정하여 네트워크 인터페이스와 결합합니다.

    SOCKADDR_IN servaddr={0};//소켓 주소

    servaddr.sin_family = AF_INET;

    servaddr.sin_addr = GetDefaultMyIP();

    servaddr.sin_port = htons(PORT_NUM);

    int re = 0;

    re = bind(sock,(struct sockaddr *)&servaddr,sizeof(servaddr));//소켓 주소와 네트워크 인터페이스 결합

    if(re == -1){    return -1;    }

 TCP 서버에서는 연결 요청 과정동안 클라이언트 정보를 기억하기 위한 백 로그 큐를 설정합니다.

    re = listen(sock,blog);//백 로그 큐 설정

    if(re == -1){    return -1;    }

    return sock;

}

 

 이처럼 대기 소켓을 설정하였으면 서버에서는 클라이언트의 연결 요청을 수락하는 작업을 반복합니다.

void AcceptLoop(SOCKET sock)

{

    SOCKET dosock;

    SOCKADDR_IN cliaddr={0};

    int len = sizeof(cliaddr);

    while(true)

    {

 accept함수를 호출하면 클라이언트의 소켓 주소를 알 수 있고 송수신에 사용할 소켓을 반환합니다.

        dosock = accept(sock,(SOCKADDR *)&cliaddr,&len);//연결 수락

        if(dosock == -1)

        {

            perror("Accept 실패");

            break;

        }

        printf("%s:%d의 연결 요청 수락\n",inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));

 연결 수락하는 accept 함수가 반환한 소켓을 이용하여 송수신합니다.

        DoIt(dosock);

    }

 모든 작업을 완료하면 소켓을 닫습니다.

    closesocket(sock);//소켓 닫기

}

 

 이제 메시지 송수신 함수를 작성합시다.

void DoIt(SOCKET dosock)

{

    char msg[MAX_MSG_LEN]="";

 에코 서버에서는 메시지를 수신하는 것을 반복합니다. 만약 상대가 소켓을 닫으면 recv 함수는 0을 반환합니다. 그리고 수신이 실패하면 -1을 반환합니다. 따라서 recv 함수가 양수를 반환하면 계속 수행합니다.

    while(recv(dosock,msg,sizeof(msg),0)>0)//수신

    {

        printf("recv:%s\n",msg);

 에코 서버이므로 받은 메시지를 상대에게 전송합니다.

        send(dosock,msg,sizeof(msg),0);//송신

    }

    closesocket(dosock);//소켓 닫기

}

#include "common.h"

#define PORT_NUM      10200

#define BLOG_SIZE       5

#define MAX_MSG_LEN 256

SOCKET SetTCPServer(short pnum,int blog);//대기 소켓 설정

void AcceptLoop(SOCKET sock);//Accept Loop

void DoIt(SOCKET dosock); //송수신

 

int main()

{

    WSADATA wsadata;

    WSAStartup(MAKEWORD(2,2),&wsadata);//윈속 초기화           

    SOCKET sock = SetTCPServer(PORT_NUM,BLOG_SIZE);//대기 소켓 설정

    if(sock == -1)

    {

        perror("대기 소켓 오류");

        WSACleanup();

        return 0;

    }

    AcceptLoop(sock);//Accept Loop

    WSACleanup();//윈속 해제화

    return 0;

}

SOCKET SetTCPServer(short pnum,int blog)

{

    SOCKET sock;

    sock = socket(AF_INET, SOCK_STREAM,IPPROTO_TCP);//소켓 생성

    if(sock == -1){    return -1;    }

 

    SOCKADDR_IN servaddr={0};//소켓 주소

    servaddr.sin_family = AF_INET;

    servaddr.sin_addr = GetDefaultMyIP();

    servaddr.sin_port = htons(PORT_NUM);

 

    int re = 0;

    re = bind(sock,(struct sockaddr *)&servaddr,sizeof(servaddr));//소켓 주소와 네트워크 인터페이스 결합

    if(re == -1){    return -1;    }

    re = listen(sock,blog);//백 로그 큐 설정

    if(re == -1){    return -1;    }

    return sock;

}

void AcceptLoop(SOCKET sock)

{

    SOCKET dosock;   

    SOCKADDR_IN cliaddr={0};

    int len = sizeof(cliaddr);

    while(true)

    {

        dosock = accept(sock,(SOCKADDR *)&cliaddr,&len);//연결 수락

        if(dosock == -1)

        {

            perror("Accept 실패");

            break;

        }

        printf("%s:%d의 연결 요청 수락\n",inet_ntoa(cliaddr.sin_addr),

            ntohs(cliaddr.sin_port));

        DoIt(dosock);

    }

    closesocket(sock);//소켓 닫기

}

void DoIt(SOCKET dosock)

{

    char msg[MAX_MSG_LEN]="";

    while(recv(dosock,msg,sizeof(msg),0)>0)//수신

    {

        printf("recv:%s\n",msg);

        send(dosock,msg,sizeof(msg),0);//송신

    }   

    closesocket(dosock);//소켓 닫기

}

[소스 3.1]  서버 측 소스 Program.cpp

 

 

 

 

3.3TCP 에코 클라이언트 구현

 이번에는 TCP 에코 클라이언트를 구현합시다.

 

 먼저 약속한 포트 번호와 서버 주소 및 메시지 크기를 정의합니다.

#define PORT_NUM      10200

#define MAX_MSG_LEN 256

#define SERVER_IP        "192.168.34.50"

 클라이언트 측도 윈속 초기화부터 시작합니다.

int main()

{

    WSADATA wsadata;

    WSAStartup(MAKEWORD(2,2),&wsadata);//윈속 초기화      

 소켓을 생성합니다.   

    SOCKET sock;

    sock = socket(AF_INET, SOCK_STREAM,IPPROTO_TCP);//소켓 생성

    if(sock == -1){    return -1;    }

 서버의 소켓 주소를 설정하여 연결을 요청합니다. 클라이언트에서는 로컬 네트워크 인터페이스와 결합하는 과정은 선택 사항입니다. 여기에서는 생략합시다.

    SOCKADDR_IN servaddr={0};//소켓 주소

    servaddr.sin_family = AF_INET;

    servaddr.sin_addr.s_addr = inet_addr(SERVER_IP);

    servaddr.sin_port = htons(PORT_NUM);

    int re = 0;

    re = connect(sock,(struct sockaddr *)&servaddr,sizeof(servaddr));//연결 요청

    if(re == -1){    return -1;    }

 연결 성공하면 문자열을 입력받아 서버에 전송하는 것을 반복합시다.

    char msg[MAX_MSG_LEN]="";

    while(true)

    {

        gets_s(msg,MAX_MSG_LEN);       

        send(sock,msg,sizeof(msg),0);//송신

 만약 입력한 문자열이 "exit"이면 종료하기로 합시다.

        if(strcmp(msg,"exit")==0){    break;    }

 전송 후에 서버가 보낸 메시지를 수신하여 출력합시다.

        recv(sock,msg,sizeof(msg),0);//수신

        printf("수신:%s\n",msg);

    }   

    closesocket(sock);//소켓 닫기

    WSACleanup();//윈속 해제화

    return 0;

}

 

#include "common.h"

#define PORT_NUM      10200

#define MAX_MSG_LEN 256

#define SERVER_IP        "192.168.34.50"

int main()

{

    WSADATA wsadata;

    WSAStartup(MAKEWORD(2,2),&wsadata);//윈속 초기화           

   

    SOCKET sock;

    sock = socket(AF_INET, SOCK_STREAM,IPPROTO_TCP);//소켓 생성

    if(sock == -1){    return -1;    }

 

    SOCKADDR_IN servaddr={0};//소켓 주소

    servaddr.sin_family = AF_INET;

    servaddr.sin_addr.s_addr = inet_addr(SERVER_IP);

    servaddr.sin_port = htons(PORT_NUM);

 

    int re = 0;

    re = connect(sock,(struct sockaddr *)&servaddr,sizeof(servaddr));//연결 요청

    if(re == -1){    return -1;    }

 

    char msg[MAX_MSG_LEN]="";

    while(true)

    {

        gets_s(msg,MAX_MSG_LEN);       

        send(sock,msg,sizeof(msg),0);//송신

        if(strcmp(msg,"exit")==0){    break;    }

        recv(sock,msg,sizeof(msg),0);//송신

        printf("수신:%s\n",msg);

    }   

    closesocket(sock);//소켓 닫기

    WSACleanup();//윈속 해제화

    return 0;

}

[소스 3.2]  클라이언트 측 소스 Program.cpp

반응형