TCP Socket - Blocking / Non-Blocking

김정용·2020년 1월 1일
1

TCP 소켓의 Blocking 모드는 소켓이 블록 되는것을 의미합니다.
따라서 블록상태의 처리가 진행되기 전까진 다음 처리를 진행할 수 없습니다.
애플리케이션이 싱글스레드 모델이라면 블록상태에서 문제가 생겼을 경우 다음 처리를 진행할 수 없기 때문에 문제가 될 수 있습니다.

Non-Blocking 모드는 Blocking 소켓의 단점을 보완하기 위해 등장한 개념으로 소켓이 블록 되지 않고 즉시 소켓의 상태를 반환하게 됩니다.
이렇게되면 소켓의 상태를 주기적으로 체크하고 다른 처리를 진행할 수 있게됩니다.

기본적으로 소켓은 블로킹 모드로 동작하는데 fcntl 함수를 이용하면 지정한 fd를 Non-Blocking 소켓으로 변경할 수 있습니다.
다음은 fcntl로 소켓 fd를 Non-Blocking 모드로 설정하는 방법입니다.

#include <unistd.h>
#include <fcntl.h>

int flag = fcntl(sock_fd, F_GETFL, 0);
fcntl(sock_fd, F_SETFL, flag | O_NONBLOCK);

이해를 돕기위해 블로킹 모드 별 서버를 만들어보았다.

Blocking 소켓 서버


앞서 설명했듯이 소켓은 기본적으로 블로킹 모드로 동작한다고 했습니다.
따라서 블로킹 소켓 서버는 fcntl를 사용하지 않습니다.
다음 코드는 클라이언트를 accept하고 데이터를 read한 후에 other_routine을 실행하는 서버입니다.

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

#define BUF_SIZE 1024

