본문 바로가기
개발/리눅스

[Linux]계산기 서버/클라이언트

by p_human 2020. 4. 2.

잡담

계산기 서버/클라이언트는 어느정도 생각을 해서 풀어야 하는 문제입니다. 클라이언트에서 서버에게 데이터를 어떻게 보내야 할 것인지에 대해서 생각을 해보고, 데이터를 받은 서버가 어떻게 계산해서 클라이언트에게 보내줄 것인지...

이렇게 생각하면서 문제를 풀어야 실력도 늘고, 막혔을 때 끙끙 앓다가 포기하는 것보다는 끝까지 구글링을 하면서, 결국에는 답을 찾아내는 것이 좋다고 생각합니다. 갑자기 왜 이런 소리를 하냐면 제가 이 문제를 풀 때 여태까지는 코드를 복붙만 해서 각 함수에 대한 기능만 알고 있지, 처음부터 끝까지 작성한 적이 없었거든요... 그래서 계산기 문제를 풀 때 좀 오래걸렸었어요. 제 기억에는 한 3시간 정도 붙들고 있었던 것 같아요.

이제 시작해보죠

클라이언트 입력 기능

클라이언트는 아래와 같이 입력을 받는 기능을 구현해야 합니다.

몇개의 숫자를 입력할 것인지에 대한 수 입력

위에서 입력받은 수만큼 숫자 입력

연산기호 입력(+, -, *)

계산기 클라이언트 입력기능

위 그림에서 보이는 것과 같이 숫자 몇개를 입력받을 지 정하고, 그 다음에 지정한 수만큼 숫자들을 입력한 뒤에 연산기호를 입력받는 구조를 설계하면 됩니다. 그냥 서버가 없었던 상황에서는 위의 구조를 만들기가 쉬웠지만, 이게 서버라는 개념이 추가되면서 생각할 것들이 많아졌습니다.

  • 클라이언트에서 데이터를 어떤 형식으로 몇 바이트를 보낼 것인지
  • 서버에서는 클라이언트로부터 전송된 데이터를 어떤 방식으로 처리할 것인지

제가 이 문제를 풀 때는 책에 나와 있는 답을 안보고 풀었기 때문에 효율적이지 못한 방법일 수도 있습니다. 그래도 봐주시면 감사하겠습니다.

 

처음에는 이걸 어떻게 보낼까에 대해서 많이 생각을 했습니다. 입력한 모든 데이터를 한개의 배열에 담아서 보내볼까도 생각해봤지만 차례차례 보내는게 좀 더 수월할 것 같아서 3개의 변수를 만들어서 입력한 데이터를 순서대로 보냈습니다.

그래서 서버에서도 데이터를 순서대로 받아서 처리하기 때문에 제가 작성한 서버/클라이언트는 이해하기가 쉬울 것입니다. (책에서는 입력한 데이터를 모두 모아서 한번에 보내기 때문에 좀 생각해야 될 포인터 및 배열 연산이 필요합니다.)

 

아래의 코드는 제가 작성한 클라이언트 입니다. 서버와 연결하기 위한 코드만 없다면 그냥 일반적인 계산기 코드입니다.

// C++ 계산기 클라이언트 프로그램
#include <stdio.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
void Error_Handling(const char* _message);

