TCP socket library 만들기 [세번째] : Client Socket 준비 및 연결

server socket 이 준비되고 listen 까지 끝났으니 이제 양측의 연결을 위해서는 client socket 을 준비해야겠네요.
client 가 server 로 TCP 세션을 연결하기 위해서는 다음과 같은 함수들을 호출해야 합니다.

  • socket 생성
  • socket option 변경
  • connect
#include <tcp_socket.h>

int tcpcli::connect( char*     addr,
                            int         port )
{
    int                 ret;
    struct sockaddr_in  serv;
    int                 sock;

    if( addr == NULL ) return -1;

    memset((char *)&serv, 0x00, sizeof(struct sockaddr_in));

    // socket 생성
    sock = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP );
    if( sock < 0 ) return -2;

    m_sock_fd = sock;
    m_port    = port;
    strcpy(m_addr, addr);

    // socket option 변경
    ret = set_sock_option();
    if( ret < 0 ) return -3;

    serv.sin_family = AF_INET;
    serv.sin_addr.s_addr = inet_addr(addr);
    serv.sin_port = htons(port);

    // server 로 접속 시도
    ret = ::connect( sock,
                          (struct sockaddr *)&serv,
                          sizeof(struct sockaddr_in) );
    if( ret < 0 ) return -4;

    return sock;
}

위의 함수를 실행하면 connect 함수에 주어지는 server 의 주소와 port 로 접속을 시도합니다.
그런데, server 는 listen 까지 하고 client 는 connect 만 한다고 접속이 성공하는 것은 아닙니다.
server 에서는 listen socket 을 감시하고 있다가 접속 요청이 오면 accept 를 통해 새로운 연결 socket 을 accept 를 통해 만들어야죠.
listen 은 client 로부터의 연결요청을 pending 해두는 역할을 할 뿐이고, 실제적인 연결 성공은 그 pending queue 에서 연결 요청을 하나 꺼내서 accept 를 해주어야 되는 것입니다.

자, 이제 accept 를 해봅시다.

int tcpsvr::accept()
{
    struct sockaddr_in  cli;
    int                 addr_len;
    int                 new_sock;
    int                 flag;
    int                 ret;

    memset((char *)&cli, 0x00, sizeof(struct sockaddr_in) );

    addr_len = sizeof(struct sockaddr_in);

    // listen queue 에 pending 된 연결 요청 accept
    new_sock = ::accept( m_sock_fd,
                                   (struct sockaddr *)&cli,
                                   (socklen_t *)&addr_len );
    if( new_sock < 0 ) return -1;

    // accepted socket 을 non blocking 모드로 변경
    if( m_non_block_f > 0 )
    {
        flag = fcntl( new_sock, F_GETFL, 0 );
        ret = fcntl( new_sock, F_SETFL, flag | O_NDELAY );
        if( ret < 0 ) return -2;
    }

    m_accept_sock_fd = new_sock;

    return new_sock;
}

소스에서 보듯이 tcpsvr 객체 생성 시 non blocking 모드로 생성했다면 accepted socket 의 blocking 모드를 변경하여 사용하게 됩니다.

흠… 이제는 server 와 client 가 서로 연결할 준비는 된 것 같습니다.
이제 tcpsvr 와 tcpcli 객체를 만들어서 서로 연결하는 test code 를 작성해 봅시다.
(컴파일할 때 unused parameter warning 들이 뜰텐데 그건 신경쓰지 않으셔도 됩니다.
아직 send, recv 등 구현 안된 함수들이 있어서 그렇습니다.)

(1) server 프로그램

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <tcp_socket.h>

