서버와 클라이언트의 연결을 다루려면, 소켓을 활용하여 둘 간의 연결을 이어주는 과정이 필요하다.
앞선 게시글인 윤성우의 열혈 TCP/IP 책에서 보았듯, 서버가 클라이언트와 연결해주는 과정은 다음과 같다.
[[Chapter 04 TCP 기반 서버 & 클라이언트 1]]
TCP 서버에서의 기본적인 함수 호출 순서
:socket()
->bind()
->listen()
->accept()
->read() / write()
->close()
socket()
을 통해 사용할 소켓의 프로토콜 체계의 정보, 전송타입 등에 대해 정하여 생성bind()
를 통해 만들어진 소켓에 IP주소와 포트번호를 할당하여 연결망에 연결listen()
을 통해 다른 소켓의 연결 요청을 대기하고 있는다.accept()
를 통해 소켓 간 연결을 수락.ft_irc에서도 동일한 과정을 통해 소켓 간의 연결을 한다.
앞서 배운 내용에서 추가적으로, 논블락킹으로 데이터의 막힘 없이 read/write가 이루어져야 하며, 여러 클라이언트를 받을 수 있어야하고 이벤트를 감지하여 발생한 이벤트에 알맞게 처리하면 된다.
개요에서 소개한 대로, 기본적인 함수 호출의 순서는 동일하다.
단 연결되어 있는 소켓에서 이벤트가 발생하는 것을 확인할 수 있어야한다. 이에 대한 역할을 하는 poll()
함수를 사용하였다.
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
타임아웃 값을 -1로 두어 이벤트가 발생할 때 까지 무한정 대기하도록 한다. 서버가 관리하고 있는 디스크립터 배열에서 이벤트가 발생 시 poll은 성공 시 준비된 파일 디스크립터의 개수를 반환한다. (타임아웃 시 0, 에러 발생시 -1)
listen()
과 accept()
사이에 poll()
을 사용한다.
이벤트가 발생 시, 발생한 이벤트에 대해 서버에 연결된 정보가 없는 소켓이라면 accept하고, 혹은 이미 accept된 경우 해당 소켓으로부터 받는 읽어들일 수 있는 메시지를 어떻게 처리할지 결정한다.
논 블락킹 모드 설정 시, 입출력 작업 시 즉시 반환되므로, 다른 작업을 병렬로 처리할 수 있다. 이를 통해 응답 시간을 줄일 수 있게된다.
이 때 소켓의 파일 디스크립터가 논 블락킹으로 동작하게끔 설정하는 것이 fcntl() 함수이다. (아마 File descriptor Control 인듯?)
과제에서는 다음과 같이 fcntl()
을 사용하도록 한다.
fcntl(fd, F_SETFL, O_NONBLOCK);
논 블락킹 모드 설정 시, 입출력 작업 시 즉시 반환되므로, 다른 작업을 병렬로 처리할 수 있다. 이를 통해 응답 시간을 줄일 수 있게된다.
소켓에 대해 해당 함수를 사용하여 논블락킹으로 만들어서 통신 시간에 발생하는 대기시간을 줄이도록 하자.
모니터링 중인 파일 디스크립터를 순회하며, 배열 인덱스 0번(서버 리스닝 소켓)에 이벤트 발생은 새로운 소켓의 연결시도이므로, accept()후 모니터링 파일 디스크립터에 추가를 해준다.
그 외의 소켓에서 발생하는 이벤트는 읽기 가능한 메시지(POLLIN)인지를 확인하거나, 소켓의 연결해제(POLLHUP)인지를 확인하여 각 이벤트에 맞게 처리하도록 한다.
(prefix) CMD param1 param2 ...
prefix에는 유저정보가 담기고 커맨드에는 PRIVMSG, JOIN 과 같은 명령어, 파라미터로는 그 명령어를 실행하는데 필요한 것을 담게된다.
형식에 맞게 잘 파싱하여 메시지의 정보를 잘 보관하는 것이 필요하다.
이후 메시지에 따라 프로토콜에 맞춰 정해진 응답코드를 리턴하는 등 서버가 행동하게끔 한다.
IRC 서버에 대해 공부해보면서, 이 서브젝트에서는 IO멀티플렉싱을 통해 하나의 프로세스로 여러 클라이언트의 메시지를 처리하도록 하는 방법에 대해 알아보게 되었다. 만약 여기서 더 나아간다면, 멀티쓰레드를 통한, 멀티 프로세스를 통한 서버를 다루어보는것도 좋은 방법이 될 듯 하다.
기회가 된다면 좀 더 공부해서 멀티쓰레드로 서버를 발전시켜보는 공부를 해보아야겠다.