다중 사용자 처리 기법
- select() 이용
-
다중 프로세스 방식
- 사용자(또는 클라이언트)당 하나의 프로세스를 할당함
- preforking
-
다중 쓰레드 방식
- 사용자 당 하나의 쓰레드 할당
- prethreading
- 기타 : non-blocking socket이용, SIGIO처리
다중 사용자용 TCP서버
- listening socket 생성
-
listening socket을 이용, Client당 하나의 connected socket생성
- Client당 한번의 accept 호출
- int main(int argc, char **argv)
{
int listenfd, connfd;
pid_t childpid;
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
listen(listenfd, LISTENQ);
for ( ; ; ) {
clilen = sizeof(cliaddr);
connfd = accept(listenfd, (SA *) &cliaddr, &clilen);
if ( (childpid = fork()) == 0) { /* child process */
close(listenfd); /* close listening socket */
str_echo(connfd); /* process the request */
exit(0);
}
close(connfd); /* parent closes connected socket */
}
}
Preforking
-
선분기
- 사용자(또는 클라이언트)로 부터의 연결 요청 이전에 다수의 자식 프로세스를 미리 생성하여 "process pool"준비
- 연결 요청 수신 시 가용 자식 프로세스를 특정 사용자에게 할당
- Thundering herd 문제
-
기존의 방법
- 사용자로 부터의 요청을 받은 후 분기(fork)하여 자식 프로세스 생성
- 문제점 : fork() 호출 처리 시간 ▶ 처리 지연
Preforking 예제
- static int nchildren;
static pid_t pids[50];
intmain(int argc, char **argv)
{
int listenfd, i;
socklen_t addrlen;
void sig_int(int);
pid_t child_make(int, int, int); - listenfd = tcp_listen(argv[1], argv[2], &addrlen);
nchildren = atoi(argv[argc-1]); - for (i = 0; i < nchildren; i++)
pids[i] = child_make(i, listenfd, addrlen);
/* parent returns */ - signal(SIGINT, sig_int);
for ( ; ; )
pause();/* everything done by children */
} - pid_t child_make(int i, int listenfd, int addrlen)
{
pid_t pid;
void child_main(int, int, int);
if ( (pid = Fork()) > 0)
return(pid); /* parent */
child_main(i, listenfd, addrlen); /* never returns */
} - void child_main(int i, int listenfd, int addrlen)
{
int connfd;
void web_child(int);
socklen_t clilen;
struct sockaddr *cliaddr;
cliaddr = malloc(addrlen);
printf("child %ld starting\n", (long) getpid());
for ( ; ; ) {
clilen = addrlen;
connfd = accept(listenfd, cliaddr, &clilen);
web_child(connfd);
/* process the request */
close(connfd);
}
}
Preforking 서버 구현 방식
-
모든 자식 프로세스에서 accept() 호출
- accept() 호출 시점에서 block되어 client로부터의 연결을 기다림
- 하나의 자식 프로세스에서만 accept()함수가 호추되어야 할 경우 lock 이용
-
메인 프로세스에서 accept 호출, 자식 프로세스에 descriptor를 넘기는 방식
- UNIX domain socket, sendmsg/recvmsg 이용
Preforking 예제 - lock이용
Descriptor passing 방식
Passing descriptor
- int socketpair(int domain, int type, int protocol,
int socket_vector[2]);
UNIX domain socket 생성:
socketpair(AF_UNIX,SOCK_STREAM,0,sockfd); - ssize_t recvmsg(int sockdf, struct msghdr *msg, int flags);
ssize_t sendmsg(int sockdf, struct msghdr *msg, int flags); - struct msghdr {
void *msg_name;
socklen_t msg_namelen; /* Length of name */
struct iovec *msg_iov; /* Data blocks */
socklen_t msg_iovlen; /* Number of blocks */
void *msg_control; /* ancillary data */
socklen_t msg_controllen; /* Length of ancillary data */
unsigned msg_flags;
};
TCP multithreaded concurrent server
- int main(int argc, char **argv)
{
int listenfd, connfd;
pthread_t tid, clilen, addrlen;
struct sockaddr *cliaddr;
listenfd = tcp_listen(argv[1], argv[2], &addrlen);
cliaddr = malloc(addrlen);
signal(SIGINT, sig_int);
for ( ; ; ) {
clilen = addrlen;
connfd = accept(listenfd, cliaddr, &clilen);
pthread_create(&tid, NULL, &doit, (void *) connfd);
}
} - void *doit(void *arg)
{
pthread_detach(pthread_self());
web_child((int) arg);
close((int) arg);
return(NULL);
}
Pre-threading
(0)
(
