본문 바로가기
CS/컴퓨터네트워크

[열혈] 멀티쓰레드 기반 서버구현

by sun__ 2020. 12. 22.

목적 : 멀티플렉싱 멀티쓰레드 기반 서버 구현에 대한 이해, 쓰레드 특징

 

 

프로그램 :

1. 멀티쓰레드를 이용해서 256명 이하의 다수의 사용자에게 채팅 서비스를 해주는 서버 (에코서버의 일종)

 


 

멀티프로세스 기반 서버는 프로세스 생성 오버헤드와 IPC 구현의 어려움 + 프로세스의 컨텍스트 스위칭에 드는 오버헤드 단점이 존재한다.

 

멀티프로세스의 특징을 유지하면서 단점을 어느정도 극복하기 위해 쓰레드가 등장했다. 컨텍스트 스위칭이 빠르고 쓰레드간 데이터 교환에 특별한 기법이 필요하지 않다.

 

하나의 프로세스 내에서 여러 쓰레드가 각각 스택영역만 갖고 힙과 데이터 영역(전역변수)은 공유한다.

프로세스와 쓰레드 차이

 

 


<쓰레드 관련 함수>

#include <pthread.h>

int pthread_create(
	pthread_t *restrict thread,			//생성할 쓰레드 id의 주소
    const pthread_attr_t *restrict attr,		//쓰레드 특성정보. NULL 전달시 default
    void *(*start_routine)(void*),			//쓰레드의 main함수 역할을 하는 함수포인터
    void *restrict arg						//3번 인자로 넘어간 함수에 전달할 인자
);
//성공 시 0, 실패 시 그 외값 반환

//정확한 이해하려면 restrict 따로 찾아봐야함. 책에선 따로 설명안해줌


int pthread_join(pthead_t tid, void **status);
/*
tid : tid에 해당하는 쓰레드가 종료될 때까지 block
status : tid쓰레드의 main함수가 반환하는 값이 저장될 포인터 변수
*/
//성공 시 0, 실패 시 그 외값 반환


int pthread_detach(pthread_t thread);
/*
tid : 쓰레드의 main 종료와 동시에 소멸시킬 쓰레드의 id전달
*/
//성공 시 0, 실패 시 그 외값 반환

쓰레드가 완전히 끝난 후에 다음 일을 진행하는 경우 join을 쓴다.

 

쓰레드가 언제 끝나도 상관이 없는 경우 detach를 쓴다.

 


<임계영역 문제>

 

main 프로세스가 종료되면 실행중이던 쓰레드들은 모두 종료된다.

 

어떤 데이터영역에 둘 이상의 쓰레드가 접근하면 critical section(임계영역) 문제가 생길 수 있다. 뮤텍스나 세마포어로 문제를 해결한다. (수업에선 뮤텍스만 다룸)

#include <pthread.h>

int pthread_mutex_init(
	pthread_mutex_t *mutx, 			//뮤텍스 참조 값 저장을 위한 변수의 주소 값
    const pthread_mutexattr_t *attr,	//뮤텍스 특성정보. 기본 NULL
)

int pthread_mutex_destroy(phtread_mutex_t *mutx);

int pthread_mutex_lock(pthread_mutex_t *mutx);
int phtread_mutex_unlock(pthread_mutex_t *mutx);

 

 

기본적으로 제공되는 함수들은 보통 쓰레드에 안전하지만 어떤 건 안전하지 않다(안전한 버전과 안전하지 않은 버전이 있다.). 아래와 같이 컴파일하면 안전하지 않은 함수들을 안전한 버전으로 실행하도록 해준다. (ex. gethostbyname -> gethostbyname_r)

$gcc -D_REENTRANT main.c -o mthread -lphtread

 

 


 

 

<서버 코드>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>

#define BUF_SIZE 100
#define MAX_CLNT 256  //256명의 사용자에게 서비스

void * handle_clnt(void * arg);
void send_msg(char * msg, int len);
void error_handling(char * msg);

//데이터 영역. 쓰레드간 공유할 것
int clnt_cnt=0;
int clnt_socks[MAX_CLNT];
pthread_mutex_t mutx;

int main(int argc, char * argv[]){
    int serv_sock, clnt_sock;
    struct sockaddr_in serv_adr, clnt_adr;
    int clnt_adr_sz;
    pthread_t t_id;

    if(argc!=2){
	printf("Usage : %s <port>\n", argv[0]);
	exit(1);
    }


///뮤텍스 생성, 서버소켓생성//
    pthread_mutex_init(&mutx, NULL);
    serv_sock=socket(PF_INET, SOCK_STREAM, 0);

    memset(&serv_adr, 0 , sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));

    if(bind(serv_sock, (struct sockaddr*) &serv_adr, sizeof(serv_adr))==-1)
	error_handling("bind() error");
    if(listen(serv_sock, 5)==-1)
	error_handling("listen() error");
