반응형


- 본 내용은 Linux (Ubuntu 14.04 lts)를 기반으로 제작되었습니다. -


- I/O Multiplexing


예를들어 네트워크 프로그램을 구현하다 보면, scanf중에는 printf가 되지 않는 현상들을 볼 수 있다.

즉, I/O가 병행적으로 진행되지 못하여 (I/O Blocking 발생) 그런 것인데, 이러한 방법을 해결하기 위해 다양한 방법들이 나타났다.


그중 Fork, 스레드에 대해서 이전 게시물에서 다루어 보았고, 

이번에는 하나의 프로세스, 하나의 스레드를 이용하는 I/O Multiplexing에 대해 다루어 볼 예정이다.


I/O Multiplexing을 구현하기 위해서 이용되는 함수는 select, poll, epoll, kqueue등이 있다.


I/O Multiplexing 기능은 하나의 프로세스가 여러 파일 디스크립터를 모니터링해서 

어떤 종류의 I/O 이벤트가 일어났는지 검사하고 각각의 파일 디스크립터가 Ready 상태가 되었는지 인지하는게 주요 목적이다.


이중 select와 poll에 대해서만 다룰 예정인데, 이번 게시물에서는 poll을 다루려 한다.




- poll이란?


select와 비슷한 기능을 하는 함수로 지정한 소켓의 변화를 확인하고자 사용되는 함수이다.


즉, 소켓셋에 저장된 소켓에 변화가 생길 때 까지 기다리고 있다가 


소켓이 어떤 동작을 하면 동작한 소켓을 제외한 나머지 소켓을 모두 제거하고 해당되는 소켓에 대해 진행을 한다.


이러한 poll 함수는 여러개의 fd를 동시에 모니터링 하다가 한개라도 읽을 수 있는 상태가 되면 blocking을 해제한다.



- poll의 특징


poll은 거의 select와 동일하지만 다음과 같은 차이가 있다.


1. file descriptor가 무제한적이다.


2. 좀더 low level의 처리로 system call의 호출이 select보다 적고, 이식성 나쁘다.



- poll 함수 사용 법


#include <poll.h>를 이용한다.


int poll(struct pollfd *fds, nfds_t nfds, int timeout);



인자 설명

1. struct pollfd 구조체명[개수];


이러한 struct pollfd의 구성은 다음과 같다.


struct pollfd{


int fd; // firle descriptor

short events; // request events

short revents; // returned events

};



short events에 입력할 수 있는 상수 목록


POLLIN :: 읽을 데이터가 있을 때(events / revents)

POLLPRI :: 긴급 데이터(Out-of-band Data)를 읽을 것이 있을 때(events / revents)


POLLOUT :: 바로 쓸 수 있는 상태일 때(events / revents)

POLLWRBAND :: 긴급 데이터(Out-of-band data)를 쓸 수 있을 때(events / revents)


POLLERR :: 주어진 file descriptor에 오류가 있을 때(revents only)

POLLHUP :: 주어진 file descriptor에서 event가 지체되고 있을 때(revents only)

POLLNVAL :: 주어진 file descriptor이 유효하지 않을 때(revents only)




2. nfds_t nfds 


fd의 개수를 입력하면 된다.


3. int timeout

millisecond 단위로 입력한다. (ex : 1000 = 1초)


-1 :: 무한 대기

0 :: 대기 없음



poll 함수의 리턴 값 



1이상 :: Event가 발생한 fd의 개수를 의미


0 :: timeout 발생


-1 :: 오류 발생








poll을 이용한 통신 소스 코드



<header.h>


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//header.h
 
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/select.h>
#include <poll.h>
 
#define PORT 20162
#define BUFFER_SIZE 100
#define LISTEN_QUEUE_SIZE 5
#define theNumberOfFDs 100
 
 
//                                                       This source code Copyright belongs to Crocus
//                                                        If you want to see more? click here >>
Crocus



