본문 바로가기
잡동사니

pthread를 이용한 client-side socket programming

by L3m0n S0ju 2021. 12. 6.

 

 

주제       pthread 이용한 client-side socket programming

 

제출자    *

 

 

제출일자   11/8

 

 

Freeday   0

 

 

환경        Oracle VM virtualBox, 우분투 16.04 LTS, kernel: linux-4.4.0

 

 

 

목차

1.    과제 목적

2.    pthread 정의 사용 이유

3.    작성한 코드 설명

4.    실행 순서

5.    과제 수행 시의 어려웠던 점과 해결방법

 

 

 

 

 


1. 과제 목적

Client-side socket programming pthread 이용해 구현하여, 5개의 Blocking Socket 동시에 킷을 수신하는 application 작성

 

 

 

 

 

 

 

 


2.pthread 사용하는 이유

 

2.1      pthread 개념

pthread란 POSIX Thread의 약자로 유닉스계열 POSIX시스템에서 병렬적으로 작동하는 소프트웨 어를 작성하기 위하여 제공하는 API이다. 즉, pthread API를 통해 쓰레드를 구현할 수 있다. 쓰레 드란 프로레스 내에서 실행되는 흐름의 단위로 일반적으로 하나의 프로그램에서는 하나의 스레드 를 가진다. 하지만 pthread를 통해서 프로그램 내에서 여러가지 스레드를 동시에 실행할 수 있다.

thread의 장점은 다음과 같다.

 

 

1.   스레드 자원 공유

Thread는 한 프로세스 내에 존재하고, 해당 프로세스의 code, data, heap 영역을 서로 공유한다. 따라서 프로세스를 여러 CPU 통해 실행하는 보다 메모리를 효율적으로 사용할 있으며 IPC를 사용하지 않으면서 thread communication이 가능하므로 프로세스 실행 시 처리 속도가 빠르다.

 

 

2.   문맥교환(Context Switching) 시간 감소

Thread들은 프로세스 내에서 서로 code, data, heap 영역을 공유한다. 따라서 context switching 이 발생하는 경우에는 서로 공유하지 않는 영역인 stack만 저장하면 되므로 저장할 영역이 작아 지므로 overhead 크게 감소한다.

 

 

3.  Thread 병렬처리

기본적으로 thread는 한 프로세스 내에서 각자의 흐름을 갖기 때문에, 한 프로세스를 병렬적으로 실행하게 된다. 이런 특성 때문에 thread를 사용해서 실행한 프로세스의 실행 순서는 예측하기 어렵고, 동시에 병렬적으로 처리하므로 빠르게 프로세스를 실행할 있게 된다.

 

 

4.   오류 처리

프로그램의 일부분이 중단되거나 작업을 수행하더라도 프로그램의 수행이 계속되어 사용자에 대한 응답성이 증가한다. 한 개의 스레드가 오류가 나더라도 나머지 스레드들은 동작하기 때문에 오류에 대한 회복력이 증가한다.

 

 

2.2      pthread 사용하는 이유

이번 과제에서 작성한 클라이언트 프로그램은 아주 간단한 프로그램으로 위에서 설명한 스레드 간 자원 공유 그리고 문맥교환 시간 감소 같은 효과를 기대하기는 어렵다. 하지만 Thread의 병렬 처리를 통해 속도 향상의 효과를 기대할 있다. 순차적으로 소켓을 5 생성하고 서버에 연결 시도하는 보다 5개의 스레드를 통해 동시에 서버에 연결하는 것이 훨씬 빠르다. 다음으로 오류 처리를 고려할 때 스레드를 사용하지 않는 경우 중간에 한 개의 소켓에 오류가 발생했을 때 프로세스 전체에 영향을 끼치지만 스레드를 사용하는 경우 해당 스레드를 제외한 나머지 스레드 에는 영향을 끼치지 않기 때문에 조금 오류에 대해서 안전하다고 있다.

 

 

 

 

 

 

 


3. 작성한 코드 설명

 

<client.c -> main 함수 부분>

int main(int argc, char* argv[])
{
    pthread_t t_id[PORT_NUM];   // pthread id 생성
 
    for(int i=0; i<PORT_NUM ;i++)
    {
// thread_main 함수를 기준으로 5개의 쓰레드를 생성
        if(pthread_create(&t_id[i], NULL, (void*)thread_main, (void*)argv[i+1]) != 0)
        {
            puts("pthread_create() error"); return -1;
        }
    }
 
    for(int i=0; i<PORT_NUM; i++)
    {
        int result;
// thread 종료하기를 기다림
        if(pthread_join(t_id[i], (void**)&result)!=0)
        {
            puts("pthread_join() error"); return -1;
        }
    }
    return 0;
}

코드는 클라이언트 main함수이다. 쓰레드 생성에 사용할 쓰레드 id 선언하고 pthread_create 함수를 통해 thread_main 함수에 대한 5개의 쓰레드를 생성하는데 포트번호를 인 자로 전해준다. 다음으로 5개의 쓰레드가 종료되기를 기다리다가 모두 종료되면 0을 반환하고 프 로세스가 종료된다.

 

 

 

 

 

 

<소켓 초기화 셋팅 부분>

sock = socket(PF_INET, SOCK_STREAM, 0); // 소켓 생성
if(sock == -1)
    error_handling("socket() error");
 
