본문 바로가기
IT/programming

[C/C++] 리눅스 C언어 소켓 통신 서버, C++ socket server example

by 어느해겨울 2022. 1. 11.

리눅스 C언어 소켓 서버

socket server example 소스코드를 공유한다.

리눅스 C++ 로 작성되었고 이전에 포스팅했던 signal handler library 를 활용하였다.
https://muabow.tistory.com/200

 

[C/C++] signal handler library 활용과 소스코드 공유

signal handler library C/C++ 에는 signal.h 를 사용한 signal 함수가 존재한다. 시스템 인터럽트 외 여러 상황에 사용할 수 있기 때문에 필수로 시그널 처리를 하게 된다. 가장 쉬운 예를 들면 ctrl + c를 통해

muabow.tistory.com

 

자세한 개발환경, 디렉토리 구성, 빌드 방법 등은 github의 README.md 참고하자.

https://github.com/muabow/home/tree/main/src/cpp/socket_server

 

GitHub - muabow/home: C/C++, PHP, GO source/library

C/C++, PHP, GO source/library. Contribute to muabow/home development by creating an account on GitHub.

github.com

 

목적

소스코드를 클래스로 구성한 이유는 멀티 클라이언트를 처리할 수 있고 부가적인 기능을 하는 서비스를 제공하기 위함이다. 몇가지 특징을 설명한다.

 1. 서버의 동작 포트 변경 가능.

 2. 클라이언트 최대 접속 개수 변경 가능. (기본값: 20대, 최대 수용은 실행 환경에 따라 다름)

 3. 최대 접속 개수 만큼 클라이언트가 접속 시 이후 접속 차단.

 4. 접속하는 클라이언트의 IP 주소, 재접속 여부, 접속 시간, 끊김 시간을 보존함.

 5. 서버 동작의 alive 상태와 클라이언트의 접속 alive 상태 체크 가능.

 6. 서버 to 클라이언트 단방향 전송. 코드 수정 시 양방향 동작 가능.

등이 있다. 클래스 내 자잘한 메서드들은 필요 시 수정하여 사용하자.

 

구성

 1. 소스코드 : include/socket_server.h

 2. 소스코드 : src/socket_server.cpp

 3. 소스코드 : include/main.h

 4. 소스코드 : src/main.cpp

 5. 컴파일 및 실행

 6. 테스트 결과

 7. 테스트 클라이언트 결과

 

자세한 사항은 아래 소스코드를 살펴보자.


1. 소스코드 : include/socket_server.h

#ifndef __SOCKET_SERVER_H__
#define __SOCKET_SERVER_H__

#include <stdarg.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/select.h>
#include <limits.h>
#include <libgen.h>
#include <fcntl.h>
#include <sys/time.h>

#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include <mutex>
#include <tuple>
#include <memory>
#include <ctime>
#include <queue>

using namespace std;

class SOCKET_UnicastWorker {
    private :
        bool            is_debug_print = false;
        void            print_debug_info(const char *_format, ...);
        
        string          ip_addr;
        int             time_connect;
        int             time_disconnect;
        
        bool            is_run;
        bool            is_loop;
        
        int             index;
        int             socket_fd;
        int             num_reconnect;
        
        void            (*event_handle)(int, bool);
        
        thread          *p_thread_func;
        
        queue<tuple<char *, int>>    li_queue_data;
        
    public  :
        SOCKET_UnicastWorker(bool _is_debug_print = false);
        ~SOCKET_UnicastWorker(void);
        
        void            set_debug_print(void);

        void            set_index(int _index);
        
        bool            is_reconnect(string _ip_addr);
        bool            is_alive(void);    // get is_loop

        string          get_ip_addr(void);
        string          get_mac_addr(void);
        int             get_num_reconnect(void);
        int             get_timestamp(void);
        
        int             get_time_connect(void);
        int             get_time_disconnect(void);
        
        void            init(string _ip_addr, int _socket_fd);
        void            stop(void);
        void            run(void);
        void            reconnect(int _socket_fd);
        
        void            set_event_handler(void (*_func)(int, bool));
        void            set_queue_data(char *_data, int _size);

        void            execute(void);
};


class SOCKET_UnicastServer {
    typedef void (*func_ptr)(int, bool);
    
    const    int        DFLT_SERV_PORT        = 7593;
    const    int        DFLT_NUM_MAX_CLIENT   = 20;
    
    const    int        SIZE_SCALE_SNDBUF     = 10;
    const     int       SIZE_BACK_LOG         = 40;
    
    const    int        TIME_ACCEPT_SEC       = 1;
    const    int        TIME_ACCEPT_MSEC      = 0;
    const    int        TIME_RCV_TIMEOUT      = 2;
        
    private :
        bool            is_debug_print = false;
        void            print_debug_info(const char *_format, ...);
        
        bool            is_init;
        bool            is_run;
        bool            is_loop;
                
