• 계산기 Server - Client 만들기


  • Server
    - 1_server
     : 계산기 서버 초기 구조
     : log 메세지를 확인할 때, \n 빼먹으면 문제가 생길 가능성이 크다.
     : 프로토콜 설계
     : 데이터 파싱

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    #include <stdio.h>
     
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
     
    #include <arpa/inet.h>
    #include <netinet/in.h>
      
    int calculate(char op, int value1, int value2)
    {
        switch (op) {
            case '+': return value1 + value2;
            case '-': return value1 - value2;
            case '*': return value1 * value2;
     
            default:
                return -1;
        }
    }
     
    int main()
    {
        int sock = socket(PF_INET, SOCK_STREAM, 0);
     
        struct sockaddr_in saddr = {0, };
        saddr.sin_family = AF_INET;
        saddr.sin_port = htons(4000);
        saddr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0
     
        int option = true;
        socklen_t optlen = sizeof(option);
        setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &option, optlen);
     
        bind(sock, (struct sockaddr*)&saddr, sizeof saddr);
        listen(sock, 5);
     
        while (1)
        {
            struct sockaddr_in caddr = {0, };
            socklen_t clen = sizeof(caddr);
            int csock = accept(sock, (struct sockaddr*)&caddr, &clen);
     
            char* cip = inet_ntoa(caddr.sin_addr);
            printf("Connected from %s\n", cip);
     
     
            // TCP 는 데이터의 경계가 존재하지 않는다.
            //-------------
            char buf[1024];
            int ret = read(csock, buf, 1024);
            printf("ret : %d\n", ret);
            if (ret == 0) {
                close(csock);
                break;
            } else if (ret == -1) {
                perror("read()");
                close(csock);
                break;
            }
     
            char* p = buf;
            char op = *p;
            ++p;
     
            int value1 = *(int*)p;
            p += sizeof(int);
     
            int value2 = *(int*)p;
            p += sizeof(int);
     
            printf("%2d%2c%2d\n", value1, op, value2);
            int result = calculate(op, value1, value2);
     
            write(csock, &result, sizeof result);
     
            //-----------------
            close(csock);
        }
     
        close(sock);
    }


    - 2_server
     : 수식 여러번 받아서 계산을 할 수 있는 구조
     : tcp는 write와 read가 병합되는 형태의 스트림 단위의 처리방식, 데이터의 경계가 존재하지 않는다.(반면 udp는 패킷 덩어리로 보낸만큼 간다.)
     : 100번 보낸다고해서 100번을 받는것이 아니다. -> 패킷을 분리해야한다. -> 무조건 패킷의 길이가 들어가야한다.
     : 길이는 약 3배정도 여유있게 잡는 것이 좋다.
     : 서버는 온전하게 동작하게 만든 뒤, 고성능 서버로 바꾸는 것!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    #include <stdio.h>
     
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
     
    #include <arpa/inet.h>
    #include <netinet/in.h>
     
    int calculate(char op, int value1, int value2)
    {
        switch (op) {
            case '+': return value1 + value2;
            case '-': return value1 - value2;
            case '*': return value1 * value2;
     
            default:
                return -1;
        }
    }
     
    int main()
    {
        int sock = socket(PF_INET, SOCK_STREAM, 0);
     
        struct sockaddr_in saddr = {0, };
        saddr.sin_family = AF_INET;
        saddr.sin_port = htons(4000);
        saddr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0
     
        int option = true;
        socklen_t optlen = sizeof(option);
        setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &option, optlen);
     
        bind(sock, (struct sockaddr*)&saddr, sizeof saddr);
        listen(sock, 5);
     
        while (1)
        {
            struct sockaddr_in caddr = {0, };
            socklen_t clen = sizeof(caddr);
            int csock = accept(sock, (struct sockaddr*)&caddr, &clen);
     
            char* cip = inet_ntoa(caddr.sin_addr);
            printf("Connected from %s\n", cip);
     
     
            // TCP 는 데이터의 경계가 존재하지 않는다.
            //-------------
     
            // int len;
            // int ret = read(csock, &len, sizeof len);
     
            char buf[4096];
            int ret = read(csock, buf, sizeof buf);
            printf("ret : %d\n", ret);
            if (ret == 0) {
                close(csock);
                break;
            } else if (ret == -1) {
                perror("read()");
                close(csock);
                break;
            }
     
     
            while (ret > 0) {
                char* p = buf;
     
                int len = *(int*)p;
                p += sizeof(int);
     
                char op = *p;
                ++p;
     
                int value1 = *(int*)p;
                p += sizeof(int);
     
                int value2 = *(int*)p;
                p += sizeof(int);
     
                ret -= len;
     
                printf("%2d%2c%2d\n", value1, op, value2);
                int result = calculate(op, value1, value2);
     
                write(csock, &result, sizeof result);
            }
     
            //-----------------
            close(csock);
        }
     
        close(sock);
    }


    - 3_server

    9

    '10'

    '20'

    '-'


     
    : 2_server은 위의 패킷(스트림)이 올 경우 9에서 끊어질 수가 있다. -> readn 구현이 필요 -> block이 필요
    : TCP의 경계가 모호하거나, 데이터가 도달하지 않는 문제를 해결하기 위해서는 readn을 도입하면 된다.

    : ★★readn, tcp 패킷자체에 대한 프로토콜이 잘못될 가능성은 거의 없어진다. 즉, 길이 base로 만들어야한다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    #include <stdio.h>
     
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
     
    #include <arpa/inet.h>
    #include <netinet/in.h>
     
    int calculate(char op, int value1, int value2)
    {
        switch (op) {
            case '+': return value1 + value2;
            case '-': return value1 - value2;
            case '*': return value1 * value2;
     
            default:
                return -1;
        }
    }
     
    // TCP의 경계가 모호하거나, 데이터가 도달하지 않는 문제를
    // 해결하기 위해서는 readn을 도입하면 된다.
    int readn(int sd, char* buf, int len)
    {
        int n;
        int ret;
     
        n = len;
        while (n > 0)
        {
            ret = read(sd, buf, len);
            if (ret < 0) return -1;
            if (ret == 0) return len - n;
     
            buf += ret;
            n -= ret;
        }
     
        return len;
    }
     
     
     
    int main()
    {
        int sock = socket(PF_INET, SOCK_STREAM, 0);
     
        struct sockaddr_in saddr = {0, };
        saddr.sin_family = AF_INET;
        saddr.sin_port = htons(4000);
        saddr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0
     
        int option = true;
        socklen_t optlen = sizeof(option);
        setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &option, optlen);
     
        bind(sock, (struct sockaddr*)&saddr, sizeof saddr);
        listen(sock, 5);
     
        while (1)
        {
            struct sockaddr_in caddr = {0, };
            socklen_t clen = sizeof(caddr);
            int csock = accept(sock, (struct sockaddr*)&caddr, &clen);
     
            char* cip = inet_ntoa(caddr.sin_addr);
            printf("Connected from %s\n", cip);
     
     
            while (1)
            {
                int len;
                int ret = readn(csock, (char*)&len, sizeof(len));
                if (ret == 0) {
                    close(csock);
                    break;
                } else if (ret == -1) {
                    perror("readn()");
                    close(csock);
                    break;
                }
     
                char buf[4096];
                ret = readn(csock, buf, len);
                if (ret == 0) {
                    break;
                } else if (ret == -1) {
                    perror("readn()");
                    break;
                }
     
                // 이제 데이터를 파싱하면 됩니다.
                char* p = buf;
                char op = *p;
                ++p;
     
                int value1 = *(int*)p;
                p += sizeof(int);
     
                int value2 = *(int*)p;
                p += sizeof(int);
     
                printf("%2d%2c%-2d\n", value1, op, value2);
                int result = calculate(op, value1, value2);
     
                write(csock, &result, sizeof result);
            }
         
            //-----------------
            close(csock);
        }
     
        close(sock);
    }

    - 4_server
     : 패킷 객체화

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    #include <stdio.h>
     
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
     
    #include <arpa/inet.h>
    #include <netinet/in.h>
     
    #include "Packet.h"
     
    int calculate(char op, int value1, int value2)
    {
        switch (op) {
            case '+': return value1 + value2;
            case '-': return value1 - value2;
            case '*': return value1 * value2;
     
            default:
                return -1;
        }
    }
     
    // TCP의 경계가 모호하거나, 데이터가 도달하지 않는 문제를
    // 해결하기 위해서는 readn을 도입하면 된다.
    int readn(int sd, char* buf, int len)
    {
        int n;
        int ret;
     
        n = len;
        while (n > 0)
        {
            ret = read(sd, buf, len);
            if (ret < 0) return -1;
            if (ret == 0) return len - n;
     
            buf += ret;
            n -= ret;
        }
     
        return len;
    }
     
     
     
    int main()
    {
        int sock = socket(PF_INET, SOCK_STREAM, 0);
     
        struct sockaddr_in saddr = {0, };
        saddr.sin_family = AF_INET;
        saddr.sin_port = htons(4000);
        saddr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0
     
        int option = true;
        socklen_t optlen = sizeof(option);
        setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &option, optlen);
     
        bind(sock, (struct sockaddr*)&saddr, sizeof saddr);
        listen(sock, 5);
     
        while (1)
        {
            struct sockaddr_in caddr = {0, };
            socklen_t clen = sizeof(caddr);
            int csock = accept(sock, (struct sockaddr*)&caddr, &clen);
     
            char* cip = inet_ntoa(caddr.sin_addr);
            printf("Connected from %s\n", cip);
     
     
            while (1)
            {
                int len;
                int ret = readn(csock, (char*)&len, sizeof(len));
     
                printf("len : %d\n", len);
                if (ret != sizeof len) {
                    close(csock);
                    break;
                } else if (ret == -1) {
                    perror("readn()");
                    close(csock);
                    break;
                }
     
                char buf[4096];
                ret = readn(csock, buf, len);
                if (ret != len) {
                    break;
                } else if (ret == -1) {
                    perror("readn()");
                    break;
                }
     
                // 이제 데이터를 파싱하면 됩니다.
                Packet packet(buf);
                char op = packet.getByte();
                int value1 = packet.getInt32();
                int value2 = packet.getInt32();
     
                printf("%2d%2c%-2d\n", value1, op, value2);
                int result = calculate(op, value1, value2);
     
                write(csock, &result, sizeof result);
            }
         
            //-----------------
            close(csock);
        }
     
        close(sock);
    }

    ■ 패킷 클래스 

     ∴ 게임에서는? 

      : 패킷을 만드는 클래스를 하나 둔다.
      : Builder pattern? 객체가 만들어지는 공정은 동일하지만, 내부적으로 표현이 달라지는 형태의 객체를 만들 때 사용(ex. StringBuilder), 코드 반복을 줄일 수 있다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    #ifndef PACKET_H
    #define PACKET_H
     
    class Packet
    {
    public:
        inline Packet(char* buf) : start(buf), pos(buf) {}
     
        inline int getInt32() const
        {
            int ret = *(int*)pos;
            pos += sizeof(int);
     
            return ret;
        }
     
        inline char getByte() const
        {
            char ret = *(char*)pos;
            ++pos;
     
            return ret;
        }
     
        inline Packet& putByte(char value)
        {
            *pos = value;
            ++pos;
     
            return *this;
        }
     
        inline Packet& putInt32(int value)
        {
            *(int*)pos = value;
            pos += sizeof(int);
     
            return *this;
        }
     
        inline int build()
        {
            int len = pos - start;
            return len;
        }
     
     
    private:
        char* start;
        mutable char* pos;
    };
     
     
    #endif


  • Client
    - 1_client
     : 계산기 클라이언트 초기 구조
     : 패킷 만들기

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
     
    #include <errno.h>
    #include <stdio.h>
    #include <string.h>
     
    #include <iostream>
    using namespace std;
     
    // 계산기 클라이언트를 만들어보자.
    // 1. 프로토콜을 설계해야 한다.
    //  [op:1][lvalue:4][rvalue:4]
    int main()
    {
        int sd = socket(PF_INET,
                SOCK_STREAM, 
                IPPROTO_TCP);
     
        struct sockaddr_in addr = {0, };
        addr.sin_family = AF_INET;
        addr.sin_port = htons(4000);
        addr.sin_addr.s_addr = inet_addr("127.0.0.1");
     
        int ret = connect(sd, (struct sockaddr*)&addr, sizeof(addr));
        if (ret == -1) {
            perror("connect()");
            return -1;
        }
     
        printf("Connection succeed!!\n");
        //-------------
        char op = '+';
        int value1 = 10, value2 = 20;
        // cout << "value 1: "; cin >> value1;
        // cout << "value 2: "; cin >> value2;
        // cout << "op : ";     cin >> op;
     
        // 패킷을 만들어야 한다.
        char buf[1024];
        char* p = buf;
     
        *p = op;
        ++p;
     
        *(int*)p = value1;
        p += sizeof(int);
     
        *(int*)p = value2;
        p += sizeof(int);
        int size = p - buf;
     
        for (int i = 0 ; i < 100; i++) {
            write(sd, buf, size);
        }
     
        for (int i = 0 ; i < 100; i++) {
            int result;
            int n = read(sd, &result, sizeof result);
            cout << i+1 << "- result : " << result << endl;
        }
     
        close(sd);
    }


    - 2_client ~ 3_client
     : 수식을 여러번 보내서, 여러 결과값을 받을 수 있는 구조
     : 패킷의 길이까지 붙여서 패킷 만들기

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
     
    #include <errno.h>
    #include <stdio.h>
    #include <string.h>
     
    #include <iostream>
    using namespace std;
     
    // 계산기 클라이언트를 만들어보자.
    // 1. 프로토콜을 설계해야 한다.
    //  [op:1][lvalue:4][rvalue:4]
    int main()
    {
        int sd = socket(PF_INET,
                SOCK_STREAM, 
                IPPROTO_TCP);
     
        struct sockaddr_in addr = {0, };
        addr.sin_family = AF_INET;
        addr.sin_port = htons(4000);
        addr.sin_addr.s_addr = inet_addr("127.0.0.1");
     
        int ret = connect(sd, (struct sockaddr*)&addr, sizeof(addr));
        if (ret == -1) {
            perror("connect()");
            return -1;
        }
     
        printf("Connection succeed!!\n");
        //-------------
        char op = '+';
        int value1 = 10, value2 = 20;
        // cout << "value 1: "; cin >> value1;
        // cout << "value 2: "; cin >> value2;
        // cout << "op : ";     cin >> op;
     
        // 패킷을 만들어야 한다.
        char buf[1024];
        char* p = buf;
     
        *p = op;
        ++p;
     
        *(int*)p = value1;
        p += sizeof(int);
     
        *(int*)p = value2;
        p += sizeof(int);
        int size = p - buf;
        for (int i = 0 ; i < 1000; i++) {
            write(sd, &size, sizeof size);
            write(sd, buf, size);
        }
     
        for (int i = 0 ; i < 1000; i++) {
            int result;
            int n = read(sd, &result, sizeof result);
            cout << i+1 << "- result : " << result << endl;
        }
     
        close(sd);
    }

    - 4_client
     : 패킷 클래스로 만들기
     : 체이닝 방식 권장

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
     
    #include <errno.h>
    #include <stdio.h>
    #include <string.h>
     
    #include <iostream>
    using namespace std;
     
    #include "Packet.h"
     
     
    int main()
    {
        int sd = socket(PF_INET,
                SOCK_STREAM, 
                IPPROTO_TCP);
     
        struct sockaddr_in addr = {0, };
        addr.sin_family = AF_INET;
        addr.sin_port = htons(4000);
        addr.sin_addr.s_addr = inet_addr("127.0.0.1");
     
        int ret = connect(sd, (struct sockaddr*)&addr, sizeof(addr));
        if (ret == -1) {
            perror("connect()");
            return -1;
        }
     
        printf("Connection succeed!!\n");
        //-------------
        char op = '+';
        int value1 = 10, value2 = 20;
     
        // 패킷을 만들어야 한다.
        char buf[1024];
     
        // 체이닝 방식 - 완전한 객체로
        Packet packet = Packet(buf).putByte(op)
                                    .putInt32(value1)
                                    .putInt32(value2);
        // vs
        // 불완전한 객체로 존재
        // Packet packet(buf);
        // packet.putByte(op);
        // packet.putInt32(value1);
        // packet.putInt32(value2);
     
        int size = packet.build();
     
        /*
        char* p = buf;
        *p = op;
        ++p;
     
        *(int*)p = value1;
        p += sizeof(int);
     
        *(int*)p = value2;
        p += sizeof(int);
        int size = p - buf;
        */
        for (int i = 0 ; i < 10000; i++) {
            write(sd, &size, sizeof size);
            write(sd, buf, size);
        }
     
        for (int i = 0 ; i < 10000; i++) {
            int result;
            int n = read(sd, &result, sizeof result);
            cout << i+1 << "- result : " << result << endl;
        }
     
        close(sd);
    }


+ Recent posts