네트워크 및 보안/윈도우즈 소켓 통신 프로그램

4. 스레드를 이용한 멀티플레싱 [TCP/IP 소켓 통신 프로그래밍 with 윈도우즈]

언제나휴일 2016. 4. 7. 12:30
반응형

4. 스레드를 이용한 멀티플렉싱

 

 3장에서 만든 TCP 에코 서버는 동시에 하나의 클라이언트에게만 서비스를 제공할 수 있습니다. 하나의 서버가 동시에 여러 클라이언트에게 서비스를 제공하는 것을 멀티플렉싱(Multiplexing)이라 부릅니다.

 

 이번 장에서는 스레드를 이용하여 멀티플렉싱을 구현하는 방법을 알아보기로 합시다.

 

4.1 스레드

 스레드란 하나의 프로세스 내에서 독립적인 수행할 수 있는 작업 단위입니다.

 

 예를 들어 파일 공유 P2P 프로그램을 사용할 때 동시에 여러 개의 파일을 다운로드를 받는 것은 스레드를 사용한 대표적인 예입니다.

 

스레드를 시작하는 함수

#include <process.h>

unsigned int _beginthread (void (* _ThreadEntryPoint) (void *),unsigned _stacksize, void * param);

 

 스레드를 시작하는 함수에는 C 라이브러리 함수인 _beginthread, _beginthreadex외에도 Windows API 함수인 CreateThread 등이 있습니다. 이 책에서는 C 라이브러리 함수인 _beginthread를 이용할게요. 다른 스레드를 시작하는 함수도 입력 인자로 스레드 진입점 함수명(함수명은 코드 메모리 주소를 의미함)와 진입점에 전달할 인자입니다.

 

 _beginthread 함수도 첫번째 인자로 스레드 진입점을 입력 인자로 받으며 세번째 인자로 진입점에 전달할 인자를 받습니다. 그리고 두번째 인자는 스레드의 스택 크기인데 0을 전달하면 디폴트 크기를 사용합니다.

 

 스레드 진입점은 다음의 예처럼 입력 인자가 void 포인터를 받는 함수로 정의해야 합니다.

void ThreadEntryPointA(void *param)

{

    for(int i = 100; i<110;i++)

    {

        Sleep(rand()%100);

        cout<<i<<endl;

    }

}

 

 

 

#include <iostream>

using namespace std;

#include <process.h>

#include <Windows.h>

 

void ThreadEntryPointA(void *param)

{

    for(int i = 100; i<110;i++)

    {

        Sleep(rand()%100);

        cout<<i<<endl;

    }

}

void ThreadEntryPointB(void *param)

{

    for(int i = 200; i<210;i++)

    {

        Sleep(rand()%100);

        cout<<i<<endl;

    }

}

int main()

{

    _beginthread(ThreadEntryPointA,0,0);

    _beginthread(ThreadEntryPointB,0,0);

    for(int i = 0; i<10;i++)

    {

        Sleep(rand()%100);

        cout<<i<<endl;

    }

    cin.get();

}

[소스 4.1]  스레드 예제 1

 

 [소스 4.1] 예제는 main(프로세스 진입점, 메인 스레드 진입점이라도 부름)에서 서로 다른 두 개의 스레드를 시작한 후에 반복문에서 랜덤한 시간동안 멈추고 0에서 9까지 출력하고 엔터를 누르면 종료하도록 작성한 코드입니다. ThreadEntryPointA 스레드 진입점에서 100에서 109까지 출력하고 ThreadEntryPointB 스레드 진입점에서는 200에서 209까지 출력하게 작성하였습니다.

 이 프로그램을 실행하면 main 스레드 외에 두 개의 스레드가 동작하며 서로 간섭없이 독립적으로 동작합니다. 실제 출력 결과는 매 번 다를 수 있으며 다음처럼 세 개의 스레드(main 스레드와 main에서 가동한 두 개의 스레드)의 작업은 서로 간섭없이 동작함을 확인할 수 있습니다.


스레드 예제 1 실행 화면

[그림 4.1] 스레드 예제 1 실행 화면

 

 

 

 그리고 다음처럼 스레드에 인자를 전달할 수도 있습니다. 주의할 점은 스레드마다 독립적인 스택을 갖으므로 지역 변수의 주소를 인자로 전달하지 말아야 합니다.

 

#include <iostream>

using namespace std;

#include <process.h>

#include <Windows.h>

 

void ThreadEntryPointA(void *param)

{

    for(int i = 100; i<110;i++)

    {

        Sleep(rand()%100);

        cout<<i<<endl;

    }

}

void ThreadEntryPointB(void *param)

{

    for(int i = 200; i<210;i++)

    {

        Sleep(rand()%100);

        cout<<i<<endl;

    }

}

int main()

{

    _beginthread(ThreadEntryPointA,0,0);

    _beginthread(ThreadEntryPointB,0,0);

    for(int i = 0; i<10;i++)

    {

        Sleep(rand()%100);

        cout<<i<<endl;

    }

    cin.get();

}

