본문 바로가기

CS지식/컴퓨터네트워크

소켓 프로그래밍 과제 3번

과제 3번) 서버에 수식을 보내서 계산하는 계산기 프로그램을 만들어라. 값을 반환하여 server의 화면에 보여라. (stack 이용한 계산기 프로그래밍)

 

 우리가 계산식을 쓸 때 사용하는 방법은 '중위표기식(infix expression)'이다. 2+3 과 같이 숫자들 사이에 연산자가 들어가는 방식이다. 사람이 눈으로 보면서 계산할 때에는 중위 표기식이 좋지만 컴퓨터는 앞에서부터 차례대로 데이터를 읽고 공간을 왔다갔다 하는데에 시간이 낭비되므로 '후위표기식(profix expression)' 이 더 적합하다. 후위 표기식은 23+와 같이 연산자를 피연산자 뒤에 놓는다. 중위표기식을 후위표기식으로 바꾸는 데에는 규칙이 존재한다. 우선 연산자를 담을 스택을 만들어주어야 한다.

 

1 . 피연산자는 그대로 출력한다.

2.  스택이 비어있으면 연산자는 자신을 바로 스택에 추가한다.

3. 만약 스택이 비어있지 않다면, stack의 top 부터 검사한다

(1) 내 자신보다 우선순위가 높거나 같은 연산자가 있다면 차례로 꺼내서 출력한 후 자신을 스택에 추가한다.

(2) 우선순위가 낮은 연산자가 있다면 그 위에 자신을 추가한다

4. 괄호는 우선순위가 가장 낮은 것으로 취급한다.

5. '('는 ')'가 아니라면 pop하지 않는다.

6. ')'가 나오면 '('가 나올 때 까지 차례로 꺼내서 출력한다.

7. 입력한식의 마지막에 도착하면 스택에서 차례로 꺼내서 출력한다.

 

<구현 방법>

이러한 계산을 구현하는 함수를 서버 측에 구현해주면 우리가 문제에서 구하고자 하는 계산기 프로그램이 완성된다.여기서 스택은 쓰여지기 전에 대기하는 연산자를 담는 역할을 하는데, 스택배열의 순서번호를 이용하여 연산자를 넣을 때는 순서번호를 하나씩 늘리고 스택에서 삭제(pop)할 때에는 순서번호를 하나씩 줄인다. 즉 , 가장 최근에 들어온 것이 먼저 나가게 된다.  

  

<서버 코드>

서버 코드에서 infixToPostfix() 함수는 입력받은 수식을 컴퓨터가 연산하기 쉽게 후위표기식으로 변환해준다.   

calculate()함수는 변환한 후위표기식을 계산한다. 계산한 결과값을 클라이언트에게 전송하고, 클라이언트의 출력창에 출력되도록 한다.  

//sol3_server.c

#include <stdio.h> //표준 입출력 라이브러리
#include <string.h> // 문자열 관련 함수 사용 위한 라이브러리
#include <unistd.h> // memset등 사용
#include <stdlib.h> // exit 등 사용
#include <sys/socket.h> // 소켓 관련 라이브러리
#include <arpa/inet.h> // 아파넷 관련 라이브러리

char stack[100] = { 0, }; // 문자 저장배열
double result[100] = { 0, }; // 결과 연산 및 결과값 계산 배열
int p_top = -1; // postfix 배열 순서 번호
int s_top = -1; // stack 배열 순서 번호
int r_top = -1; // result 배열 순서 번호
double temp = 0; // 결과값 저장 변수

char Priority(char c) { // 우선순위 지정 함수
    switch (c) {
    case '(':
        return 0;
    case '+':
    case '-':
        return 1;
    case '*':  
    case '/':
        return 2;

    }
}

char push(char infix[]) { // push함수 : 스택에 infix 문자열 집어넣는다.
    stack[++s_top] = infix;
    return stack;
}

char infixToPostfix(char infix[], char postfix[], char stack[]) { // infix를 postfix로 바꾸는 함수
    for (int i = 0; i < strlen(infix); i++) {
        if (infix[i] >= 48 && infix[i] <= 57) // infix[i]가 숫자일경우
            postfix[++p_top] = infix[i];
        else if (infix[i] == '(') // '('가 들어올경우
            stack[++s_top] = infix[i];
        else if (infix[i] == ')') { // ')'가 들어올경우
            for (; ; ) {
                if (stack[s_top] == '(') { //'('만나면 널값으로 바꾸고 for문 탈출
                    stack[s_top--] = NULL;
                    break;
                }
                postfix[++p_top] = stack[s_top]; //'('만나기 전까지의 스택의 연산자를 식에 집어넣는다.
                stack[s_top--] = NULL; //사용한 스택값 널로 변경.
            }
        }
        else if (Priority(stack[s_top]) < Priority(infix[i])) // 앞에있는 연산자보다 뒤에오는 연산자의 우선순위가 높을때
            stack[++s_top] = infix[i]; // 연산자를 그대로 스택에 넣는다

        else {
            while(Priority(stack[s_top]) >= Priority(infix[i])) { // 앞에있는 연산자보다 뒤에오는 연산자의 우선순위가 낮거나 같을 때,
            postfix[++p_top] = stack[s_top]; // 높은 우선순위를 가진 연산자가 스택에서 다 나올 때 까지 꺼낸다.
            stack[s_top--] = 0;
            }
            push(infix[i]); // 스택에 연산자 넣기
        }
    }
    while (s_top != -1) // 마지막 남은 stack의 연산자를 pop해주는 반복문
        postfix[++p_top] = stack[s_top--];
}