        int             socket_fd;
        int             port;
        
        int             num_max_client;
        int             num_current_client;
        int             num_accrue_client;
        
        mutex           mutex_counter;
        mutex           mutex_worker_func;
        
        thread          thread_func;
        
        void            (*event_handle)(int, bool);
        
        vector<unique_ptr<SOCKET_UnicastWorker>> v_worker_func;
        
    public  :
        SOCKET_UnicastServer(bool _is_debug_print = false);
        ~SOCKET_UnicastServer(void);
        
        void            set_debug_print(void);
        
        bool            is_alive_status(void);
        
        void            set_server_port(int _port);
        void            set_num_max_client(int _count);
        void            set_socket_option(int _socket_fd);
        
        string          get_client_info(int _idx, string _type);
        int             get_max_client_count(void);
        
        int             get_current_count(void);
        void            inc_current_count(void);
        void            dec_current_count(void);
        
        int             get_accrue_count(void);
        void            inc_accrue_count(void);

        void            reset_current_count(void);
        void            reset_accrue_count(void);

        bool            init(void);
        void            stop(void);
        void            run(void);
        
        func_ptr        get_event_handler(void);
        void            set_event_handler(void (*_func)(int, bool));
        void            send_data_handler(char *_data, int _size);

        void            execute(void);    // thread pool
};


#endif

 

2. 소스코드 : src/socket_server.cpp

#include "../include/socket_server.h"

//
// ## Unicast worker class
//
SOCKET_UnicastWorker::SOCKET_UnicastWorker(bool _is_debug_print) {
    if( this->is_debug_print ) {
        this->set_debug_print();
    }
    this->print_debug_info("SOCKET_UnicastWorker() create instance\n");

    this->is_run            = false;
    this->is_loop           = false;

    this->index             = -1;
    this->socket_fd         = -1;
    this->num_reconnect     = 0;
    
    this->time_connect      = 0;
    this->time_disconnect   = 0;
    
    this->p_thread_func     = NULL;
    this->event_handle      = NULL;
    
    this->li_queue_data     = queue<tuple<char *, int>>();
    
    return ;
}

SOCKET_UnicastWorker::~SOCKET_UnicastWorker(void) {
    this->print_debug_info("SOCKET_UnicastWorker() instance destructed\n");
    
    this->is_loop = true;
    if( this->p_thread_func != NULL ) {
        if( this->p_thread_func->joinable() ) {
            this->p_thread_func->join();
        }
    
        delete this->p_thread_func;
    }
    this->p_thread_func = NULL;
    
    return ;
}
        
void SOCKET_UnicastWorker::print_debug_info(const char *_format, ...) {
    if( !this->is_debug_print ) return ;
    
    printf("\033[31m");
    fprintf(stdout, "SOCKET_UnicastWorker::");
    va_list arg;
    va_start(arg, _format);
    vprintf(_format, arg);
    va_end(arg);
    printf("\033[0m");
    
    return ;
}

void SOCKET_UnicastWorker::set_debug_print(void) {
    this->is_debug_print = true;
    
    this->print_debug_info("set_debug_print() is set on\n");
    
    return ;
}

void SOCKET_UnicastWorker::set_index(int _index) {
    this->print_debug_info("set_index() worker instance index set [%d]\n", _index);
    this->index = _index;
    
    return ;
}

bool SOCKET_UnicastWorker::is_reconnect(string _ip_addr) {
    bool is_reconnect = false;

    this->print_debug_info("is_reconnect() check ip_addr: [%s/%s]\n", 
        this->ip_addr.c_str(), _ip_addr.c_str());

    if( this->ip_addr.compare(_ip_addr) == 0 ) {
        is_reconnect = true;
    }

    return is_reconnect;
}

bool SOCKET_UnicastWorker::is_alive(void) {
    
    return this->is_run;
}

string SOCKET_UnicastWorker::get_ip_addr(void) {

    return this->ip_addr;
}

int    SOCKET_UnicastWorker::get_num_reconnect(void) {
    
    return this->num_reconnect;
}

int SOCKET_UnicastWorker::get_timestamp(void) {
    int now_time = time(NULL);

    return now_time;
}

int SOCKET_UnicastWorker::get_time_connect(void) {
    
    return this->time_connect;
}

int SOCKET_UnicastWorker::get_time_disconnect(void) {
    
    return this->time_disconnect;
}

void SOCKET_UnicastWorker::init(string _ip_addr, int _socket_fd) {
    this->ip_addr   = _ip_addr;
    this->socket_fd    = _socket_fd;
    
    this->num_reconnect = 0;
    this->p_thread_func = NULL;
    
    this->print_debug_info("init() [/%s] worker ready\n", this->ip_addr.c_str());
    
    return ;
}

