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);
이해를 돕기위해 블로킹 모드 별 서버를 만들어보았다.
앞서 설명했듯이 소켓은 기본적으로 블로킹 모드로 동작한다고 했습니다.
따라서 블로킹 소켓 서버는 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이 실행되는 결과를 볼 수 있습니다.
논 블로킹 소켓 서버에는 클라이언트 소켓만 논 블로킹 모드로 설정했다.
따라서 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이 계속해서 실행되는 결과를 볼 수 있습니다.
안녕하세요 정용님! 한가지 질문이 있어서 글 남겨봅니다.
int flag = fcntl(client_sock, F_GETFL, 0); //이 부분에서는 F_GETFL을 이용해 flag에 현재 어떤값(?)을 받고 (여기서 flag값은 어떤값이 되는건가요? 아마 블락킹모드 값이 나오는 건가요?)
fcntl(client_sock, F_SETFL, flag | O_NONBLOCK); //여기서는 위에 flag값과 O_NONBLOCK의 OR연산으로 덧샘(?)을 하는거라 저는 생각을 했는데 혹시 제가 잘못 이해한것이 있는지 궁금합니다.