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 에서 자신이 필요한 함수만 구현해서 사용하도록 하기 위해서 위처럼 만든 것입니다.