int main(int argc, char* argv[]){
    if(argc!=3){
        printf("Usage : %s <IP> <port>\n", argv[0]);
        exit(0);
    }

    int sock=socket(PF_INET, SOCK_STREAM, 0);
    if(sock==-1){
        Error_Handling("socket() error!");
    }
    
    struct sockaddr_in serv_adr;
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family=AF_INET;
    serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
    serv_adr.sin_port=htons(atoi(argv[2]));
    
    if(connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
        Error_Handling("connect() error!");
    else
        puts("Connected....");
	
    // 몇개의 숫자를 입력받을지
    // 그리고 입력한 크기만큼 데이터 전송
    int count;
    printf("Operand count : ");
    scanf("%d", &count);
    write(sock, &count, sizeof(int));
    
    // 숫자 입력하고 입력한 크기만큼 데이터 전송
    int *arr;
    arr = new int[count];
    for(int i = 0; i < count; i++){
        printf("Operand %d : ", i+1);
        scanf("%d", &arr[i]);
    }
    
    // 문자를 입력받기 위해서 미리 '\n'를 입력받아서
    // 엔터값을 없애준다. int * 입력한 수 만큼 데이터 전송
    fgetc(stdin);
    write(sock, arr, sizeof(int)*count);

    char oper = 0;
    printf("Operator : ");
    scanf("%c", &oper);
    write(sock, &oper, sizeof(char));
    
    // 서버로부터 전송된 데이터를 받아온다.
    int result = 0;
    if(read(sock, &result, sizeof(int))== -1)
    	Error_Handling("read() error!");
    
    printf("Operation result : %d\n", result);
    close(sock);
    delete[] arr;
    return 0;
}

void Error_Handling(const char* _message){
    fputs(_message, stderr);
    fputc('\n', stderr);
    exit(1);
}

다음은 서버 코드 입니다. 서버에서는 클라이언트로부터 전송된 데이터를 순서대로 받아서 들어온 연산기호에 따라서 계산을 다르게 처리한 후 다시 클라이언트에게 연산 결과를 전송하고 있습니다.

// C++ 계산기 서버 프로그램
#include <stdio.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
void Error_Handling(const char* _message);

int main(int argc, char* argv[]){
    if(argc!=2){
        printf("Usage : %s <IP> <port>\n", argv[0]);
        exit(1);
    }

    int serv_sock=socket(PF_INET, SOCK_STREAM, 0);
    if(serv_sock==-1)
        Error_Handling("socket() error!");
	
    struct sockaddr_in serv_adr;
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family=AF_INET;
    serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
    serv_adr.sin_port=htons(atoi(argv[1]));
    
    if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1)
        Error_Handling("bind() error!");

    int back_log = 2;
    if(listen(serv_sock, back_log)==-1)
        Error_Handling("listen() error!");

    struct sockaddr_in clnt_adr;
    socklen_t clnt_adr_sz = sizeof(clnt_adr);
	
    int clnt_sock;
    int count = 0;
    int arr_len = 0;
    int arr[10];
    char oper = 0;
    int result = 0;
    for(int i = 0; i < back_log; i++){
        // 클라이언트의 연결을 수락한 후에 새로운 소켓을 생성해서
        // 새로 생성된 소켓으로 데이터를 송수신한다.
        clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
        if(clnt_sock==-1)
            Error_Handling("accept() error!");
        printf("Connect client : %d\n", i+1);

        // 클라이언트가 몇개의 숫자를 입력받는지 전달받는다.
        count = 0;
        // read Client count
        if(read(clnt_sock, &count, sizeof(int))==-1)
            Error_Handling("read() error!");

        // 배열의 길이(숫자 개수만큼)만큼 데이터를 전달받는다.
        arr_len = 0;
        while(arr_len < count){
        // 배열의 각 방에 데이터를 입력한다.
        if(read(clnt_sock, &arr[arr_len++], sizeof(int))==-1)
            Error_Handling("read() error!");
        }
        
        // 연산기호를 전달받는다.
        if(read(clnt_sock, &oper, sizeof(char))==-1)
            Error_Handling("read() error!");
        result = arr[0];
        switch(oper){
        case '+':
            for(int i = 1; i < count; i++)
                result += arr[i];
        break;
        case '-':
            for(int i = 1; i < count; i++)
                result -= arr[i];
        break;
        case '*':
            for(int i = 1; i < count; i++)
                result *= arr[i];
        break;
        }
    
        // 클라이언트에게 4바이트만큼의 데이터를 전송한다.
        write(clnt_sock, &result, sizeof(int));
        close(clnt_sock);
    }
    close(serv_sock);

    return 0;
}

void Error_Handling(const char* _message){
    fputs(_message, stderr);
    fputc('\n', stderr);
    exit(1);
}

결론적으로 서버/클라이언트의 코드 흐름을 보면 클라이언트가 입력을 할 때마다 서버에게 데이터를 보내서 서버는 처리를 완료한 데이터를 클라이언트에게 전송합니다. 이로써 계산기의 서버/클라이언트에 대한 설명을 마쳤습니다.

 

마치며

윤성우님의 코드를 보고 싶은 분들은 제가 링크를 걸어드릴테니 회원가입을 하시고 자료실에 가셔서 다운 받으시면 됩니다. Chapter 5에 첨부되어 있습니다.

 

https://www.orentec.co.kr/jaryosil/TCP_IP_1/add_form.php

 

====== 오렌지 미디어 ======

『윤성우의 열혈 TCP/IP 소켓프로그래밍』 확인문제 답안

www.orentec.co.kr

 

내일은 TCP에 대한 이론에 대해서 알아보는 시간을 가져보도록 하겠습니다.

오늘도 제 글을 읽어주신 분들 감사합니다. 혹시 코드의 잘못된 부분이나 개선해야 될 점을 알려주시면 빠르게 수정하도록 하겠습니다. 감사합니다.