[소스 4.2]  스레드 예제 2

 

 

4.2 TCP 에코 서버 - 스레드를 이용한 멜티플렉싱

 3장에서 작성한 TCP 에코 서버는 AcceptLoop 함수에서 하나의 클라이언트의 연결을 수락하면 해당 클라이언트에게 에코 서비스를 제공하는 작업이 끝나야 다른 클라이언트의 연결을 수락하였습니다.

 

 이번에는 하나의 클라이언트의 연결을 수락하면 클라이언트에게 에코 서비스를 제공하는 스레드를 시작하게 합시다.

void AcceptLoop(SOCKET sock)

{

    SOCKET dosock;   

    SOCKADDR_IN cliaddr={0};

    int len = sizeof(cliaddr);

    while(true)

    {

        dosock = accept(sock,(SOCKADDR *)&cliaddr,&len);//연결 수락

        if(dosock == -1)

        {

            perror("Accept 실패");

            break;

        }

        printf("%s:%d의 연결 요청 수락\n",inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));

        _beginthread(DoIt,0,(void *)dosock);

    }

    closesocket(sock);//소켓 닫기

}

 

 이처럼 연결 수락 과정에서 반환한 소켓으로 송수신하는 부분을 스레드를 이용하면 AcceptLoop 수행하는 부분과 연결을 수락한 클라이언트와의 송수신 부분을 독립적으로 수행할 수 있습니다. 물론 송수신하는 스레드는 하나의 클라이언트와의 송수신을 담당합니다.

 

 참고로 소켓 라이브러리에서는 getpeername 함수를 제공하여 상대 소켓 주소를 알아낼 수 있습니다.

    getpeername(dosock,(SOCKADDR *)&cliaddr,&len);//상대 소켓 주소 알아내기

 

 

#include "common.h"

#define PORT_NUM      10200

#define BLOG_SIZE       5

#define MAX_MSG_LEN 256

SOCKET SetTCPServer(short pnum,int blog);//대기 소켓 설정

void AcceptLoop(SOCKET sock);//Accept Loop

void DoIt(void *param); //송수신 스레드 진입점

int main()

{

    WSADATA wsadata;

    WSAStartup(MAKEWORD(2,2),&wsadata);//윈속 초기화           

    SOCKET sock = SetTCPServer(PORT_NUM,BLOG_SIZE);//대기 소켓 설정

    if(sock == -1)

    {

        perror("대기 소켓 오류");

        WSACleanup();

        return 0;

    }

    AcceptLoop(sock);//Accept Loop

    WSACleanup();//윈속 해제화

    return 0;

}

SOCKET SetTCPServer(short pnum,int blog)

{

    SOCKET sock;

    sock = socket(AF_INET, SOCK_STREAM,IPPROTO_TCP);//소켓 생성

    if(sock == -1){    return -1;    }

    SOCKADDR_IN servaddr={0};//소켓 주소

    servaddr.sin_family = AF_INET;

    servaddr.sin_addr = GetDefaultMyIP();

    servaddr.sin_port = htons(PORT_NUM);

    int re = 0;

    re = bind(sock,(struct sockaddr *)&servaddr,sizeof(servaddr));//소켓 주소와 네트워크 인터페이스 결합

    if(re == -1){    return -1;    }

    re = listen(sock,blog);//백 로그 큐 설정

    if(re == -1){    return -1;    }

    return sock;

}

void AcceptLoop(SOCKET sock)

{

    SOCKET dosock;   

    SOCKADDR_IN cliaddr={0};

    int len = sizeof(cliaddr);

    while(true)

    {

        dosock = accept(sock,(SOCKADDR *)&cliaddr,&len);//연결 수락

        if(dosock == -1)

        {

            perror("Accept 실패");

            break;

        }       

        printf("%s:%d의 연결 요청 수락\n",inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));

        _beginthread(DoIt,0,(void *)dosock);       

    }

    closesocket(sock);//소켓 닫기

}

void DoIt(void *param)

{

    SOCKET dosock = (SOCKET)param;

    SOCKADDR_IN cliaddr={0};

    int len = sizeof(cliaddr);

    getpeername(dosock,(SOCKADDR *)&cliaddr,&len);//상대 소켓 주소 알아내기

    char msg[MAX_MSG_LEN]="";

    while(recv(dosock,msg,sizeof(msg),0)>0)//수신

    {

        printf("%s:%d 로부터 recv:%s\n",inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port),msg);

        send(dosock,msg,sizeof(msg),0);//송신

    }  

    printf("%s:%d와 통신 종료\n",inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));

    closesocket(dosock);//소켓 닫기

}

[소스 4.3]  TCP 에코 서버 - 스레드를 이용한 멀티플렉싱

 

 클라이언트는 3장에서 작성한 TCP 에코 클라이언트를 사용하시길 바랍니다.

반응형