3.2 TCP 에코 서버 구현 [TCP/IP 소켓 프로그래밍 with 윈도우즈]

먼저 클라이언트와 서버 사이에 약속할 부분을 정의합시다. TCP 에코 서버는 10200 포트를 bind 하여 사용하고 listen 함수에 설정할 백 로그 큐의 크기는 5로 설정할게요. 그리고 송수신 메시지 크기를 256으로 약속합시다.

#define PORT_NUM      10200
#define BLOG_SIZE       5
#define MAX_MSG_LEN 256

진입점에서는 윈속을 초기화한 후에 대기 소켓을 설정합니다. 대기 소켓 설정이 성공하면 클라이언트 연결 요청을 수락하는 작업을 반복하는 Accept Loop을 수행합니다. 그리고 모든 작업이 끝나면 윈속을 해제화합니다.

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;
    }

TCP 서버에서는 연결 요청 과정동안 클라이언트 정보를 기억하기 위한 백 로그 큐를 설정합니다.

    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)
    {

accept함수를 호출하면 클라이언트의 소켓 주소를 알 수 있고 송수신에 사용할 소켓을 반환합니다.

        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));

연결 수락하는 accept 함수가 반환한 소켓을 이용하여 송수신합니다.

        DoIt(dosock);
    }

모든 작업을 완료하면 소켓을 닫습니다.

    closesocket(sock);//소켓 닫기
}

이제 메시지 송수신 함수를 작성합시다.

void DoIt(SOCKET dosock)
{
    char msg[MAX_MSG_LEN]="";

에코 서버에서는 메시지를 수신하는 것을 반복합니다. 만약 상대가 소켓을 닫으면 recv 함수는 0을 반환합니다. 그리고 수신이 실패하면 -1을 반환합니다. 따라서 recv 함수가 양수를 반환하면 계속 수행합니다.

    while(recv(dosock,msg,sizeof(msg),0)>0)//수신
    {
        printf("recv:%s\n",msg);

에코 서버이므로 받은 메시지를 상대에게 전송합니다.

        send(dosock,msg,sizeof(msg),0);//송신
    }
    closesocket(dosock);//소켓 닫기
}

다음은 이번 실습에서 작성한 소스 코드입니다.

#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(SOCKET dosock); //송수신

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));
        DoIt(dosock);
    }
    closesocket(sock);//소켓 닫기
}
void DoIt(SOCKET dosock)
{
    char msg[MAX_MSG_LEN]="";
    while(recv(dosock,msg,sizeof(msg),0)>0)//수신
    {
        printf("recv:%s\n",msg);
        send(dosock,msg,sizeof(msg),0);//송신
    }    
    closesocket(dosock);//소켓 닫기
}