열렬히.뛰기

13. 멀티플랙싱 : kqueue()

네트워크 > 소켓 프로그래밍 > 소켓 프로그래밍 > 13. 멀티플랙싱 : kqueue()

kqueue()란?

책에 없는 내용이지만, mac에서 작업하는 환경을 위해 추가.

  • BSD 계열에서 지원하는 Event 관리 system call. epoll과 비슷하게 동작한다.
  • select()와 poll()을 개선한 버전이라고 이해할 수 있다.
  • select()와 poll()은 등록한 모든 fd를 돌면서 상태 체크를 해야 한다. 반면 kqueue()는 이벤트가 발생한 fd에 대한 배열을 리턴해서 모든 fd를 검사할 필요가 없게 해준다. 따라서 O(N) -> O(1) 으로 감소한다.

이해할 때 필요한 사항

  • 이벤트라는 개념을 반드시 이해할 필요가 있다.
  • fd를 직접 다루지 않고, 이벤트를 통해서 다룬다.

kqueue()

c
#include <sys/time.h>
#include <sys/event.h>
#include <sys/types.h>

int kqueue(void);

새로운 이벤트 큐를 만들고, 해당 이벤트 큐의 fd를 리턴한다.

이벤트 큐(event queue) : kevent 구조체의 배열

kevent 구조체는 다음과 같이 생겼다.

c
struct kevent
{
		uintptr_t ident; // 이벤트에 대한 식별자, fd 번호
    int16_t filter;  // 이벤트에 대한 식별자, 이벤트의 종류에 해당
    uint16_t flags;  // event를 적용시키거나, event가 return 됐을 때 flag
    uint32_t fflags; // filter에 대한 flag
    intptr_t data;   // filter에 대한 data
    void *udata;     // user data
};

여기서 말하는 이벤트란 파일 디스크립터를 쓰고, 읽는 등의 행위를 이야기하는 것.

kevent()

select()와 같은 역할을 한다고 보면 된다.

c
int kevent
(
		int kq,
    const struct kevent *changelist,
    int nchanges,
    struct kevent *eventlist,
    int nevents,
    const struct timespec *timeout
);
  • kq : 이벤트 큐의 fd
  • changelist : eventlist에 담을 이벤트의 종류
  • nchanges : changelist의 길이
  • eventlist : 이벤트를 저장하는 배열
  • nevents : eventlist의 길이
  • timeout : 타임아웃

보통 while(1) 안 쪽에 이 함수를 쓰게 되며, 작동원리는 다음과 같다.

  1. kevent()는 계속 이벤트를 기다리고 있다.
  2. kq로 들어온(=발생한) 이벤트가 생긴다.
  3. 만약 그 이벤트가 change_list에 있으면, event_list에 담기게 된다.

EV_SET

change_list를 설정할 때 쓰는 함수.

c
EV_SET(&temp_event, ident, filter, flags, fflags, data, udata);

kqueue로 서버 만들기

에코서버의 구조

c
1. 서버소켓 초기화
2. 포트 및 ip설정
3. bind(), listen()
4. 소켓 상태를 논블록으로 설정
5. kqueue()로 kq 만들기
	- 이 kq는 이벤트가 들어왔음을 감지하는 역할.

6. EV_SET() 함수로 초기화
7. 
8. while(1) 가동 (첫번째 턴)
	1. 몇 개의 이벤트가 일어났는지 확인
	2. kevent() 가동
			- change_list 안에 있는 이벤트이면 event_list에 담아줌
			- 몇 개의 이벤트가 담겼는지 반환값으로 제공
	3. for()로 event_list를 돌려가며 지금의 fd가 무엇인지 판단
			1. 에러 처리
			2. "읽기" 이벤트이면
				1. 지금 fd가 서버 소켓이면 클라이언트 소켓을 만든다.
					1. 이후 change_list에 클라이언트 소켓의 이벤트를 추가한다.
				2. 지금 fd가 서버 소켓이 아니면 clients의 끝이 아닌지 판단.
					1. 클라이언트에서 데이터를 읽는다.
					2. 데이터가 없으면 에러처리
					3. 데이터가 있으면 출력

9. while(1) 가동 (n번째 턴, n >= 2)
	1. 몇 개의 이벤트가 일어났는지 확인
	2. kevent() 가동
			- change_list 안에 있는 이벤트이면 event_list에 담아줌
			- 몇 개의 이벤트가 담겼는지 반환값으로 제공
	3. for()로 event_list를 돌려가며 지금의 fd가 무엇인지 판단
			1. 에러 처리
			2. "읽기" 이벤트이면
				1. 지금 fd가 서버 소켓이면 클라이언트 소켓을 만든다.
					1. 이후 change_list에 클라이언트 소켓의 이벤트를 추가한다.
				2. 지금 fd가 서버 소켓이 아니면 clients의 끝이 아닌지 판단.
					1. 클라이언트에서 데이터를 읽는다.
					2. 데이터가 없으면 에러처리
					3. 데이터가 있으면 출력
			3. "쓰기" 이벤트이면
				1. 해당 fd에 작성.

참고자료

hyeonski.tistory.com/9