int main()
{
    int             ret;
    int             sock = -1;
    int             accept_sock = -1;
    struct timeval  timeout;
    fd_set          fd;
    tcpsvr *        svr = new tcpsvr(1,1,0);  // tcpsvr 객체 생성 (socket option 값 지정)

    // server socket 준비 및 listen
    sock = svr->open( 11111 );
    if( sock < 0 ) exit(1);

    printf("open okay...[%d]\n", sock);

    while(1)
    {
        // event 가 없을 경우 select 에서 기다려줄 시간 (timeout)
        timeout.tv_sec  = 1;
        timeout.tv_usec = 0;

        // fdset 초기화와 FD_SET ==> 되도록 while 안에서 매번 해주도록 한다.
        FD_ZERO(&fd);  
        if( sock > 0 ) FD_SET( sock, &fd );  // listen socket 을 fdset 에 셋팅

        // select 를 통해 fdset 에 설정된 FD 에 대해 Event 감시
        if( (ret = select( FD_SETSIZE, &fd, NULL, NULL, &timeout)) <= 0 )
        {
            // timeout 이나 error 이면 잠깐 쉬었다가 다시 시도
            // sleep here
            continue;
        }

        // sock (listen socket) 에 Event 가 감지되었을 경우
        if( FD_ISSET( sock, &fd ) )
        {
            // 만약 이전의 accept_sock 이 남아있는 듯하면 확인사살로 close 하고 초기화
            if( accept_sock > 0 )
            {
                close(accept_sock);
                accept_sock = -1;
            }

            // listen socket 에 있는 pending 연결요청을 하나 받아들인다.
            accept_sock = svr->accept();
            if( accept_sock < 0 )
            {
                printf("accept fail...[%d] [%s]\n", accept_sock, strerror(errno));
                // sleep here
                continue;
            }
            printf("accept okay...[%d]\n", accept_sock );
        }
    }

    return 0;
}

주석을 세심하게 달아두었으니 코드를 잘 보시기 바랍니다.

server 측은 위와 같이 listen socket 준비 후에는 while 을 돌면서 event 감지 로직을 지속적으로 수행해야합니다.
send 와 recv 까지 구현하고 실제로 데이터를 주고받는 sample 을 작성할 때는 위의 코드에서 fdset 에 accepted socket fd 도 설정해주는 부분이 추가되게 되는데 이는 나중에 살펴보도록 하겠습니다.
위의 코드에서 FD_ZERO(&fd); 와 FD_SET 부분은 반드시 while 문 안에 두는 것을 습관화하시기 바랍니다.
시스템에 따라서는 fdset 이 select 를 수행하고 나면 Event 가 도착한 FD 만 남기로 나머지 FD 는 clear 해주는 경우가 있습니다. 그래서, 매번 set 해주지 않으면 정상적으로 통신이 안됩니다.

(2) client 프로그램

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <tcp_socket.h>

int main()
{
    int      sock;
    tcpcli * cli = new tcpcli(1,1,0);  // tcpcli 객체 생성 : socket option 부여

    // server 로 접속 시도
    sock = cli->connect( (char *)"127.0.0.1", 11111 );
    if( sock < 0 ) exit(1);

    printf("open okay...[%d]\n", sock);

    while(1) // sleep here;

    return 0;
}

client 프로그램은 참 쉽죠 ? ^^  객체 만들고 connect 시도만 하면 됩니다.

자 이제 컴파일 후 server 프로그램을 먼저 실행해봅시다.

(1) cd socklib; make clean; make
(2) cd test; make clean; make
(3) ./svr

위와 같이 실행한 후 다른 창을 띄워서 netstat -na | grep 11111 을 수행해보면 다음과 같이 listen 상태의 socket 이 존재함을 볼 수 있습니다.

sh> netstat -na | grep 11111
tcp        0      0 0.0.0.0:11111               0.0.0.0:*                   LISTEN
다음은 또 다른 창에서 ./cli 와 같이 client 프로그램을 실행해보죠.
그런 다음 마찬가지로 netstat -na | grep 11111 을 수행해보면 다음과 같이 establish 된 tcp 세션이 하나 생겼네요. ^^
11111 port 로는 여전히 listen 하고 있습니다.
sh> netstat -na | grep 11111
tcp        0      0 0.0.0.0:11111               0.0.0.0:*                   LISTEN
tcp        0      0 127.0.0.1:11111             127.0.0.1:29444             ESTABLISHED
어때요 ?  참 쉽고 재미있죠 ?
개발하면서 가장 재미있는 순간이 이렇게 내가 짠 코드가 무언가 예상된 결과를 잘 수행해낼 때인 것 같습니다. ㅎㅎ
지금까지의 소스를 tar 로 묶어서 첨부합니다.
아직 완성된 것은 아닙니다. send, recv 등의 함수를 구현하고 실제 데이터를 주고 받아봐야죠. ^^

PS> 다 아시겠지만 혹시 실행하다가 아래와 같이 library 못찾는다는 에러가 나오면 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:..  요렇게 환경설정해주는 것 아시죠? ^^

cfile3.uf.271B9F35528EBE2B3626F2.gz

 

./svr: error while loading shared libraries: libtcp.so: cannot open shared object file: No such file or directory

You may also like...