double calculate(char postfix[]) { // 값을 계산하는 함수
    for (int i = 0; postfix[i] != NULL; i++) { // postfix의 값을 하나씩 불러오면서 계산을 한다.
        if (postfix[i] >= 48 && postfix[i] <= 57) { // postfix[i]가 숫자일경우(아스키코드 값 48~57)
            postfix[i] = (int)postfix[i] - 48; //정수값으로 변환
            result[++r_top] = (double)postfix[i]; // 숫자를 result배열에 저장
        }
        else { // postfix[i]가 연산자일 경우
            if (postfix[i] == '+')
                temp = result[r_top - 1] + result[r_top];
            else if (postfix[i] == '-')
                temp = result[r_top - 1] - result[r_top];
            else if (postfix[i] == '*')
                temp = result[r_top - 1] * result[r_top];
            else if (postfix[i] == '/')
                temp = result[r_top - 1] / result[r_top];
            result[r_top--] = 0;
            result[r_top] = temp; //결과값을 result배열에 저장
        }
    }
    return result[0]; // 결과값 반환
}
// 에러 발생 시 내용 출력하는 에러 핸들러
void error_handling(char * message){
    fputs(message,stderr);
    fputc('\n',stderr);
    exit(1);
}

void main(){
    int ss, sa;
    char infix[100] = { 0, };
    char postfix[100] = { 0, };
    double result1 = 0;
    struct sockaddr_in ssa;

// ssa 구조체 초기화 후 통신타입, ip주소, 포트번호 지정
    memset(&ssa, 0, sizeof(ssa));
    ssa.sin_family = AF_INET;
    ssa.sin_addr.s_addr = htonl(INADDR_ANY); // 어떤 주소이든 상관없다.
    ssa.sin_port = htons(11234); // 포트번호 11234

// tcp 통신 소켓 생성
    ss = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(ss == -1) // 소켓 생성 실패할 경우 에러 메세지 출력
        error_handling("socket error");

//소켓과 서버 주소를 연결한다. 실패할 경우 에러 메세지 출력
    if(bind(ss,(struct sockaddr *) &ssa, sizeof(ssa))==-1){
        error_handling("bind error");
    }
   
// 서버는 무한루프를 돌면서 클라이언트의 요청이 있을 때 마다 서비스해준다.

    while(1){
   
    // 소켓을 사용가능한 상태로 활성화. 소켓 대기열의 크기는 10.
        if(listen(ss,10)==-1){
            error_handling("listen error");
        }

//클라이언트로부터 요청이 오면 연결을 수락한다.
        sa = accept(ss,0,0);
       
        if(sa ==-1){
            error_handling("accept error");
        }

        //클라이언트에게 계산할 식을 수신받는다.
        recv(sa,infix,sizeof(infix),0);
       
        // 계산 식을 스택을 이용한 후위연산식으로 변경한다.
        infixToPostfix(infix, postfix, stack);

        //계산을 수행하고 결과값을 result에 저장한다.
        result1 = calculate(postfix);

        //결과값을 클라이언트에게 전송.
        send(sa,&result1,sizeof(result1),0);

        //통신 종료 전 계산기 값 모두 초기화 시켜주기
        memset( stack, 0, sizeof(stack));
        memset( result, 0, sizeof(result));
        p_top = -1;
        s_top = -1;
        r_top = -1;
        temp = 0;
        close(sa); //클라이언트와의 통신 소켓 닫기
    }
 
    }

<클라이언트 코드>

//sol3_client.c

#include <stdio.h> //표준 입출력 라이브러리
#include <string.h> // 문자열 관련 함수 사용 라이브러리
#include <unistd.h> // memset 등 사용
#include <stdlib.h> // exit 등 사용
#include <sys/socket.h> // 소켓 관련 라이브러리
#include <arpa/inet.h> // 아파넷 관련 라이브러리

#define IPADDR "127.0.0.1"

// 에러 발생 시 내용 출력하는 에러 핸들러
void error_handling(char * message){
    fputs(message,stderr);
    fputc('\n',stderr);
    exit(1);
}

void main(){
    int cs;
    char infix[100] = { 0, };
    double result;  
    struct sockaddr_in csa;


    memset(&csa, 0, sizeof(csa));
    csa.sin_family = AF_INET;
    csa.sin_addr.s_addr = inet_addr(IPADDR);
    csa.sin_port = htons(11234);

// tcp 통신 소켓 생성
    cs = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(cs ==-1){ // 소켓 생성 실패할 경우 에러 메세지 출력
        error_handling("client socket error");
    }
    // 서버와 소켓 연결한다. 연결 실패 시 에러 메세지 출력
    if(connect(cs, (struct sockaddr *) &csa, sizeof(csa))==-1){
        error_handling("connect error");
    }

    //계산하고자하는 계산식을 입력한다
    printf("계산기 입력 : ");
    scanf("%s", infix); // 식을 입력받아 infix 문자열에 저장
    send(cs, infix, sizeof(infix), 0); // 문자열의 값을 서버에게 전송한다.

    recv(cs,&result,sizeof(result),0); // 계산식의 값을 서버로부터 받아 result 변수에 저장
    printf("Result : %s = %.3f\n",infix,r

 

<실행결과>