void SOCKET_UnicastWorker::stop(void) {
    if( this->p_thread_func != NULL ) {
        this->is_loop = true;
        
        if( this->p_thread_func->joinable() ) {
            this->print_debug_info("stop() join & wait worker thread term\n");
            this->p_thread_func->join();
        }
        
        this->print_debug_info("stop() worker thread delete\n");
        delete this->p_thread_func;
        this->p_thread_func = NULL;
    
    } 
    this->is_run = false;

    return ;
}

void SOCKET_UnicastWorker::run(void) {
    if( this->is_run ) {
        this->print_debug_info("run() already running\n");
        return ;
    }
    this->is_run  = true;
    this->is_loop = false;

    this->time_connect = this->get_timestamp();
    this->event_handle(this->index, true);
    
    this->print_debug_info("run() create worker thread\n");
    this->p_thread_func = new thread(&SOCKET_UnicastWorker::execute, this);
    
    return ;
}

void SOCKET_UnicastWorker::reconnect(int _socket_fd) {
    this->stop();

    this->is_run    = true;
    this->is_loop   = false;
    this->socket_fd = _socket_fd;

    this->time_connect = this->get_timestamp();
    this->num_reconnect++;
    this->event_handle(this->index, true);
    
    this->print_debug_info("reconnect() worker reconnect [%d] : [%s]\n", this->num_reconnect, this->ip_addr.c_str());
        
    this->p_thread_func = new thread(&SOCKET_UnicastWorker::execute, this);
    
    return ;
}

void SOCKET_UnicastWorker::set_event_handler(void (*_func)(int, bool)) {
    this->print_debug_info("set_event_handler() set function\n");
    this->event_handle = _func;
    
    return ;
}

void SOCKET_UnicastWorker::set_queue_data(char *_data, int _size) {
    if( this->is_run ) {
        this->li_queue_data.push(make_tuple(_data, _size));
    }
    
    return ;
}
    
void SOCKET_UnicastWorker::execute(void) {
    this->print_debug_info("execute() run worker thread : [%d/%s]\n", this->index, this->ip_addr.c_str());
    
    tuple<char *, int> tp_data;
    char  *data_ptr = NULL;
    int  data_length;
    int  rc;

    this->li_queue_data    = queue<tuple<char *, int>>();
    
    while( !this->is_loop ) {
        if( this->li_queue_data.empty() ) {
            usleep(1000);
            continue;
        }

        tp_data = this->li_queue_data.front();

        data_ptr    = get<0>(tp_data);
        data_length = get<1>(tp_data);
        
        if( (rc = send(this->socket_fd, data_ptr, data_length, MSG_DONTWAIT)) < 0 ) {
            this->print_debug_info("execute() worker send failed [%s] : [%d] [%02d] %s\n",
                    this->ip_addr.c_str(), rc, errno, strerror(errno));
            break;
        }

        this->li_queue_data.pop();
    }
    
    if( this->socket_fd != -1 ) {
        this->print_debug_info("execute() close socket [%d]\n", this->socket_fd);

        close(this->socket_fd);
        this->socket_fd = -1;
    }
    
    this->is_run = false;
    this->time_disconnect = this->get_timestamp();
    this->event_handle(this->index, false);

    this->is_loop = true;
    
    this->print_debug_info("execute() stop worker thread\n");
    
    return ;
}


//
// ## Unicast server class
//
SOCKET_UnicastServer::SOCKET_UnicastServer(bool _is_debug_print) {
    if( this->is_debug_print ) {
        this->set_debug_print();
    }
    this->print_debug_info("SOCKET_UnicastServer() create instance\n");

    this->is_init               = false;
    this->is_run                = false;
    this->is_loop               = false;

    this->port                  = DFLT_SERV_PORT;
    this->socket_fd             = -1;
    
    this->num_max_client        = DFLT_NUM_MAX_CLIENT;
    this->num_current_client    = 0;
    this->num_accrue_client     = 0;
    
    this->event_handle          = NULL;
    
    this->v_worker_func.clear();
    
    return ;
}

SOCKET_UnicastServer::~SOCKET_UnicastServer(void) {
    this->print_debug_info("SOCKET_UnicastServer() instance destructed\n");
    
    this->is_loop = true;
    if( this->thread_func.joinable() ) {
        this->thread_func.join();
    }

    this->print_debug_info("SOCKET_UnicastServer() mutex_worker_func lock()\n");
    this->mutex_worker_func.lock();
    
    int num_worker_thread = (int)this->v_worker_func.size();
    for( int idx = 0 ; idx < num_worker_thread ; idx++ ) {
        this->v_worker_func[idx].get()->stop();
    }
    
    this->print_debug_info("SOCKET_UnicastServer() mutex_worker_func unlock()\n");
    this->mutex_worker_func.unlock();
    
    return ;
}
        