// 소켓 초기화 셋팅
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;

serv_addr.sin_addr.s_addr = inet_addr("192.168.0.17");      // 서버 ip address
int port = atoi(arg);                     // 포트번호는 인자 arg 받아서 port 저장
serv_addr.sin_port = htons(port);
if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1) // connect 실행
    error_handling("connect() error!");

코드는 client.c thread_main 함수의 소켓 생성 connect 부분이다. socket 함수를 통해 켓을 생성하고 만약 error가 난 경우 error_handling 함수를 따로 구현하여 에러 처리를 하였다. 다음으로 소켓에 값을 세팅하였는데, 과정에서 IP 주소를 고정된 값으로 넣어주고 매개변수로 받은 port 번호를 이용하였다. connect 함수를 통해 서버에 연결을 요청한다.

 

 

 

 

 

 

 

<파일 생성 파일 쓰기 부분>

// file name setting
FILE *fp;
char* filename = malloc(sizeof(char)*10); // 파일 이름 저장할 메모리 영역 생성
strcpy(filename,arg);
strcat(filename,".txt");               
// {포트번호}.txt
fp = fopen(filename,"w");         // {포트번호}.txt 형식으로 파일 생성


// read log & write file
int i;
for(i=0; i<LOG_NUM; i++)       // LOG_NUM 만큼 데이터 수신 루틴 반복
{
    gettimeofday(&tv,NULL);
    tmp = localtime(&tv.tv_sec); 
   
read(sock, message, SIZE);     
// SIZE만큼 서버에서 오는 데이터 수신
    if(str_len==-1)
        error_handling("read() error!");
 
// 시간, 길이, 내용을 파일에 저장
    fprintf(fp,"Time:   %d:%d:%d.%d   Length:   %d   Content:   %s\n\n",tmp->tm_hour,tmp-
>tm_min,tmp->tm_sec,(int)(tv.tv_usec/1000),strlen(message),message);
    memset(message, 0 , sizeof(char)*SIZE);                                 // 버퍼 비우기

}

위 코드는 client.c thread_main 함수의 파일 생성 및 파일 쓰기 부분이다. malloc을 통해 파일 이름을 저장할 공간을 만들고 main 함수에서 thread_main 함수로 넘겨준 인자를 이용해 {포트번 }.txt 같은 형식의 파일 이름을 생성한다. 다음으로 서버에서 보낸 데이터를 수신하여 fprintf 통해 파일에 쓰기를 진행한다. 서버에서 보낸 데이터를 모두 수신할 만큼 SIZE LOG_NUM 적절히 설정해준다.

 

 

 

 

 

 

 

 

 

 


4. 실행 순서

1.    서버에서 ./server 입력하고 원하는 포트를 입력한다. server 프로그램을 실행하면 클라이언 트에 대한 요청을 기다린다.

 

 

 

 

 

 

2.    클라이언트 PC에서 client.c 아래와 같이 컴파일하고 포트 번호와 함께 실행한다.

 

 

 

 

 

 

 

3.    아래 그림과 같이 5 포트 모두 정상적으로 accept하면 클라이언트 측으로 데이터를 송신하 고 프로그램은 종료된다. 로그 파일은 client 프로그램의 디렉토리에 {포트번호}.txt 형식으로 저장된다.

 

 

 

 

 

 

 

 

 


4. 과제 수행 시의 trouble 및 troubleshooting 과정

 

 

1. pthread_create

 

설명을 들었을 때는 이해가 되어서 간단히 사용이 가능할 줄 알았는데, 막상 사용을 해보니 어려 움이 많았다. 마지막 매개변수에 하나의 값을 void pointer 형식으로 줄 수 있었는데, socket setting에는 port번호와 ip 주소 2가지가 필요했다. 딱히 다른 아이디어가 떠오르지 않아 IP 주소 를 그냥 고정된 주소로 주고 port번호에 대해서만 매개변수로 넘겨주었다. 또한 void pointer에 대 해 형변환이 필요한지 모르고 그냥 넣어줬다가 어려움을 겪게 되었다. Pthread_create와 함께 pthread_join에도 (void**)형식을 쓰는데, 마찬가지로 형변환을 해서 매개변수 전달을 해서 해결을 할 수 있었다.

 

 

 

 

2. Pthread_create, pthread_join

 

전반적인 작동과정에서 pthread_create는 thread를 생성하는 역할을, pthread_join은 모든 thread가 종료될 때까지 기다린 후에 같이 종료하게 된다. 5개의 thread를 만들고 기다린 후에 5개의 thread에 대해서 모두 기다린 후에 같이 종료한다는 말만 듣고는 사실 어떻게 코드를 짜야할지 막막했다. 결국 생각한 구조는 첫 번째 for문에서 thread 생성을 그리고 두 번째 for문에서 thread join을 한다면 문제가 없을 것 같았다. 또한 처음에 accept port 했을 때 분명히 for문을 1111 2222 3333 4444 5555로 넣어줬기 때문에 accept하는 순서대로 나올 것이라 예상했는데, 순차적인 순서대로 나오지 않아서 코드를 잘못 짠 줄 알았다. 조원과 상의 결과 pthread의 특성인 병렬처 리로 인해 실행한 프로세스의 실행 순서는 예측이 불가능하다는 것임을 알았고, 코드에 문제가 없음을 이해했다

댓글