멀티플렉싱 서버
- 하나의 프로세스로 여러 클라이언트에 서비스를 하는 서버
- 서버의 리스닝 소켓을 bind, listen하고 클라이언트 쪽에서 connect가 들어오면 accept하여 클라이언트 소켓을 반환하고 그 소켓을 통해 클라이언트와 데이터 송수신
- 멀티 프로세스 서버에서는 connect마다 새로 프로세스를 만들어 하나의 프로세스마다 하나의 소켓을 관리
- 하지만 멀티 플렉싱 서버에서는 하나의 프로세스가 서버 소켓 + 여러 클라이언트 소켓을 관리함
- 관리할 때 fd_set이라는 구조체를 사용
fd_set
- fd를 관리하기 위해 디자인 된 구조체
- 배열 형태로 0번 인덱스부터 fd 0을 매핑하고 있음
- fd_set 관련 함수
-> FD_ZERO : 인자로 전달된 fd_set의 모든 비트를 0으로 초기화
-> FD_SET : 인자로 전달된 fd_set의 인덱스를 1로 설정
-> FD_CLR : 인자로 전달된 fd_set의 인덱스를 0로 설정
-> FD_ISSET : 인자로 전달된 fd_set의 해당 인덱스가 1이면 양수를 반환
select 함수
- 어느 소켓의 fd에 read, write, exception이 발생했는지 확인하는 함수
- fd_set을 전달하여 호출하면 변화가 발생한(입력 받은 데이터가 존대하거나 출력이 가능한 상황 등) 소켓의 디스크립터만 1로 설정
- fd_set에 대한 주소값을 전달하고 각 액션에 대한 결과를 적용하기 때문에 원본을 복사하여 복사본을 전달해야 함
#include <sys/select.h>
#include <sys/time.h>
int select(int maxfd, fd_set* readset, fd_set* writeset, fd_set* exceptset, const struct timeval* timeout);
struct timeval
{
long tv_sec; // 초
long tv_usec; // 밀리초
}
- 위와 같은 예시에서 readset에 fd_set을 전달했을 때 호출 후 아래와 같이 변했다면 fd1, fd3에 읽어들일 데이터가 있다(입력 버퍼에 데이터가 있다)라고 볼 수 있음
select를 이용한 멀티플렉싱 서버의 구현
- 1단계 : 서버 소켓과 fd_set 생성
int main(int argc, char *argv[])
{
int serv_sock, clnt_sock;
struct sockaddr_in serv_adr, clnt_adr;
struct timeval timeout;
fd_set reads, cpy_reads;
socklen_t adr_sz;
int fd_max, str_len, fd_num, i;
...;
serv_sock=socket(PF_INET, SOCK_STREAM, 0);
...;
if(bind(serv_sock, (struct sockaddr*) &serv_adr, sizeof(serv_adr))==-1) {
printf("bind() error");
}
if(listen(serv_sock, 5)==-1) {
printf("listen() error");
}
FD_ZERO(&reads); // fd_set 초기화
FD_SET(serv_sock, &reads); // 서버 소켓을 관리 대상으로 지정
fd_max=serv_sock; // 최대 파일 디스크립터 값
- 2단계 : select 함수 호출
while(1)
{
cpy_reads=reads; // 원본 fd_set 복사
timeout.tv_sec=5;
timeout.tv_usec=5000; // 타임아웃 설정
if((fd_num=select(fd_max+1, &cpy_reads, 0, 0, &timeout))==-1) {
break;
} // 아직 서버 소켓만 있으므로 connect 연결 요청 시 서버소켓에 데이터가 들어오게 됨
if(fd_num==0) {
continue;
} // 타임 아웃 시 continue
- 3단계 : 소켓에 따른 구분
-> for문으로 fd_set의 인덱스를 하나씩 순회하면서 변화가 있는 인덱스를 찾아냄
-> 만약 그 fd가 서버 소켓이면 connect 요청이므로 새로운 소켓을 생성하여 fd_set에 등록
for(i=0; i<fd_max+1; i++)
{
if(FD_ISSET(i, &cpy_reads))
{
if(i==serv_sock)
{
adr_sz=sizeof(clnt_adr);
clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
FD_SET(clnt_sock, &reads);
if(fd_max<clnt_sock)
fd_max=clnt_sock;
printf("connected client: %d \n", clnt_sock);
} // 변화가 일어난 소켓이 서버 소켓이면 connect 요청인 경우
else
{
str_len=read(i, buf, BUF_SIZE);
if(str_len==0) // close request!
{
FD_CLR(i, &reads);
close(i);
printf("closed client: %d \n", i);
}
else
{
write(i, buf, str_len); // echo!
}
} // 다른 소켓인 경우에는 데이터 read
}
}
}
close(serv_sock);
return 0;
}
'Programming > Network' 카테고리의 다른 글
열혈 TCP/IP 13-2. readv & writev 입출력 함수 (0) | 2021.03.14 |
---|---|
열혈 TCP/IP 13-1. send & recv 입출력 함수 (0) | 2021.03.14 |
열혈 TCP/IP 12-1. IO 멀티플렉싱 기반의 서버 (0) | 2021.03.05 |
열혈 TCP/IP 11-1. 프로세스간 통신의 기본 개념 (0) | 2021.03.01 |
열혈 TCP/IP 10-5. TCP의 입출력 루틴(Routine) 분할 (0) | 2021.03.01 |