State Threads是一个广受关注的高性能网络线程库,winlin在SRS中做了比较充分的应用,我很遗憾直到现在才精心研究它。下面是我的研究实录,以作备忘。
一、源码编译
下面是在Ubuntu 14.04 64bit上面的实操记录:
从官网http://sourceforge.net/projects/state-threads/下载源码包,最新版是1.9,如果不能下载,就从github上下载fork版本
https://github.com/toffaletti/state-threads
下载完st-1.9.tar.gz,然后解压
tar zxvf st-1.9.tar.gz
cd st-1.9
make linux-debug
下面我们在example中创建一个huge_threads.c并编译。这个例子来自winlin文章中提供的.
编译
- //gcc-I../obj-ghuge_threads.c../obj/libst.a-ohuge_threads
- //./huge_threads10000
- //./huge_threads30000
- //
- #include<stdio.h>
- #include"st.h"
- #defineSLEEP_INTERVAL30//inms
- void*do_calc(void*arg){
- longpidx=*(long*)arg;
- for(;;){
- printf("sthread[#%ld]usleep\n",pidx);
- st_usleep(SLEEP_INTERVAL*1000);
- }
- returnNULL;
- }
- intmain(intargc,char**argv){
- if(argc<=1){
- printf("Testtheconcurrenceofstate-threads!\n"
- "Usage:%s<sthread_count>\n"
- "eg.%s10000\n",argv[0],argv[0]);
- return-1;
- }
- if(st_init()<0){
- printf("statethreadslibruntimeiniterror!");
- return-1;
- }
- inti,count=atoi(argv[1]);
- for(i=1;i<=count;i++){
- if(st_thread_create(do_calc,(void*)&i,0)==NULL){
- printf("createstatethread%dFailed\n",i);
- return-1;
- }
- }
- st_thread_exit(NULL);
- return0;
- }
gcc-I../obj-ghuge_threads.c../obj/libst.a-ohuge_threads
运行
./huge_threads 10000
运行过程中,利用top查看程序性能,按ctrl+C中断
二、doc目录研究
在st-1.9源码中doc目录有几个文档,可以参考
st.html ST库概论,winlin翻译的那篇文章
timeout_heap.txt 超时heap实现
notes.html 给出了编程注意点,包括移植,信号,进程内同步,进程间同步,非网络IO,超时处理,特别谈到进程内同步非常简单,不需要同步资源;非网络IO中谈到drawback和设计时需要避免的方法
reference.html 一个API接口文档介绍,需要认真阅读和熟悉,但是需要编码实战来加深理解
st_set_eventsys() 设置事件通知机制event notification mechanism,在st_init()之前调用,建议优先选用ST_EVENTSYS_ALT,它会使用epoll,然后才是ST_EVENT_DEFAULT
st
_set_switch_in_cb(st_switch_cb_t cb) 设置thread被resume时的回调函数,默认NULL
st_set_switch_out_cb(st_switch_cb_t cb) 设置thread被stop时的回调函数,默认NULL
st_randomize_stacks() 打开或关闭stack base address randomization,打开会提高性能,避免所有线程的stack是page aligned,而是随机生成的
st_key_create() 为进程内的所有线程创建一个非负整数的key,以便去set和get thread-specific data,所有线程的key是一样的但是存放的私有数据不同
st_thread_setspecific() 设置每个线程自己的私有数据,不同的线程可以对这个相同的key bindig不同的值
set_time() 返回1970**以来的秒数
set_netfd_free() free file descriiptor但是不closing所在的os file descriptor
st_readv() 从指定文件描述符读数据到multiple buffers中
最重要的是末尾的Program Structure,给出了
在一个网络应用程序中使用ST库的基本步骤:
2.调用st_init()来初始化ST库
5.创建多进程,fork(),父进程退出或是watchdog
6.在子进程中创建thread pool来处理user connection,线程池中的每个线程可以accept client connection,connect到其他服务器,执行各种network I/O等等
每个process的每个socket的空闲线程spare thread的最大个数默认为8, server一启动就创建线程池,它是最大空闲线程个数,线程池可以增长到最大线程个数,也可以限制总线程个数,而非针对每个listening socket
三、example目录
下面研究example目录下面的3个例子
首先阅读里面的README,它简单介绍了这三个例子的基本情况和用法
server 包含server.c和error.c
lookupdns 包含lookupdns.c和res.c
proxy 包含proxy.c
1.server.c程序分析:
General server example,accept a client connection and just outputs a short html page
先定义进程数组,个数是
vp_count,是当前cpu的core数,这样能充分利用cpu多核能力,再定义listening socket数组
srv_socket[],个数是
sk_count,再定义其上每个srv_socket的wait for thread和busing thread个数。每个进程一个线程池在运转,都在独立地运行
在每个process中启动线程数时,先创建access log flushing thread,它的职责是定期(配置为30s)写日志,然后是建立connection handlingthreads,它的职责是首先保存连接双方的ip和port,不管对方发的啥,闷头就向对方发送一个"It worked!"的html页面,
然后关闭该tcp连接,参见线程函数handle_connections,它调用handle_session()
在创建socket并绑定侦听时,ST的socket fd是使用st_netfd_open_socket(sock)得到的,就是在该sock上面得到的。
在C/S交互过程中,使用到线程私有数据方法来保存listening socket index和client的sockaddr
整个程序的架构值得我们借鉴。
程序使用方法
./server -l ./ 指定访问日志目录,必须指定,下面都是可选参数
-b 127.0.0.1:8000 binding and listening sockets对数,否则使用0.0.0.0:8000代替
-t 2:10 每个listening socket上的thread limits
-u taoyx 指定运行该程序的user名,据此找出其group名和user名
-q 1000 待处理连接队列的长度
-a 启用访问日志记录
-i 以互动模式运行吗?否则以daemon守护进程运行。互动模式不记录日志信息,建议命令行调试时使用
-S Serialize all accept() calls,依据平台来确定
-h print usage info
为了简单,我直接使用默认命令打开
./server -l ./
下面使用curl作为客户端来测试
curl -vx 127.0.0.1:8000 -o a.html "http://news.sohu.com"
服务器返回的固定page内容如下
通过发送信号来研究进程的行为
使用kill -l来列出所有的信号编码表,我们这里只需要使用到SIGHUP(1),SIGUSR1(10),SIGTERM(15)
kill -10 4044
dump info打印当前各进程的侦听端口,线程池线程情况,请求信息
2.res.c分析
_res从哪儿来的?
dns_getaddr() ==>query_domain()==> res_mkquery() ==> dn_skipname(),dn_expand(),
||==>parse_answer()
lookupdns.c分析
异步host name解析,一个host name对应一个ST线程,所有的线程都是并发的,查询成功后打印出host name和对应的ip地址
使用方法
./lookupdnswww.baidu.comnews.sohu.comnew.sina.com.cn www.ifeng.comv.youku.com
源码分析
main() ==> do_resolve() ==>dns_getaddr() ==> 引用res.c中的函数
3.proxy.c分析
使用方法
-l 127.0.0.1:8086 本地侦听的ip和port
-r 1.2.3.4: 80 远程连接的ip和port,如果不是点分十进制
-p 8 并行进程个数,如果不指定,默认使用cpu的内核数
-S serialize accept
-t mask 测试或调试模式,掩码为1,4,8,16,这里选择1,打印所有信息
-X 只用一个进程,不使用守护进程方式
使用下面的命令启动
./proxy -l 127.0.0.1:8086 -r news.sohu.com:80 -t 1
我们看到会分别起4个进程独立工作,这样停止这些线程比较麻烦,只能手动kill掉各个进程
kil -9 13458
kil -9 13459
kil -9 13460
kil -9 13461
现在使用curl做客户端测试
curl -vx 127.0.0.1:8086 -o sohu.html "http://news.sohu.com"
得到的网页可以在chrome中正常打开
深入调研工作
1.dns查找是如何实现的?
参考链接
[1].http://www.jb51.cc/article/p-fqkxzqpu-so.html winlin的译文,不错,原文在源码doc目录中
[2].http://www.obroot.com/state-threads-for-internet-applications/?utm_source=tuicool 另一篇中文翻译,不错
[3].https://github.com/winlinvip/state-threads winlin移植精简版本
[4].http://sourceforge.net/projects/state-threads/ state-threads官网源码
[5].http://coolshell.cn/articles/12012.html 不可多得的心得荟萃
http://www.th7.cn/system/lin/201505/104951.shtml
原文链接:https://www.f2er.com/ubuntu/356434.html