void SOCKET_UnicastServer::print_debug_info(const char *_format, ...) {
    if( !this->is_debug_print ) return ;
    
    printf("\033[31m");
    fprintf(stdout, "SOCKET_UnicastServer::");
    va_list arg;
    va_start(arg, _format);
    vprintf(_format, arg);
    va_end(arg);
    printf("\033[0m");
    
    return ;
}

void SOCKET_UnicastServer::set_debug_print(void) {
    this->is_debug_print = true;
    
    this->print_debug_info("set_debug_print() is set on\n");
    
    return ;
}

bool SOCKET_UnicastServer::is_alive_status(void) {
    
    return this->is_run;
}

void SOCKET_UnicastServer::set_server_port(int _port) {
    if( this->port == _port ) return ;
    
    this->print_debug_info("set_server_port() change to [%d] -> [%d]\n", this->port, _port);
    this->port = _port;
    
    return ;
}

void SOCKET_UnicastServer::set_num_max_client(int _count) {
    if( this->num_max_client == _count ) return ;
        
    this->print_debug_info("set_num_max_client() change to [%d] -> [%d]\n", this->num_max_client, _count);
    this->num_max_client = _count;

    return ;
}

void SOCKET_UnicastServer::set_socket_option(int _socket_fd) {
    int optval = 1; // enable
    if( setsockopt(_socket_fd, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof (optval)) < 0 ) {
        this->print_debug_info("set_socket_option() setsockopt() SO_NODELAY failed : [%02d] %s\n", errno, strerror(errno));
    }

    struct linger opt_linger;
    opt_linger.l_onoff  = 1; // enable
    opt_linger.l_linger = 0;
    if( setsockopt(_socket_fd, SOL_SOCKET, SO_LINGER, (char *)&opt_linger, sizeof(opt_linger)) < 0 ) {
        this->print_debug_info("set_socket_option() setsockopt() SO_LINGER failed : [%02d] %s\n", errno, strerror(errno));
    }

    struct timeval t_rcv_timeo = {TIME_RCV_TIMEOUT, 0};
    if( setsockopt(_socket_fd, SOL_SOCKET, SO_RCVTIMEO, &t_rcv_timeo, sizeof(t_rcv_timeo) ) < 0 ) {
        this->print_debug_info("set_socket_option() setsockopt() SO_RCVTIMEO failed : [%02d] %s\n", errno, strerror(errno));
    }
    
    return ;
}

string SOCKET_UnicastServer::get_client_info(int _idx, string _type) {
    int idx = _idx;
    
    if( _type.compare("ip_addr") == 0 ) {
        string str_ip_addr = this->v_worker_func[idx].get()->get_ip_addr();
        
        return str_ip_addr;
    }
    
    return "";
}


int SOCKET_UnicastServer::get_max_client_count(void) {
    return this->num_max_client;
}

int SOCKET_UnicastServer::get_current_count(void) {
    this->mutex_counter.lock();
    
    int current_count = this->num_current_client;
    
    this->mutex_counter.unlock();
    
    return current_count;
}

void SOCKET_UnicastServer::inc_current_count(void) {
    this->mutex_counter.lock();
    
    this->num_current_client++;
    
    this->mutex_counter.unlock();
    
    return ;
}

void SOCKET_UnicastServer::dec_current_count(void) {
    this->mutex_counter.lock();
    
    this->num_current_client--;
    
    this->mutex_counter.unlock();
    
    return ;
}

int SOCKET_UnicastServer::get_accrue_count(void) {
    this->mutex_counter.lock();
        
    int accrue_client = this->num_accrue_client;
    
    this->mutex_counter.unlock();
    
    return accrue_client;
}


void SOCKET_UnicastServer::inc_accrue_count(void) {
    this->mutex_counter.lock();
    
    this->num_accrue_client++;
    
    this->mutex_counter.unlock();
    
    return ;
}

void SOCKET_UnicastServer::reset_current_count(void) {
    this->mutex_counter.lock();
    
    this->num_current_client = 0;
    
    this->mutex_counter.unlock();
    
    return ;
}

void SOCKET_UnicastServer::reset_accrue_count(void) {
    this->mutex_counter.lock();
    
    this->num_accrue_client = 0;
    
    this->mutex_counter.unlock();
    
    return ;
}