void other_routine();

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

        int server_sock, client_sock;
        char buf[BUF_SIZE];

        if((server_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0 )
        {
                printf("socket create error\n");
                return -1;
        }

        int on = 1;
        if(setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
        {
                printf("socket option set error\n");
                return -1;
        }

        struct sockaddr_in server_addr, client_addr;
        int client_addr_size = sizeof(client_addr);
        server_addr.sin_addr.s_addr = INADDR_ANY;
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(atoi(argv[1]));

        if(bind(server_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0 )
        {
                printf("bind error\n");
                return -1;
        }

        if(listen(server_sock, 5) < 0)
        {
                printf("listen error\n");
                return -1;
        }

        printf("accept...\n");

        client_sock = accept(server_sock, (struct sockaddr *)&client_addr, (socklen_t *)&client_addr_size);
        if(client_sock < 0)
        {
                printf("accept error\n");
        }

        printf("accept client\n");

        int read_rtn;

        while(1)
        {
                printf("----- read wait\n");

                memset(buf, 0x00, sizeof(buf));
                read_rtn = read(client_sock, buf, sizeof(buf));

                if(read_rtn < 0)
                {
                        printf("----- read error\n");
                        printf("----- Socket close\n");
                        close(client_sock);
                        break;
                }
                else if(read_rtn == 0)
                {
                        printf("----- Socket close\n");
                        close(client_sock);
                        break;
                }
                else
                {
                        printf("----- read : %s\n", buf);
                }

                other_routine();
        }


        return 0;

}

void other_routine()
{
        printf("----- Other routine processing\n");
}

출력 결과

----- accept wait
----- accept client

----- read wait
----- read : A
----- Other routine processing

----- read wait
----- read : B
----- Other routine processing

----- read wait
----- read : C
----- Other routine processing

----- read wait

read에서 블록이 되므로 데이터를 수신했을 경우에만 other_routine이 실행되는 결과를 볼 수 있습니다.

Non-Blocking 소켓 서버


논 블로킹 소켓 서버에는 클라이언트 소켓만 논 블로킹 모드로 설정했다.
따라서 accept는 블로킹 모드로 동작하고 데이터를 read하는 부분만 논 블로킹 모드로 동작한다.

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>

#define BUF_SIZE 1024

void other_routine();

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

        int server_sock, client_sock;
        char buf[BUF_SIZE];

        if((server_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0 )
        {
                printf("socket create error\n");
                exit(1);
        }

        int on = 1;
        if(setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
        {
                printf("socket option set error\n");
                exit(1);
        }

        struct sockaddr_in server_addr, client_addr;
        int client_addr_size = sizeof(client_addr);
        server_addr.sin_addr.s_addr = INADDR_ANY;
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(atoi(argv[1]));

        if(bind(server_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0 )
        {
                printf("bind error\n");
                exit(1);
        }

        if(listen(server_sock, 5) < 0)
        {
                printf("listen error\n");
                exit(1);
        }

        printf("----- accept wait\n");

        client_sock = accept(server_sock, (struct sockaddr *)&client_addr, (socklen_t *)&client_addr_size);

        if(client_sock < 0)
        {
                printf("accept error\n");
                exit(1);
        }

        printf("----- accept client\n");

        int flag = fcntl(client_sock, F_GETFL, 0);
        fcntl(client_sock, F_SETFL, flag | O_NONBLOCK);

        int read_rtn;

        while(1)
        {
                sleep(1);

                printf("----- read wait\n");

                memset(buf, 0x00, sizeof(buf));
                read_rtn = read(client_sock, buf, sizeof(buf));

                if (read_rtn < 0)
                {
                        if(errno == EAGAIN)
                        {
                                printf("----- read EAGAIN\n");
                        }
                        else
                        {
                                printf("----- read error\n");
                                printf("----- Socket close\n");
                                close(client_sock);
                                break;
                        }

                }
                else if(read_rtn == 0)
                {
                        printf("----- Socket close\n");
                        close(client_sock);
                        break;
                }
                else
                {
                        printf("----- read : %s\n", buf);
                }

                other_routine();
        }

        return 0;

}

void other_routine()
{
        printf("----- Other rutine processing...\n");
}

(출력 결과)

----- accept wait
----- accept client

----- read wait
----- read EAGAIN
----- Other rutine processing...

----- read wait
----- read EAGAIN
----- Other rutine processing...

----- read wait
----- read EAGAIN
----- Other rutine processing...

----- read wait
----- read EAGAIN
----- Other rutine processing...

----- read wait
----- read : A
----- Other rutine processing...

----- read wait
----- read EAGAIN
----- Other rutine processing...

----- read wait
----- read EAGAIN
----- Other rutine processing...

----- read wait
----- read EAGAIN
----- Other rutine processing...

----- read wait
----- read EAGAIN
----- Other rutine processing...

----- read wait
----- read : B
----- Other rutine processing...

----- read wait
----- read EAGAIN
----- Other rutine processing...

----- read wait
----- read EAGAIN
----- Other rutine processing...

----- read wait
----- read EAGAIN
----- Other rutine processing...

----- read wait
----- read EAGAIN
----- Other rutine processing...

----- read wait
----- read : C
----- Other rutine processing...

----- read wait

read에서 블록이 되지 않으므로 데이터를 수신여부와 상관없이 other_routine이 계속해서 실행되는 결과를 볼 수 있습니다.

profile
Network Programming, Cloud, IOT, Web

1개의 댓글

comment-user-thumbnail
2021년 1월 31일

안녕하세요 정용님! 한가지 질문이 있어서 글 남겨봅니다.
int flag = fcntl(client_sock, F_GETFL, 0); //이 부분에서는 F_GETFL을 이용해 flag에 현재 어떤값(?)을 받고 (여기서 flag값은 어떤값이 되는건가요? 아마 블락킹모드 값이 나오는 건가요?)

fcntl(client_sock, F_SETFL, flag | O_NONBLOCK); //여기서는 위에 flag값과 O_NONBLOCK의 OR연산으로 덧샘(?)을 하는거라 저는 생각을 했는데 혹시 제가 잘못 이해한것이 있는지 궁금합니다.

답글 달기