/////////////////////////////

    while(1){
	clnt_adr_sz = sizeof(clnt_adr);
	clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
    
    //클라이언트가 새로 연결되면 이곳에 도달함

	pthread_mutex_lock(&mutx);
	clnt_socks[clnt_cnt++] = clnt_sock;		//데이터영역에 접근하는 경우 lock
	pthread_mutex_unlock(&mutx);

	//쓰레드를 하나 생성해서 클라이언트 하나를 처리하도록 함. 소켓 번호 전달
	pthread_create(&t_id, NULL, handle_clnt, (void*)&clnt_sock);		
	pthread_detach(t_id);			
	printf("Connected client IP: %s \n", inet_ntoa(clnt_adr.sin_addr));
    }
    close(serv_sock);
    return 0;
}

void * handle_clnt(void * arg){
    int clnt_sock = *((int *)arg);
    int str_len = 0, i;
    char msg[BUF_SIZE];

	//메세지를 받을 때까지 block, 받으면 echo. 상대방에서 끝내면 0읽어서 종료
    while((str_len=read(clnt_sock, msg, sizeof(msg)))!=0)
	send_msg(msg, str_len);
    
    //상대방과 연결이 끊어졌다면 이곳에 도달

    pthread_mutex_lock(&mutx);
    for(i=0; i<clnt_cnt; i++){
	if(clnt_sock == clnt_socks[i]){
	    while(i++<clnt_cnt-1)
		clnt_socks[i] = clnt_socks[i+1];
	    break;
	}
    }
    clnt_cnt--;
    pthread_mutex_unlock(&mutx);
    close(clnt_sock);
    return NULL;
}

void send_msg(char * msg, int len){
    int i;
    pthread_mutex_lock(&mutx);
    for(i=0; i<clnt_cnt; i++)
	write(clnt_socks[i], msg, len);
    pthread_mutex_unlock(&mutx);
}

void error_handling(char * msg){
	fputs(msg, stderr);
	fputc('\n', stderr);
	exit(1);
}

 

 

<클라이언트 코드>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <pthread.h>

#define BUF_SIZE 100
#define NAME_SIZE 20

void * send_msg(void * arg);
void * recv_msg(void * arg);
void error_handling(char * msg);

char name[NAME_SIZE] = "[DEFAULT]";
char msg[BUF_SIZE];

int main(int argc, char ** argv){
    int sock;
    struct sockaddr_in serv_addr;
    pthread_t snd_thread, rcv_thread;
    void * thread_return;

    if(argc!=4){
	exit(1);
    }

    sprintf(name, "[%s]", argv[3]);
    sock = socket(PF_INET, SOCK_STREAM, 0 );

    memset(&serv_addr, 0 ,sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_addr.sin_port = htons(atoi(argv[2]));

    if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1)
	error_handling("connect() error");

	//하나의 소켓을 사용하지만 메세지를 보내는 것과 받는 것을 다른 쓰레드에서 비동기적으로 수행하도록 함 
    pthread_create(&snd_thread, NULL, send_msg, (void *)&sock);
    pthread_create(&rcv_thread, NULL, recv_msg, (void *)&sock);
    pthread_join(snd_thread, &thread_return);
    pthread_join(rcv_thread, &thread_return);
    close(sock);
    return 0;
}

void * send_msg(void * arg){
    int sock = *((int *)arg);
    char name_msg[NAME_SIZE+BUF_SIZE];
    while(1){
	fgets(msg, BUF_SIZE, stdin);
	if(!strcmp(msg, "q\n") || !strcmp(msg, "Q\n")){
	    close(sock);
	    exit(0);
	}
	sprintf(name_msg, "%s %s", name, msg);
	write(sock, name_msg, strlen(name_msg));
    }
    return NULL;
}

void * recv_msg(void * arg){
    int sock = *((int *) arg);
    char name_msg[NAME_SIZE + BUF_SIZE];
    int str_len;
    while(1){
	str_len = read(sock, name_msg, NAME_SIZE+BUF_SIZE-1);
	if(str_len == -1)
	    return (void *)-1;
	name_msg[str_len] = 0;
	fputs(name_msg, stdout);
    }
    return NULL;
}

'CS > 컴퓨터네트워크' 카테고리의 다른 글

TCP/IP (2)  (0) 2020.12.22
TCP/IP (1)  (0) 2020.12.22
[열혈] 멀티플렉싱 기반 서버구현  (0) 2020.11.24
[열혈] 멀티 프로세스 기반 서버구현  (2) 2020.10.25
[열혈] dns  (0) 2020.10.24