bool SOCKET_UnicastServer::init(void) {
    if( this->is_init ) {
        this->print_debug_info("init() already init\n");
        return false;
    }
    
    if( this->is_run ) {
        this->print_debug_info("init() already running\n");
        return false;
    }
    
    this->is_init = false;
    this->is_run  = false;
    this->is_loop = false;
    
    struct sockaddr_in    t_sock_addr;
    
    if( (this->socket_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0 ) {
        this->print_debug_info("init() socket() failed : [%02d] %s\n", errno, strerror(errno));

        return false;
    }

    // TCP uni-cast //
    t_sock_addr.sin_family      = AF_INET;
    t_sock_addr.sin_port        = htons(this->port);
    t_sock_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    int         option     = true;
    socklen_t   socket_len = sizeof(option);
    
    if( setsockopt(this->socket_fd, SOL_SOCKET, SO_REUSEADDR, &option, socket_len) < 0 ) {
        this->print_debug_info("init() setsockopt() SO_REUSEADDR failed : [%02d] %s\n", errno, strerror(errno));

        return false; 
    }
    
    // BIND init
    if( bind(this->socket_fd, (struct sockaddr *)&t_sock_addr, sizeof(t_sock_addr)) < 0 ) {
        this->print_debug_info("init() bind() failed : [%02d]\n", errno);

        return false;
    }

    int       optval;
    socklen_t optlen = sizeof(optval);
    getsockopt(this->socket_fd, SOL_SOCKET, SO_SNDBUF, (char*)&optval, &optlen);

    optval *= SIZE_SCALE_SNDBUF;
    setsockopt(this->socket_fd, SOL_SOCKET, SO_SNDBUF, (char*)&optval, sizeof(optval));

    optval = 1; // enable
    if( setsockopt(this->socket_fd, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof (optval)) < 0 ) {
        this->print_debug_info("set_socket_option() setsockopt() SO_NODELAY failed : [%02d] %s\n", errno, strerror(errno));
    }

    // LISTEN init
    if( listen(this->socket_fd, SIZE_BACK_LOG) < 0 ) {
        this->print_debug_info("init() listen() failed : [%02d] %s\n", errno, strerror(errno));

        return false;
    }
    
    this->print_debug_info("init() mutex_worker_func lock()\n");
    this->mutex_worker_func.lock();
    
    this->v_worker_func.clear();
    
    this->print_debug_info("init() mutex_worker_func unlock()\n");
    this->mutex_worker_func.unlock();
    
    this->print_debug_info("init() init unicast socket success \n");
    this->is_init = true;
    
    return true;
} 

void SOCKET_UnicastServer::stop(void) {
    if( !this->is_run ) {
        this->print_debug_info("stop() unicast is not running\n");
        
        return ;
    }
    this->is_loop = true;

    if( this->thread_func.joinable() ) {
        this->print_debug_info("stop() join & wait excute thread term\n");
        this->thread_func.join();
    }
    
    this->print_debug_info("stop() mutex_worker_func lock()\n");
    this->mutex_worker_func.lock();
    
    int num_worker_thread = (int)this->v_worker_func.size();
    for( int idx = 0 ; idx < num_worker_thread ; idx++ ) {
        this->print_debug_info("stop() join & wait worker thread term : [%s]\n", 
                this->v_worker_func[idx].get()->get_ip_addr().c_str());
        
        this->v_worker_func[idx].get()->stop();
    }
    
    this->print_debug_info("stop() mutex_worker_func unlock()\n");
    this->mutex_worker_func.unlock();
    
    if( this->socket_fd != -1 ) {
        close(this->socket_fd);
        this->socket_fd = -1;
        this->print_debug_info("stop() unicast socket closed \n");
    }
    
    this->reset_current_count();
    this->reset_accrue_count();
    
    this->is_init = false;
    
    return ;
}

void SOCKET_UnicastServer::run(void) {
    if( !this->is_init ) {
        this->print_debug_info("run() init is not ready\n");
        
        return ;
    }
    
    if( this->is_run ) {
        this->print_debug_info("run() already running\n");
        return ;
    }
    this->is_run = true;
    
    this->print_debug_info("run() create execute thread\n");
    this->thread_func = thread(&SOCKET_UnicastServer::execute, this);
    
    return ;
}

void SOCKET_UnicastServer::set_event_handler(void (*_func)(int, bool)) {
    this->print_debug_info("set_event_handler() set function\n");
    this->event_handle = _func;
    
    return ;
}

SOCKET_UnicastServer::func_ptr SOCKET_UnicastServer::get_event_handler(void) {
    
    return this->event_handle;
}

void SOCKET_UnicastServer::send_data_handler(char *_data, int _size) {
    if( _data == NULL || _size == 0 ) {
        this->print_debug_info("send_data_handler() data size is 0, %x\n", _data);
        return ;
    }

    int num_worker_thread = (int)this->v_worker_func.size();
    
    for( int idx = 0 ; idx < num_worker_thread ; idx++ ) {
        this->v_worker_func[idx].get()->set_queue_data(_data, _size);
    }
    
    return ;
}

void SOCKET_UnicastServer::execute(void) {
    this->print_debug_info("execute() run unicast thread\n");
    
    // server socket variables
    int     rc;
    struct  timeval    timeout;
    fd_set  fd_reads;

    // client socket variables
    int         client_socket_fd;
    struct      sockaddr_in t_sock_addr;
    socklen_t   sock_len = sizeof(struct sockaddr_in);
    
    bool        is_reconnect;
    string      str_ip_addr;
    thread      worker_func;
    int         worker_idx;
    
    while( !this->is_loop ) {
        FD_ZERO(&fd_reads);
        FD_SET(this->socket_fd, &fd_reads);
            
        timeout.tv_sec  = TIME_ACCEPT_SEC;
        timeout.tv_usec = TIME_ACCEPT_MSEC;

        if( (rc = select(this->socket_fd + 1, &fd_reads, NULL, NULL, &timeout)) < 0 ) {
            switch( errno ) {
                default :
                    this->print_debug_info("execute() select() failed : [%02d] %s\n", errno, strerror(errno));
                    break;
            }
            continue;

        } else if ( rc == 0 ) {
            // this->print_debug_info("execute() client connect - current[%d] accrue[%d]\n", this->get_current_count(), this->get_accrue_count());
            
            continue;
        }

        if( FD_ISSET(this->socket_fd, &fd_reads) ) {
            if( (client_socket_fd = accept(this->socket_fd, (struct sockaddr *)&t_sock_addr, &sock_len)) < 0 ) {
                this->print_debug_info("execute() accept() failed : [%02d] %s\n", errno, strerror(errno));
                continue;
            }
            str_ip_addr = string(inet_ntoa(t_sock_addr.sin_addr));
            
            if( this->get_current_count() + 1 > this->num_max_client ) {
                this->print_debug_info("execute() number of client exceeded : [%d/%d]\n", this->get_current_count(), this->num_max_client);
                
                close(client_socket_fd);
                this->print_debug_info("execute() client disconnected : [%s]\n", str_ip_addr.c_str());
                
                sleep(TIME_ACCEPT_SEC);
                
            } else {
                this->set_socket_option(client_socket_fd);
                
                this->print_debug_info("execute() find the client reconnects.\n");
                is_reconnect = false;
                
                this->print_debug_info("execute() mutex_worker_func lock()\n");
                this->mutex_worker_func.lock();
                
                int num_worker_thread = (int)this->v_worker_func.size();
                this->print_debug_info("execute() num_worker_thread : [%d]\n", num_worker_thread);
                for( int idx = 0 ; idx < num_worker_thread ; idx++ ) {
                    this->print_debug_info("execute() worker index : [%d] - [%s]\n", 
                        idx, this->v_worker_func[idx].get()->get_ip_addr().c_str());

                    if( this->v_worker_func[idx].get()->is_reconnect(str_ip_addr) ) {
                        this->v_worker_func[idx].get()->reconnect(client_socket_fd);

                        is_reconnect = true;
                        break;
                    }
                }
                this->print_debug_info("is_reconnect() check reconnect : [%s]\n", is_reconnect ? "true" : "false");
                
                if( is_reconnect ) {
                    // this->print_debug_info("execute() client connect - current[%d] accrue[%d]\n", this->get_current_count(), this->get_accrue_count());
                    this->print_debug_info("execute() mutex_worker_func unlock() - reconnect\n");
                    this->mutex_worker_func.unlock();
                    
                    continue;
                }
                
                this->print_debug_info("execute() not found connected client. add new client.\n");
                this->inc_accrue_count();
                
                unique_ptr<SOCKET_UnicastWorker> work_handler(new SOCKET_UnicastWorker());
                this->v_worker_func.push_back(move(work_handler));

                worker_idx = (int)this->v_worker_func.size() - 1;
                
                if( this->is_debug_print ) this->v_worker_func[worker_idx].get()->set_debug_print();
                
                this->v_worker_func[worker_idx].get()->init(str_ip_addr, client_socket_fd);
                this->v_worker_func[worker_idx].get()->set_index(worker_idx);
                this->v_worker_func[worker_idx].get()->set_event_handler(this->get_event_handler());
                this->v_worker_func[worker_idx].get()->run();
                
                this->print_debug_info("execute() worker index[%d] add complete.\n", worker_idx);
                
                this->print_debug_info("execute() mutex_worker_func unlock()\n");
                this->mutex_worker_func.unlock();
            }
        }
    }
    
    this->print_debug_info("execute() stop unicast thread\n");
    this->is_run = false;
    
    return ;
}

 

3. 소스코드 : include/main.h

#ifndef __MAIN_H__
#define __MAIN_H__

#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <mutex>
#include <vector>
#include <tuple>
#include <thread>
#include <stdexcept>


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <getopt.h>
#include <sys/time.h>
#include <libgen.h>

#include "../usr/include/lib_signal.h"
#include "socket_server.h"

#define TIME_CHECK_LOOP     1   // sec

using namespace std;

// ##
// global variables
// ##
bool    g_is_debug_print    = false;


// ##
// functions: common
// ##
void    print_debug_info(const char *_format, ...);


// ##
// functions: event handler
// ##
void    signal_event_handler(void);

// functions: network function
void    run_server_unicast(void);
void    stop_server_unicast(void);

// functions: network event handler
void    network_server_event_handler(int _index, bool _is_connect);


// ##
// global instance
// ##
SignalHandler           g_sig_handler;
SOCKET_UnicastServer    g_socket_unicast_server;

#endif

 

4. 소스코드 : src/main.cpp

#include "../include/main.h"


// ##
// functions: common
// ##
void print_debug_info(const char *_format, ...) {
    if( !g_is_debug_print ) return ;
    
    fprintf(stdout, "main::");
    va_list arg;
    va_start(arg, _format);
    vprintf(_format, arg);
    va_end(arg);
    
    return ;
}


// ##
// function: event handler
// ##
// signal handler
void signal_event_handler(int _sig_num) {
    print_debug_info("signal_event_handler() event : [%d] %s\n", _sig_num, strsignal(_sig_num));
    
    return ;
}

// functions: network event handler
void network_server_event_handler(int _index, bool _is_connect) {
    string str_ip_addr  = g_socket_unicast_server.get_client_info(_index, "ip_addr");    
    
    if( _is_connect ) {
        print_debug_info("network_server_event_handler() connect client : [%d] %s\n", _index, str_ip_addr.c_str());
        g_socket_unicast_server.inc_current_count();
        
    } else {
        print_debug_info("network_server_event_handler() disconnect audio client : [%d] %s\n", _index, str_ip_addr.c_str());
        g_socket_unicast_server.dec_current_count();
    }
    
    int num_current_count   = g_socket_unicast_server.get_current_count();
    int num_accure_count    = g_socket_unicast_server.get_accrue_count();
    int num_max_count       = g_socket_unicast_server.get_max_client_count();
    print_debug_info("network_server_event_handler() max[%d], accrue[%d], current[%d]\n", num_max_count, num_accure_count, num_current_count);
    
    return ;
}

// functions: network function
void run_server_unicast(void) {
    print_debug_info("run_server_unicast() run : [unicast] \n");
    g_socket_unicast_server.set_event_handler(&network_server_event_handler);
    
    g_socket_unicast_server.init();
    g_socket_unicast_server.run();
    
    return ;
}

void stop_server_unicast(void) {
    print_debug_info("stop_server_all() stop : [unicast] \n");
    
    g_socket_unicast_server.stop();
    
    return ;
}


// ##
// function: main
// ##
int main(int _argc, char *_argv[]) {
    int opt;
    while( (opt = getopt(_argc, _argv, "v")) != -1 ) {
        switch( opt ) {
            case 'v' :
                g_is_debug_print = true;
                print_debug_info("main() set print debug\n");
                
                g_sig_handler.set_debug_print();
                g_socket_unicast_server.set_debug_print();
                
                break;
                
            default :
                printf("usage: %s [option]\n", basename(_argv[0]));
                printf("  -v : print normal debug message \n");
                return -1;
                
                break;
        }
    }

    // signal handler
    g_sig_handler.set_signal(SIGINT);
    g_sig_handler.set_signal(SIGKILL);
    g_sig_handler.set_signal(SIGTERM);
    g_sig_handler.set_ignore(SIGPIPE);
    g_sig_handler.set_signal_handler(&signal_event_handler);
    
    run_server_unicast();

    // test data
    char arr_data[1024] = {0x00, };
    int  size_data = sizeof(arr_data);
    int     index = 0;
    
    while( !g_sig_handler.is_term() ) {
        sprintf(arr_data, "hello world : [%d]", index++);
        g_socket_unicast_server.send_data_handler(arr_data, size_data);

        sleep(TIME_CHECK_LOOP);
        
    }
    stop_server_unicast();
    
    print_debug_info("main() process has been terminated.\n");

    return 0;
}

* main.cpp 에서 1초마다 "helllo world : [N]" 을 전송하고 있다. 결과를 확인하기 위해선 socket client 가 필요하다.

 

5. 컴파일 및 실행

makefile과 signal handler lbirary는 github 에서 받아서 사용하면 된다.

그리고 바이너리 실행 시 library LD_PATH가 안잡혀있는 경우 현재경로(pwd)의 usr/lib를 참고하도록 export하면 된다.

muabow@muabow-WorkSpace:~/dev/socket_server$ make clean ; make
rm -f socket_server
g++  -o socket_server src/*.cpp -I./include -I./usr/include -L./usr/lib -lpthread -lsignal -O3 -Wall -std=c++11 -g
muabow@muabow-WorkSpace:~/dev/socket_server$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"$(pwd)/usr/lib"
muabow@muabow-WorkSpace:~/dev/socket_server$
muabow@muabow-WorkSpace:~/dev/socket_server$ ./socket_server -v

 

6. 테스트 결과

아래는 [프로세스 실행 - 클라이언트 접속 - 클라이언트 해제 - CTRL +C(시그널) - 프로세스 종료] 과정을 보여준다.

muabow@muabow-WorkSpace:~/dev/socket_server$ ./socket_server -v
main::main() set print debug
SignalHandler::set_debug_print() set debug print
SOCKET_UnicastServer::set_debug_print() is set on
SignalHandler::set_signal() bind signal event [Interrupt]
SignalHandler::set_signal() bind signal event [Killed]
SignalHandler::set_signal() bind signal event [Terminated]
SignalHandler::set_ignore() bind ignore event [Broken pipe]
SignalHandler::set_signal_handler() bind user term function
main::run_server_unicast() run : [unicast]
SOCKET_UnicastServer::set_event_handler() set function
SOCKET_UnicastServer::init() mutex_worker_func lock()
SOCKET_UnicastServer::init() mutex_worker_func unlock()
SOCKET_UnicastServer::init() init unicast socket success
SOCKET_UnicastServer::run() create execute thread
SOCKET_UnicastServer::execute() run unicast thread
SOCKET_UnicastServer::execute() find the client reconnects.
SOCKET_UnicastServer::execute() mutex_worker_func lock()
SOCKET_UnicastServer::execute() num_worker_thread : [0]
SOCKET_UnicastServer::is_reconnect() check reconnect : [false]
SOCKET_UnicastServer::execute() not found connected client. add new client.
SOCKET_UnicastWorker::set_debug_print() is set on
SOCKET_UnicastWorker::init() [/127.0.0.1] worker ready
SOCKET_UnicastWorker::set_index() worker instance index set [0]
SOCKET_UnicastWorker::set_event_handler() set function
main::network_server_event_handler() connect client : [0] 127.0.0.1
main::network_server_event_handler() max[20], accrue[1], current[1]
SOCKET_UnicastWorker::run() create worker thread
SOCKET_UnicastServer::execute() worker index[0] add complete.
SOCKET_UnicastServer::execute() mutex_worker_func unlock()
SOCKET_UnicastWorker::execute() run worker thread : [0/127.0.0.1]
^Cmain::signal_event_handler() event : [2] Interrupt
main::stop_server_all() stop : [unicast]
SOCKET_UnicastServer::stop() join & wait excute thread term
SOCKET_UnicastServer::execute() stop unicast thread
SOCKET_UnicastServer::stop() mutex_worker_func lock()
SOCKET_UnicastServer::stop() join & wait worker thread term : [127.0.0.1]
SOCKET_UnicastWorker::stop() join & wait worker thread term
SOCKET_UnicastWorker::execute() close socket [4]
main::network_server_event_handler() disconnect audio client : [0] 127.0.0.1
main::network_server_event_handler() max[20], accrue[1], current[0]
SOCKET_UnicastWorker::execute() stop worker thread
SOCKET_UnicastWorker::stop() worker thread delete
SOCKET_UnicastServer::stop() mutex_worker_func unlock()
SOCKET_UnicastServer::stop() unicast socket closed
main::main() process has been terminated.
SOCKET_UnicastServer::SOCKET_UnicastServer() instance destructed
SOCKET_UnicastServer::SOCKET_UnicastServer() mutex_worker_func lock()
SOCKET_UnicastServer::SOCKET_UnicastServer() mutex_worker_func unlock()
SOCKET_UnicastWorker::SOCKET_UnicastWorker() instance destructed

 

 7. 테스트 클라이언트 결과

테스트를 위한 소켓 클라이언트 코드도 공유한다. 아래를 참고하자.

// socket_client_test.c

#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

int main(int argc, char const *argv[]) {
    int  socket_fd;
    char read_data[1024];
    struct sockaddr_in t_serv_addr;

    if( (socket_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        printf("socket() failed : [%02d] %s\n", errno, strerror(errno));
        return -1;
    }

    t_serv_addr.sin_family = AF_INET;
    t_serv_addr.sin_port   = htons(7593);

    if( inet_pton(AF_INET, "127.0.0.1", &t_serv_addr.sin_addr) <= 0 ) {
        printf("inet_pton() failed : [%02d] %s\n", errno, strerror(errno));
        return -1;
    }

    if( connect(socket_fd, (struct sockaddr *)&t_serv_addr, sizeof(t_serv_addr)) < 0 ) {
        printf("connect() failed : [%02d] %s\n", errno, strerror(errno));
        return -1;
    }

    while( 1 ) {
        read(socket_fd, read_data, sizeof(read_data));
        printf("%s\n", read_data);
    }

    return 0;
}

컴파일 및 실행을 하자.

muabow@muabow-WorkSpace:~/dev$ gcc -o socket_client_test socket_client_test.c
muabow@muabow-WorkSpace:~/dev$ ./socket_client_test
hello world : [26]
hello world : [27]
hello world : [28]
hello world : [29]
hello world : [30]
hello world : [31]
hello world : [32]
hello world : [33]
hello world : [34]
hello world : [35]
hello world : [36]
hello world : [37]
hello world : [38]
hello world : [39]
^C

정상적으로 동작하는 것을 확인할 수 있다.

 

끝.

 

 

댓글