ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Blocking, Unblocking 봉쇄/비봉쇄 통신
    C_C++ 프로그래밍/TCP_IP 2019. 6. 6. 22:09

          로재의 개발 일기      

    Blocking

    I/O를 직접 user level에서 작업이 불가능하다.

    이를 커널 장치에서 설정을 할 수 있으며

    일반적인 프로그래밍에서 별도의 작업이 없다면 

    봉쇄 통신을 사용하여 통신을 한다.


    일반적으로 생각하는 server의 역할에서

    client의 수신을 기다리는 동안 다른 작업을 하지 못하는 행위를 예시로 들 수 있다.

    생각해보면 자원 낭비가 일어나는 것이다.




    Unblocking

    비봉쇄 통신을 사용하여 통신을 한다.

    read()를 예시로 들면, 프로세스가 입력을 기다리는 동안 다른 작업을 할 수 있도록 처리를 한다.

    이때 입력이 없는 상태에서 특별한 오류가 없다면 정상적인 return 처리를 해주고

    특정 오류가 발생 시, err 처리를 해준다.




    이를 소켓에 적용하기 위해서 fcntl() 함수를 사용하기 때문에

    우선 fcntl()에 대해서 알아보자

    (그를 위해서 flock 구조체에 대해서 알아야 한다)



      flock?

    struct flock *lock 형태로 쓰이며

    다음과 같은 구조를 가지고 있다.

    1
    2
    3
    4
    5
    6
    7
    8
    struct flock {
            short   l_type;
            short   l_whence;
            off_t   l_start;
            off_t   l_len;
            pid_t   l_pid;
            __ARCH_FLOCK_PAD
    };
    cs


    l_type : 어떻게 잠금을 하고 해체를 할 지 정한다.

    F_RDLCK : 다른 프로세스가 읽기 잠금만 가능하고 쓰기 잠금은 못 한다.

    F_WRLCK : 다른 프로세스가 읽기 잠금과 쓰기 잠금 모두 불가능 하도록 한다.

    F_UNCLK : 잠금을 풉니다.


    l_whence : 블록의 위치를 정합니다.

    SEEK_SET : 파일의 시작 위치

    SEEK_CUR : 파일의 현재 위치

    SEEK_END : 파일의 끝을 기준으로 한다.


    l_start, l_len, ㅣ_pid

    1
    https://redcoder.tistory.com/
    cs

    l_where이 SEEK_SET이며

    l_start가 8이고

    l_len이 8이라면

    start가 8이므로 r부터

    len이 8이므로 r까지

    즉 redcorder에 해당됩니다.


    (SEEK_SET이며  l_start = 0이며 l_len = 0 이면 파일 전체에 해당합니다)


      fcntl?

    file control 이라는 뜻을 가지고 있는 함수이다.

    즉, 파일에 대한 정보를 가져 내부적으로 몇 가지 설정이 가능하다.


    1
    int fcntl(int fd, int cmd, struct flock *lock);
    cs

    fd : 파일 디스크팁터

    cmd : 사용할 명령. fd의 특성을 제어한다.

    lock : flags | O_NONBLOCK 등의 flags 설정이 가능하다.


    _DUPFD
    파일 지정자를 복사하기 위해서 사용한다.

    F_GETFD

    리턴 값으로 id에 대한 값을 넘겨준다.

    F_SETFD

    FD_CLOEXEC값을 지정된 비트값으로 설정한다.

    F_GETFL

    파일지정자에 대한 플래그값 호출시 지정한 플래그를 되돌려준다.

    F_SETFL

    파일지정자 fd 의 플래그를 재 설정한다. 현재는 단지 O_APPEND, O_NONBLOCK(비 봉쇄), O_ASYNC 만을 설정할수 있다. 

    F_GETOWN

    이것은 비동기 입출력과 관련되어서 사용된다.

    신호를 받는 프로세스의 ID를 얻기 위해서 사용한다.

    F_SETOWN

    비동기 입출력과 관련되어서 사용된다. 시그널 신호를 받는 프로세스의 아이디를 설정한다.


    간단한 예시를 보면서 이해를 해보자


    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
       // 파일이 잠겨있는지 확인하고 잠겨 있지 않으면
      // 잠금을 얻고
      // 잠겨 있을경우 잠김이 풀릴때까지 기다린다(F_SETLKW)
     int fd_isopen(int fd)
     {
          struct flock lock;
     
          lock.l_type = F_WRLCK;
          lock.l_start = 0;
          lock.l_whence = SEEK_SET;
          lock.l_len = 0;
     
          return fcntl(fd, F_SETLKW, &lock);
     }
     
      // 파일 잠금을 얻은후 모든 작업이 끝난다면
      // 파일 잠금을 돌려준다.
      int fd_unlock(int fd)
      {
          struct flock lock;
     
          lock.l_type = F_UNLCK;
          lock.l_start = 0;
          lock.l_whence = SEEK_SET;
          lock.l_len = 0;
     
          return fcntl(fd, F_SETLK, &lock);
      }
    cs



      echo_blocking_server.c

    다음은 봉쇄 서버에 대한 간단한 예제입니다.

    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
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    #include <sys/socket.h>
    #include <sys/stat.h>
    #include <arpa/inet.h>
    #include <stdio.h>
    #include <string.h>
     
    #include <fcntl.h>
    #include <stdlib.h>
    #include <unistd.h>
     
    #include <errno.h>
     
    #define MAXLINE  1024 
     
     
    // 비봉쇄 소켓 설정을 한다.
    int set_nonblock_socket(int fd)
    {
        int flags;
        // 소켓에 대한 정보를 얻기 실패한 경우
        if((flags = fcntl(fd, F_GETFL,0)) == -1)
        {
            perror("fnctl error");
            flags = 0;
        }
        // 비봉쇄 통신 설정을 한다.
        fcntl(fd, F_SETFL, flags | O_NONBLOCK);
    }
    int main(int argc, char **argv)
    {
        int server_sockfd, client_sockfd;
        int client_len, n;
        char buf[MAXLINE];
        struct sockaddr_in clientaddr, serveraddr;
        int client_fd_array[1024];
        int maxfd = 0;
        int i = 0;
     
        if(argc != 2){
                printf("Usage [%s] portnum \n", argv[0]);
                exit(0);
        }
     
        memset((void *)client_fd_array, -1sizeof(int)*1024);
     
        client_len = sizeof(clientaddr);
        if ((server_sockfd = socket (AF_INET, SOCK_STREAM, 0)) < 0)
        {
            perror("socket error : ");
            exit(0);
        }
        bzero(&serveraddr, sizeof(serveraddr));
        serveraddr.sin_family = AF_INET;
        serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
        serveraddr.sin_port = htons(atoi(argv[1]));
        
        if(bind (server_sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) == -1)
        {
            perror("Error");
        }
        if(listen(server_sockfd, 5== -1)
        {
     
        }
        set_nonblock_socket(server_sockfd);
        maxfd = server_sockfd;
        while(1)
        {
            memset(buf, 0x00, MAXLINE);
            client_sockfd = accept(server_sockfd, (struct sockaddr *)&clientaddr    ,
                                &client_len);
            if(client_sockfd == -1)
            {
                if(errno == EAGAIN)
                {
                    //printf("NON BLOCKING SOCKET : Temporarily unavailable\n");
                }
                else
                {
                    printf("Accept Error %d\n", errno);
                    continue;
                }
            }
            else
            {
                set_nonblock_socket(client_sockfd);        // 연결에 성공한 client_sock을 비봉쇄 설정한다.
                client_fd_array[client_sockfd] = 1;        // client file des 배열에 현재 sock이 연결에 설정했다는 표시를 해준다.
                printf("client_sockfd %d\n", client_sockfd);    // 과연 몇 번째 client 일까?
     
                if(client_sockfd > maxfd)                // 하나의 프로세스에서 여러 클라이언트를 처리해야하기 때문에 몇 개인지 알아야한다.
                    maxfd = client_sockfd;
                
                continue;
            }
            memset(buf, 0x00sizeof(buf));            // buf clear
            for(i = 0; i < maxfd +1; i++)            // 클라이언트들이 보낸 정보를 수시로 확인한다.
            {
                if(client_fd_array[i] != 1continue;    // 연결이 안됨
                client_sockfd = i;                        // 
                n = read(client_sockfd, buf, MAXLINE);    // 클라이언트가 보낸 데이터가 있는가?
                if(n == -1)                                // 없는 경우
                {
                    if(errno == EAGAIN)                    // 없지만 비봉쇄 통신으로 인해서 없다고 수신이 된 것이다.
                    {
                        //printf("NON BLOCKING SOCKET : Temporarily unavailable\n");
                    }
                    else                                // 비봉쇄 통신으로 인해서 err가 생긴 경우가 아니다.
                    {
                        printf("read Error %d\n", errno);
                        close(client_sockfd);
                        client_fd_array[client_sockfd] = -1;
                        break;
                    }
                }
                else if(n == 0)                            // 파일의 끝에 접근 더 이상 읽을 데이터가 없다.
                {
                    printf("close %d\n", errno);
                        close(client_sockfd);
                    client_fd_array[client_sockfd] = -1;
                    break;
                }
                else                                    // 데이터가 성공적으로 수신되었다.
                {
                    printf("Read Data %s", buf);
                    write(client_sockfd, buf, MAXLINE);    // 다시 클라이언트에게 보낸다.
                }
            }
        }
    }
    cs



      echo_client.c

    다음은 테스트를 진행하기 위한 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
    #include <sys/socket.h>  /* 소켓 관련 함수 */
    #include <arpa/inet.h>   /* 소켓 지원을 위한 각종 함수 */
    #include <sys/stat.h>
    #include <stdio.h>      /* 표준 입출력 관련 */
    #include <string.h>     /* 문자열 관련 */
    #include <unistd.h>     /* 각종 시스템 함수 */
     
    #define MAXLINE    1024
     
    int main(int argc, char **argv)
    {
        struct sockaddr_in serveraddr;
        int server_sockfd;
        int client_len;
        char buf[MAXLINE];
     
        if ((server_sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
        {
            perror("error :");
            return 1;
        }
     
        /* 연결요청할 서버의 주소와 포트번호 프로토콜등을 지정한다. */
        server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
        serveraddr.sin_family = AF_INET;
        serveraddr.sin_addr.s_addr = inet_addr("127.0.01");
        serveraddr.sin_port = htons(3500);
     
        client_len = sizeof(serveraddr);
     
        /* 서버에 연결을 시도한다. */
        if (connect(server_sockfd, (struct sockaddr *)&serveraddr, client_len)  == -1)
        {
            perror("connect error :");
            return 1;
        }
        while(1){
            memset(buf, 0x00, MAXLINE);
            read(0, buf, MAXLINE);    /* 키보드 입력을 기다린다. */
            if(strncmp(buf, "quit\n",5== 0){
                    break;
            }
            if (write(server_sockfd, buf, MAXLINE) <= 0/* 입력 받은 데이터를 서버로 전송한다. */
            {
                perror("write error : ");
                return 1;
            }
                memset(buf, 0x00, MAXLINE);
                /* 서버로 부터 데이터를 읽는다. */
                if (read(server_sockfd, buf, MAXLINE) <= 0)
                {
                       perror("read error : ");
                    return 1;
                }
            printf("Read : %s", buf);
        }
        close(server_sockfd);
        return 0;
    }
    cs




    참고 URL



    ※ 본 글은 개인 포트폴리오 혹은 공부용으로 사용하기 때문에, 무단 복사 유포는 금지하지만, 개인 공부 용도로는 얼마든지 사용하셔도 좋습니다




    반응형
Designed by Tistory.