TCP socket library 만들기 [첫번째] : 인터페이스(class) 설계

이번 시간부터 tcp socket library 를 만들어보겠습니다.
오늘은 사용자가 사용할 class interface 만 살펴보도록 하겠습니다.

앞으로 우리는 TCP, Inet UDP, Unix Domain Socket 을 위한 library 들을 만들 예정인데, 이러한 모든 것들의 공통점은 Socket 을 사용한다는 점입니다.
따라서, 앞으로 만들 모든 socket library 의 base 가 될 abstract base class 를 만들어두는 것이 좋을 것 같습니다.

이러한 class 설계 관련한 구조는 사람마다 기호가 좀 다릅니다. 앞으로 제가 즉석에서 구현하게 될 class 구조들이 마음에 들지 않으시다면 직접 변경해서 사용하셔도 좋습니다.
어차피 socket 에 대한 정확한 이해를 바탕으로 하고 있다면, 그것을 담아내는 그릇은 어떤 형태로든 변경해서 사용하면 됩니다.

앞으로 나오는 코드들은 비록 제가 짧은 시간에 설계해서 작성한 것이지만, 되도록이면 테스트 검증까지 마친 후 올리도록 하겠습니다.
그리고, 검증 환경은 linux 이기 때문에 다른 platform 이라면 포팅 과정을 별도로 거쳐야 합니다.

아래는 제가 만들어본 socket base class 입니다.

#ifndef __SOCKET_BASE_H__
#define __SOCKET_BASE_H__

#include <sys/types.h>
#include <sys/socket.h>
#include <linux/socket.h>
#include <netinet/tcp.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <sys/errno.h>

class socket_base
{
protected:
    int       m_sock_fd;
    int       m_accept_sock_fd;
    char*   m_addr;
    int       m_port;
    int       m_recv_buf_size;
    int       m_send_buf_size;
    int       m_non_block_f;
    int       m_no_delay_f;
    int set_sock_option( int sock );

public:
    socket_base( int   no_delay_f,
                        int   non_block_f,
                        int   buf_size )
    {
        m_no_delay_f    = no_delay_f;
        m_non_block_f   = non_block_f;
        m_recv_buf_size = buf_size;
        m_send_buf_size = buf_size;
    }

    virtual int close() = 0;
};

socket_base class 는 하나의 socket 에 대한 option 및 주소 정보 등이 저장됩니다.
모든 socket 객체들이 공통적으로 필요한 항목들을 base class 로 묶은 것입니다.

다음은 위의 socket_base class 를 상속한 tcp server 및 tcp client 용 class 입니다.

#ifndef __TCP_SOCKET_H__
#define __TCP_SOCKET_H__

#include <socket_base.h>

class tcpsvr : public socket_base
{
public:
    tcpsvr( int no_delay_f,
            int non_block_f,
            int buf_size )
        : socket_base( no_delay_f, non_block_f, buf_size ) {}

    int open( int port );
    int accept();
    int send( char * data, int size );
    int recv( char * data, int size, int timeout );
    int close();
};

class tcpcli : public socket_base
{
public:
    tcpcli( int no_delay_f,
            int non_block_f,
            int buf_size )
        : socket_base( no_delay_f, non_block_f, buf_size ) {}

    int connect( char * addr, int port );
    int send( char * data, int size );
    int recv( char * data, int size, int timeout );
    int close();
};

#endif

앞으로 tcpsvr 와 tcpcli class 의 객체를 만들어서 session 을 맺고(open, connect), 데이터를 송수신(send,recv)하며, session 의 종료를 수행하면 됩니다.
객체지향으로 설계를 했는데 이렇게 하면 객체 생성 시 socket option 값들을 미리 넘겨서 데이터 멤버로 보관해둘 수 있으므로, 각 멤버 함수들의 인자들이 확 줄어들어 편리합니다.
(위와 같이 no_delay_f, non_block_f, buf_size 를 생성자에게 넘겨서)

위의 함수 prototype 들을 보시면 send, recv, open 등의 함수들 모두 인자가 최소한으로 편리하게 줄어들어 있음을 확인할 수 있으실겁니다.

위의 class 를 이용하여 tcp server 및 client 는 다음과 같이 동작하게 될겁니다.

Server                                       Client
====================================
tcpsvr 객체 생성                      tcpcli 객체 생성
————————————————————–
obj->open(port);
————————————————————–
obj->connect(addr,port);
————————————————————–
obj->accept();
————————————————————–
obj->send(data,size);             obj->recv(data,size,timeout);
————————————————————–

엄청 간단해보이지 않나요 ?
원래 socket 프로그램 과정과 한번 비교를 해볼까요 ?

Server                                       Client
====================================
socket 생성                              socket 생성
————————————————————–
bind()
————————————————————–
setsockopt()                            setsockopt()
————————————————————–
listen()
————————————————————–
connect()
————————————————————–
accept()
————————————————————–
send()                                      recv()
————————————————————–

TCP Socket library 를 class 화하여 이용하니, 원래 과정에 비해 몇가지 step 은 줄일 수 있어서 굉장히 편리하겠네요.
자, 그러면 다음 시간에는 위의 함수들에서 open, connect, accept 를 구현하여 실제로 server 와 client 간이 TCP Session 을 한번 맺어보겠습니다.

PS> 위에서 server 와 client 의 공통함수인 send, recv 는 왜 socket_base 쪽의 virtual 함수로 빼지 않았냐고 하실 수 있는데 빼도 상관없습니다.
모든 socket 관련 함수를 socket_base 쪽으로 빼고, 하위의 class 에서 실제 구현해서 사용해도 됩니다.
저는 하위 class 에서 자신이 필요한 함수만 구현해서 사용하도록 하기 위해서 위처럼 만든 것입니다.

You may also like...