tcpdaemon
0.快速开始
老师交给小明一个开发任务,实现一个TCP网络迭代并发服务器,用于回射任何接收到的通讯数据。小明很懒,他在开源中国项目库里搜到了开源库tcpdaemon来帮助他快速完成任务。首先他安装好tcpdaemon,然后写了一个C程序文件test_callback_echo.c
$ vi test_callback_echo.c #include "tcpdaemon.h" _WINDLL_FUNC int tcpmain( struct TcpdaemonServerEnvironment *p_env , int sock , void *p_addr ) { char buffer[ 4096 ] ; int len ; len = recv( sock , buffer , sizeof(buffer) , 0 ) ; if( len == 0 ) return TCPMAIN_RETURN_CLOSE; else if( len < 0 ) return TCPMAIN_RETURN_ERROR; len = send( sock , buffer , len , 0 ) ; if( len < 0 ) return TCPMAIN_RETURN_ERROR; return TCPMAIN_RETURN_WAITINGFOR_NEXT; }
他编译链接成动态库test_callback_echo.so,最后用tcpdaemon直接挂接执行
$ tcpdaemon -m IF -l 0:9527 -s test_callback_echo.so -c 10 --tcp-nodelay --logfile $HOME/log/test_callback_echo.log
OK,总共花了五分钟,圆满完成老师作业。老师说这个太简单了,小明你给我改成像Apache经典的Leader-Follow服务端模型,小明说没问题,他把启动命令参数-m IF
改成了-m LF
,再次执行,完成老师要求,总共花了五秒钟。老师问你怎么这么快就改好了,小明说全靠开源项目tcpdaemon帮了大忙啊 ^_^
1.概述
tcpdaemon是一个TCP通讯服务端平台/库,它封装了众多常见服务端进程/线程管理和TCP连接管理模型(Forking、Leader-Follow、IO-Multiplex、WindowsThreads Leader-Follow),使用者只需加入TCP通讯数据收发和应用逻辑代码就能快速构建出完整的TCP应用服务器。
tcpdaemon提供了三种与使用者代码对接方式:(注意:.exe只是为了说明自己是可执行文件,在UNIX/Linux中可执行文件一般没有扩展名)
一般简单情况下,使用者采用回调模式即可,只要编写一个动态库user.so(内含回调函数tcpmain)被可执行程序tcpdaemon挂接上去运行。如果使用者想订制一些自定义处理,如初始化环境,可以采用主调模式,实现函数main里把自定义参数传递给tcpdaemon穿透给tcpmain。如果想实现运行时选择回调函数tcpmain则可以采用主调 回调模式。
2.编译安装
以Linux操作系统为例,下载到最新源码安装包tcpdaemon-x.y.z.tar.gz到某目录,解压之
$ tar xvzf tcpdaemon-x.y.z.tar.gz ... $ cd tcpdaemon $ cd src $ make -f makefile.Linux install rm -f LOGC.o rm -f tcpdaemon_lib.o rm -f tcpdaemon_main.o rm -f tcpdaemon rm -f libtcpdaemon.a gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -I. -I/home/calvin/include -c tcpdaemon_lib.c gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -I. -I/home/calvin/include -c LOGC.c ar rv libtcpdaemon.a tcpdaemon_lib.o LOGC.o ar: 正在创建 libtcpdaemon.a a - tcpdaemon_lib.o a - LOGC.o gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -I. -I/home/calvin/include -c tcpdaemon_main.c gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -o tcpdaemon tcpdaemon_main.o tcpdaemon_lib.o LOGC.o -L. -L/home/calvin/lib -lpthread -ldl cp -rf tcpdaemon /home/calvin/bin/ cp -rf libtcpdaemon.a /home/calvin/lib/ cp -rf tcpdaemon.h /home/calvin/include/tcpdaemon/
可以看到可执行程序tcpdaemon安装到
3.使用示例
3.1.服务模型Forking,链接模式1,接收HTTP请求报文然后发送HTTP响应报文
使用者只需编写一个函数tcpmain,实现同步的接收HTTP请求报文然后发送HTTP响应报文回去
$ vi test_callback_http_echo.c #include "tcpdaemon.h" _WINDLL_FUNC int tcpmain( struct TcpdaemonServerEnvironment *p_env , int sock , void *p_addr ) { char http_buffer[ 4096 1 ] ; long http_len ; long len ; /* 接收HTTP请求 */ memset( http_buffer , 0x00 , sizeof(http_buffer) ); http_len = 0 ; while( sizeof(http_buffer)-1 - http_len > 0 ) { len = RECV( sock , http_buffer http_len , sizeof(http_buffer)-1 - http_len , 0 ) ; if( len == 0 ) return TCPMAIN_RETURN_CLOSE; if( len == -1 ) return TCPMAIN_RETURN_ERROR; if( strstr( http_buffer , "\r\n\r\n" ) ) break; http_len = len ; } if( sizeof(http_buffer)-1 - http_len <= 0 ) { return TCPMAIN_RETURN_ERROR; } /* 发送HTTP响应 */ memset( http_buffer , 0x00 , sizeof(http_buffer) ); http_len = 0 ; http_len = sprintf( http_buffer , "HTTP/1.0 200 OK\r\nContent-length: 17\r\n\r\nHello Tcpdaemon\r\n" ) ; SEND( sock , http_buffer , http_len , 0 ); return TCPMAIN_RETURN_CLOSE; }
编译链接成test_callback_http_echo.so
$ gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -I. -I/home/calvin/include/tcpdaemon -c test_callback_http_echo.c $ gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -shared -o test_callback_http_echo.so test_callback_http_echo.o -L. -L/home/calvin/lib -lpthread -ldl
用tcpdaemon直接挂接即可
$ tcpdaemon -m IF -l 0:9527 -s test_callback_http_echo.so -c 10 --tcp-nodelay --logfile $HOME/log/test_callback_http_echo.log --loglevel-debug
可执行程序tcpdaemon所有命令行参数可以不带参数的执行而得到
$ tcpdaemon USAGE : tcpdaemon -m IF -l ip:port -s so_pathfilename [ -c max_process_count ] -m LF -l ip:port -s so_pathfilename -c process_count [ -n max_requests_per_process ] other options : -v [ --daemon-level ] [ --work-path work_path ] [ --work-user work_user ]
执行后可在$HOME/log下可以看到tcpdaemon的日志。
通过curl发测试请求
$ curl "http://localhost:9527/" Hello Tcpdaemon
测试成功!
所有代码在源码安装包的test目录里下可以找到
3.2.服务模型IO-Multiplex,链接模式2,非堵塞的接收HTTP请求报文然后发送HTTP响应报文
使用者编写一个函数main
$ test_main_IOMP.c #include "tcpdaemon.h" extern int tcpmain( struct TcpdaemonServerEnvironment *p_env , int sock , void *p_addr ); int main() { struct TcpdaemonEntryParameter ep ; memset( & ep , 0x00 , sizeof(struct TcpdaemonEntryParameter) ); snprintf( ep.log_pathfilename , sizeof(ep.log_pathfilename) , "%s/log/test_main_IOMP.log" , getenv("HOME") ); ep.log_level = LOGLEVEL_DEBUG ; strcpy( ep.server_model , "IOMP" ); ep.timeout_seconds = 60 ; strcpy( ep.ip , "0" ); ep.port = 9527 ; ep.tcp_nodelay = 1 ; ep.process_count = 1 ; ep.pfunc_tcpmain = & tcpmain ; ep.param_tcpmain = NULL ; return -tcpdaemon( & ep ); }
结构体TcpdaemonEntryParameter所有成员说明在tcpdaemon.h里可以找到
struct TcpdaemonEntryParameter { int daemon_level ; /* 是否转化为守护服务 1:转化 0:不转化(缺省) */ char log_pathfilename[ 256 1 ] ; /* 日志输出文件名,不设置则输出到标准输出上 */ int log_level ; /* 日志等级 */ char server_model[ 10 1 ] ; /* TCP连接管理模型 LF:领导者-追随者预派生进程池模型 for UNIX,Linux IF:即时派生进程模型 for UNIX,Linux WIN-TLF:领导者-追随者预派生线程池模型 for win32 IOMP:进程池 多路复用模型 for UNIX,Linux 回调函数内应分支事件处理 */ int process_count ; /* 当为领导者-追随者预派生进程池模型时为工作进程池进程数量,当为即时派生进程模型时为最大子进程数量,当为IO多路复用模型时为工作进程池进程数量 */ int max_requests_per_process ; /* 当为领导者-追随者预派生进程池模型时为单个工作进程最大处理应用次数 */ char ip[ 20 1 ] ; /* 本地侦听IP */ int port ; /* 本地侦听PORT */ char so_pathfilename[ 256 1 ] ; /* 用绝对路径或相对路径表达的应用动态库文件名 */ char work_user[ 64 1 ] ; /* 切换为其它用户运行。可选 */ char work_path[ 256 1 ] ; /* 切换到指定目录运行。可选 */ func_tcpmain *pfunc_tcpmain ; /* 当函数调用模式时,指向把TCP连接交给应用入口函数指针 */ void *param_tcpmain ; /* 当函数调用模式时,指向把TCP连接交给应用入口函数的参数指针。特别注意:自己保证线程安全 */ int tcp_nodelay ; /* 启用TCP_NODELAY选项 1:启用 0:不启用(缺省)。可选 */ int tcp_linger ; /* 启用TCP_LINGER选项 >=1:启用并设置成参数值 0:不启用(缺省)。可选 */ int timeout_seconds ; /* 超时时间,单位:秒;目前只对IO-Multiplex模型有效 */ /* 以下为内部使用 */ int install_winservice ; int uninstall_winservice ; } ;
使用者再编写一个函数tcpmain,实现非堵塞的接收HTTP请求报文然后发送HTTP响应报文回去
$ vi test_callback_http_echo_nonblock.c #include "tcpdaemon.h" struct AcceptedSession { int sock ; /* socket描述字 */ struct sockaddr addr ; /* socket地址 */ char http_buffer[ 4096 1 ] ; /* HTTP收发缓冲区 */ int read_len ; /* 读了多少字节 */ int write_len ; /* 将要写多少字节 */ int wrote_len ; /* 写了多少字节 */ } ; _WINDLL_FUNC int tcpmain( struct TcpdaemonServerEnvironment *p_env , int sock , void *p_addr ) { struct AcceptedSession *p_accepted_session = NULL ; int len ; switch( TDGetIoMultiplexEvent(p_env) ) { /* 接受新连接事件 */ case IOMP_ON_ACCEPTING_SOCKET : /* 申请内存以存放已连接会话 */ p_accepted_session = (struct AcceptedSession *)malloc( sizeof(struct AcceptedSession) ) ; if( p_accepted_session == NULL ) return TCPMAIN_RETURN_ERROR; memset( p_accepted_session , 0x00 , sizeof(struct AcceptedSession) ); p_accepted_session->sock = sock ; memcpy( & (p_accepted_session->addr) , p_addr , sizeof(struct sockaddr) ); /* 设置已连接会话数据结构 */ TDSetIoMultiplexDataPtr( p_env , p_accepted_session ); /* 等待读事件 */ return TCPMAIN_RETURN_WAITINGFOR_RECEIVING; /* 关闭连接事件 */ case IOMP_ON_CLOSING_SOCKET : /* 释放已连接会话 */ p_accepted_session = (struct AcceptedSession *) p_addr ; free( p_accepted_session ); /* 等待下一任意事件 */ return TCPMAIN_RETURN_WAITINGFOR_NEXT; /* 通讯接收事件 */ case IOMP_ON_RECEIVING_SOCKET : p_accepted_session = (struct AcceptedSession *) p_addr ; /* 非堵塞接收通讯数据 */ len = RECV( p_accepted_session->sock , p_accepted_session->http_buffer p_accepted_session->read_len , sizeof(p_accepted_session->http_buffer)-1-p_accepted_session->read_len , 0 ) ; if( len == 0 ) return TCPMAIN_RETURN_CLOSE; else if( len == -1 ) return TCPMAIN_RETURN_ERROR; /* 已接收数据长度累加 */ p_accepted_session->read_len = len ; /* 如果已收完 */ if( strstr( p_accepted_session->http_buffer , "\r\n\r\n" ) ) { /* 组织响应报文 */ p_accepted_session->write_len = sprintf( p_accepted_session->http_buffer , "HTTP/1.0 200 OK\r\n" "Content-length: 17\r\n" "\r\n" "Hello Tcpdaemon\r\n" ) ; return TCPMAIN_RETURN_WAITINGFOR_SENDING; } /* 如果缓冲区收满了还没收完 */ if( p_accepted_session->read_len == sizeof(p_accepted_session->http_buffer)-1 ) return TCPMAIN_RETURN_ERROR; /* 等待下一任意事件 */ return TCPMAIN_RETURN_WAITINGFOR_NEXT; /* 通讯发送事件 */ case IOMP_ON_SENDING_SOCKET : p_accepted_session = (struct AcceptedSession *) p_addr ; /* 非堵塞发送通讯数据 */ len = SEND( p_accepted_session->sock , p_accepted_session->http_buffer p_accepted_session->wrote_len , p_accepted_session->write_len-p_accepted_session->wrote_len , 0 ) ; if( len == -1 ) return TCPMAIN_RETURN_ERROR; /* 已发送数据长度累加 */ p_accepted_session->wrote_len = len ; /* 如果已发完 */ if( p_accepted_session->wrote_len == p_accepted_session->write_len ) return TCPMAIN_RETURN_CLOSE; /* 等待下一任意事件 */ return TCPMAIN_RETURN_WAITINGFOR_NEXT; default : return TCPMAIN_RETURN_ERROR; } }
编译成test_main_IOMP
gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -I. -I/home/calvin/include/tcpdaemon -c test_main_IOMP.c gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -I. -I/home/calvin/include/tcpdaemon -c test_callback_http_echo_nonblock.c gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -o test_main_IOMP test_main_IOMP.o test_callback_http_echo_nonblock.o -L. -L/home/calvin/lib -lpthread -ldl -ltcpdaemon
执行test_main_IOMP
$ ./test_main_IOMP
执行后可在$HOME/log下可以看到test_main_IOMP的日志。
通过curl发测试请求
$ curl "http://localhost:9527/" Hello Tcpdaemon
测试成功!
所有代码在源码安装包的test目录里下可以找到
4.参考
4.1.回调函数tcpmain
tcpdaemon会在合适时机回调用户层指定函数tcpmain。
当服务模型为Forking或Leader-Follow或WindowsThreads Leader-Follow时,回调函数tcpmain在连接被接受后调用。返回TCPMAIN_RETURN_CLOSE或TCPMAIN_RETURN_ERROR都将关闭通讯连接。
当服务模型为IO-Multiplex时,回调函数tcpmain在通讯连接被接受时、通讯连接被关闭时、通讯数据可接收/发送时调用,因为是非堵塞处理,返回TCPMAIN_RETURN_CLOSE或TCPMAIN_RETURN_ERROR会关闭通讯连接,返回TCPMAIN_RETURN_WAITINGFOR_RECEIVING会切换等待可读事件,返回TCPMAIN_RETURN_WAITINGFOR_SENDING会切换等待可写事件,返回TCPMAIN_RETURN_WAITINGFOR_NEXT会继续等待其它事件。(注意:通讯连接被接受时的tcpmain里必须调用TDSetIoMultiplexDataPtr设置通讯数据会话结构)
4.2.TcpdaemonServerEnvironment环境函数集
5.总结
tcpdaemon提供了多种服务模型和链接模式,旨在协助使用者快速构建TCP应用服务器,比如可以使用本人的另一个开源项目 HTTP解析器fasterhttp 以百行以内代码构建出一个完整的Web服务器,还有一个完整的应用案例可参阅本人的另一个开源项目 分布式发号器 ,经过tcpdaemon改造后应用代码缩短了一半。
tcpdaemon源码托管在 开源中国码云,你也可以通过 邮箱 联系到作者
能帮助到您是我的荣幸 ^_^