<poll.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
#include "header.h"
 
 
int main(int argc, char* argv[])
{
    int listenFD, connectFD;
    struct sockaddr_in listenSocket, connectSocket;
 
    socklen_t addrSz; // address size를 구할 변수
 
    int i;
    ssize_t strLen;
    char buf[BUFFER_SIZE];
 
    //if (argc != 2)
    //{
    //    printf("Usage : %s <port>\n", argv[0]);
    //    exit(1);
    //}
 
    listenFD = socket(PF_INET, SOCK_STREAM, 0);
    memset(&listenSocket, 0sizeof(listenSocket));
 
    listenSocket.sin_family = AF_INET;
    listenSocket.sin_addr.s_addr = htonl(INADDR_ANY);
    listenSocket.sin_port = htons(PORT);
 
 
    if (bind(listenFD, (struct sockaddr *&listenSocket, sizeof(listenSocket)) == -1) {
        printf("Can not bind.\n");
        return -1;
    }
 
    if (listen(listenFD, LISTEN_QUEUE_SIZE) == -1) {
        printf("Listen fail.\n");
        return -1;
    }
 
    // pollfd 배열 구조체 생성
    struct pollfd pollFDs[theNumberOfFDs];
 
    pollFDs[0].fd = listenFD; // 0번째 배열에는 listen을 지정
    pollFDs[0].events = POLLIN; // 읽도록 만든다.
    pollFDs[0].revents = 0// 처음에는 0으로 초기화 한다(아직 아무 일도 일어나지 않았으니)
 
    for (i = 1; i < theNumberOfFDs; i++)
        pollFDs[i].fd = -1// 0번째 배열은 listen을 위한것이니 1번째부터 모두 -1로 초기화
 
    while (1)
    {
        int result = poll(pollFDs, theNumberOfFDs, -1); // -1 :: 무한 대기
 
        if (result > 0)
        {
            if (pollFDs[0].revents == POLLIN)
            {
                // 새로운 커넥션 요청이 들어왔을 때
                connectFD = accept(listenFD, (struct sockaddr*)&connectSocket, &addrSz);
 
                for (i = 1; i < theNumberOfFDs; i++)
                {
                    if (pollFDs[i].fd == -1// 비어있는 fd슬롯을 찾아서 넣어준다.
                    {
                        pollFDs[i].fd = connectFD;
                        pollFDs[i].events = POLLIN;
                        pollFDs[i].revents = 0;
                        break// 모두 다 넣고 break를 통해 한번만 실행
                    }
                }
            }
 
            for (i = 1; i < theNumberOfFDs; i++)
            {
                switch (pollFDs[i].revents)
                {
                    // no events
                    case 0:
                        break
 
                    // data is ready
                    case POLLIN:
                        strLen = read(pollFDs[i].fd, buf, BUFFER_SIZE);
                        printf("%lu bytes read\n", strLen);
 
                        buf[strLen] = '\0';
                        fputs(buf, stdout);
                        fflush(stdout);
 
                        write(pollFDs[i].fd, buf, strlen(buf));
 
                    
                    // 슬롯 초기화
                    default:
                        close(pollFDs[i].fd);
                        pollFDs[i].fd = -1;
                        pollFDs[i].revents = 0;
                        
                            
                }
            }
        }
 
        
    }
 
    close(listenFD);
 
    return 0;
}
 
//                                                       This source code Copyright belongs to Crocus
//                                                        If you want to see more? click here >>

Crocus



<client.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
//client.cpp
 
# include "header.h"
 
int main(int argc, char** argv)
{
    if (argc != 2
    {
        printf("Usage: %s IPv4-address\n", argv[0]);
        return -1;
    }
 
    struct sockaddr_in connectSocket;
 
    memset(&connectSocket, 0sizeof(connectSocket));
 
    connectSocket.sin_family = AF_INET;
    inet_aton(argv[1], (struct in_addr*&connectSocket.sin_addr.s_addr);
    connectSocket.sin_port = htons(PORT);
 
    int connectFD = socket(AF_INET, SOCK_STREAM, 0);
 
    if (connect(connectFD, (struct sockaddr*&connectSocket, sizeof(connectSocket)) == -1
    {
        printf("Can not connect.\n");
        return -1;
    }
 
    else 
    {
        int readBytes, writtenBytes;
        char sendBuffer[BUFFER_SIZE];
        char receiveBuffer[BUFFER_SIZE];
 
    //while(1)
    //{
            //서버에 문자열을 보낸 뒤 서버가 보낸 echo를 받아 출력.
            printf("input :: ");
 
            fgets(sendBuffer,BUFFER_SIZE,stdin);
 
            write(connectFD, sendBuffer, strlen(sendBuffer));
            
 
            readBytes = read(connectFD, receiveBuffer, BUFFER_SIZE);
            printf("%d bytes read\n", readBytes);
            receiveBuffer[readBytes] = '\0';
        
         fputs(receiveBuffer, stdout);
            fflush(stdout);
     //}
        
        
        
    }
 
    close(connectFD);
    return 0;
 
    
}    
 
//                                                       This source code Copyright belongs to Crocus
//                                                        If you want to see more? click here >>
Crocus







처음 서버를 열었을 때






 서버에 하나의 클라이언트가 들어왔을 






 서버에 3개의 클라이언트가 들어왔을 때




반응형