4.1 TCP 에코 서버 – 스레드를 이용한 멀티플렉싱 [TCP/IP 소켓 프로그래밍 with 윈도우즈]

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);//상대 소켓 주소 알아내기

다음은 스레드를 이용한 TCP 에코 서버 소스 코드입니다.

#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);//소켓 닫기
}

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