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
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
개발하면서 가장 재미있는 순간이 이렇게 내가 짠 코드가 무언가 예상된 결과를 잘 수행해낼 때인 것 같습니다. ㅎㅎ
아직 완성된 것은 아닙니다. 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