이 글은 윤성우의 TCP/IP 소켓 프로그래밍 책을 참고하였습니다.

순서

I. 멀티 스레드 흐름 간단 정리

II. 스레드의 소멸

III. 멀티 스레드 서버 구현

 


I. 멀티 스레드 흐름 간단 정리

1. 스레드는 멀티 프로세스와 다르게 한 프로세스 안에서 실행 흐름만 독립적, 힙과 데이터 영역은 공유했다.

2. pthread_create()로 스레드를 생성했다. 하지만 프로세스 반환 시, 스레드의 실행 끝을 기다려주지 않음!

3. pthread_join()으로 프로세스를 Block 걸어 스레드를 끝내고 프로세스를 반환했었다.

4. 하지만 공유하는 힙 및 데이터 주소에 동시 접근 문제 발생!!! (동기화 필요)
--> 접근 시 자물쇠 역할을 하는 뮤텍스와, 세마포어를 사용했었다.

5. 뮤텍스 또는 세마포어를 정의해주고 공유 영역(임계 영역) 시작과 끝에
pthread_mutex_lock(), pthread_mutex_unlock()
semaphore_wait(), semaphore_post()
사용해 다른 스레드의 주소 진입을 설정해주었다.

 


II. 스레드의 소멸

우리는 3번 단계의 pthread_join()을 이용해서 스레드를 기다려주었다.

하지만 이 함수의 문제점

1. 스레드 종료 시까지 Blocking상태로 남아있게 된다.
2. 멀티 스레드일 때 해당 스레드 이외의 다른 스레드의 자원은 할당 중이다.

이를 해결하기 위한 함수는 다음과 같다.

#include <pthread.h>

int pthread_detach(pthread_t thread);
    --> 성공 시 0, 실패 시 0 이외의 값 반환
    
    ㄴ. thread : 종료와 동시에 소멸시킬 쓰레드의 ID정보 전달

이 함수는 각 스레드에 선언되어, 스레드가 종료되면 그 즉시 자원을 반환한다.

동시에, 종료되지 않은 스레드가 종료되거나, Blocking에 걸리지도 않는다.

따라서 위의 함수로 스레드의 소멸을 유도하는 것이 좋다.

주의!! 해당 스레드를 대상으로 pthread_join 함수의 호출이 불가능하다.

 


III. 멀티 스레드 서버 구현

본 소스코드는 thread와 mutex를 사용한다.

기본 통신 구현 함수들을 일부 생략함.

<hide/>
#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;

void* handle_clnt(void* arg);
void send_msg(char* msg, int len);
void error(char* msg); // 디버깅용 코드는 생략, 주석으로 반환값만 알려줌.

// 임계영역임!
int clnt_cnt =0; // 현재 클라이언트 수
int clnt_socks[MAX_CLNT]; // 클라이언트 fd를 담은 배열
pthread_mutex_t mutx;

int main()
{
    int serv_sock, clnt_sock;
    struct sockaddr_in serv_adr, clnt_adr;
    int clnt_addr_size;
    pthread_t t_id;
    
    pthread_mutex_init(&mutx,NULL); // 뮤텍스 설정
    
    1. socket()
    2. serv_adr 구조체에 변수 입력
    3. bind()
    4. listen()
    
    while(1)
    {
        clnt_addr_size = sizeof(clnt_adr);
        clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_addr_size);
        
        // 뮤텍스 열고 현재 접속된 클라이언트 배열에 추가해줌
        pthread_mutex_lock(&mutx);
        clnt_socks[clnt_cnt++] = clnt_sock;
        pthread_mutex_unlock(&mutx);
        
        // 스레드 생성해줘서 클라이언트 계속 돌려줌
        pthread_create(&t_id, NULL, handle_clnt, (void*)&clnt_sock);
        // 방금 생성한 스레드의 detach로 소멸유도
        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;
    char msg[BUF_SIZE];
    
    // 메시지가 계속 들어오면 while문 돌고 길이가 0이면 탈출해 소켓닫음
    while((str_len = read(clnt_sock, msg, sizeof(msg))) !=0)
    {
       send_msg(msg, str_len);
    }
    
    // 주소 접근할것임
    pthread_mutex_lock(&mutx);
    // 현재 클라이언트 닫고 하나씩 앞으로 당겨옴
    for(int 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;
}

// 모든 클라이언트에게 다 write함
void send_msg(char* msg, int len)
{
    pthread_mutex_lock(&mutx);
    for(int i=0;i<clnt_cnt;i++)
        write(clnt_sock[i],msg,len);
    pthread_mutex_unlock(&mutx);
}
    

+ Recent posts