리눅스 C언어 소켓 서버
socket server example 소스코드를 공유한다.
리눅스 C++ 로 작성되었고 이전에 포스팅했던 signal handler library 를 활용하였다.
https://muabow.tistory.com/200
자세한 개발환경, 디렉토리 구성, 빌드 방법 등은 github의 README.md 참고하자.
https://github.com/muabow/home/tree/main/src/cpp/socket_server
목적
소스코드를 클래스로 구성한 이유는 멀티 클라이언트를 처리할 수 있고 부가적인 기능을 하는 서비스를 제공하기 위함이다. 몇가지 특징을 설명한다.
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
정상적으로 동작하는 것을 확인할 수 있다.
끝.
'IT > programming' 카테고리의 다른 글
[C/C++] C언어 Big-endian <-> Little-endian 상호 변환 (0) | 2022.01.13 |
---|---|
[C/C++] sprintf indicator 및 format 관련 (0) | 2022.01.13 |
[C/C++] 오름차순 정렬, 내림차순 정렬 C언어, qsort 활용 (0) | 2022.01.10 |
[C/C++] readdir을 활용한 파일 목록 읽기 예제, 파일 탐색기 (0) | 2022.01.07 |
[C/C++] parse mp3 header/C언어 MP3 헤더 분석 함수 공유 (0) | 2022.01.07 |
댓글