注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

being23

写给未来的自己

 
 
 

日志

 
 
关于我

真正的坚定,就是找到力量去做自己喜欢的事情,并为之努力,这样才会觉得生活是幸福的。

网易考拉推荐

libevhtp使用小记  

2014-01-19 17:23:45|  分类: work@oppo |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

半个月前,上线一个收集服务,启动不了,发现是端口被占用。进一步检查后,发现大量的连接处于 TIME_WAIT 状态,大致如下

 Total: 13420 (kernel 14354)
 TCP:   90360 (estab 13061, closed 69056, orphaned 8192, synrecv 0, timewait 69051/0), ports 40510

 Transport Total     IP        IPv6
 *         14354     -         -
 RAW       0         0         0
 UDP       6         1         5
 TCP       21304     20053     1251
 INET      21310     20054     1256
 FRAG      0         0         0

作为对比,这是改造之后的情况:

Total: 15182 (kernel 16147)
TCP:   30464 (estab 14750, closed 6295, orphaned 9374, synrecv 0, timewait 6290/0), ports 1534

Transport Total     IP        IPv6
*         16147     -         -        
RAW       0         0         0        
UDP       6         1         5        
TCP       24169     22751     1418     
INET      24175     22752     1423     
FRAG      0         0         0 
当时共启动了4个收集服务进程,通过前端的nginx做均衡,并且收集进程和nginx运行在同一台物理机上。那些处于 TIME_WAIT 状态的连接都是收集服务和nginx之间的连接。根据TCP协议,这说明是server主动断开了链接。

收集服务使用是spserver框架,对于该框架这里有一个结构层面的介绍从spserver看HS-HA模式

翻了源码,在文件speventcb.cpp找到回调函数void SP_EventCallback :: onWrite( int fd, short events, void * arg ),发现其在处理完请求之后,调用了函数doClose(line259)。

254         if( 0 == ret && session->getOutList()->getCount() <= 0 ) {
255             if( SP_Session::eExit == session->getStatus() ) {
256                 ret = -1;
257                 if( 0 == session->getRunning() ) {
258                     sp_syslog( LOG_DEBUG, "session(%d.%d) normal exit", sid.mKey, sid.mSeq );
259                     SP_EventHelper::doClose( session );
260                 } else {
261                     sp_syslog( LOG_NOTICE, "session(%d.%d) busy, terminate session later",
262                             sid.mKey, sid.mSeq );
263                     // If this session is running, then onResponse will add write event for this session.
264                     // It will be processed as write fail at the last. So no need to re-add event here.
265                 }
266             }
267         }

在开发机上搭建了一致的环境,tcpdump抓包如下,从中可以清楚的看到链接建立,数据传输和链接断开。

15:13:15.907872 IP 127.0.0.1.37139 > 127.0.0.1.8806: S 1356459970:1356459970(0) win 32792 <mss 16396,sackOK,timestamp 699857189 0,nop,wscale 7>
15:13:25.907863 IP 127.0.0.1.8806 > 127.0.0.1.37139: S 1362613632:1362613632(0) ack 1356459971 win 32768 <mss 16396,sackOK,timestamp 699857189 699857189,nop,wscale 7>
15:13:25.907885 IP 127.0.0.1.37139 > 127.0.0.1.8806: . ack 1 win 257 <nop,nop,timestamp 699857189 699857189>
15:13:15.907874 IP 127.0.0.1.37139 > 127.0.0.1.8806: P 1:670(669) ack 1 win 257 <nop,nop,timestamp 699857189 699857189>
15:13:15.907879 IP 127.0.0.1.8806 > 127.0.0.1.37139: . ack 670 win 267 <nop,nop,timestamp 699857189 699857189>
15:13:15.908683 IP 127.0.0.1.8806 > 127.0.0.1.37139: P 1:171(170) ack 670 win 267 <nop,nop,timestamp 699857189 699857189>
15:13:15.908689 IP 127.0.0.1.37139 > 127.0.0.1.8806: . ack 171 win 265 <nop,nop,timestamp 699857189 699857189>
15:13:15.908828 IP 127.0.0.1.8806 > 127.0.0.1.37139: F 171:171(0) ack 670 win 267 <nop,nop,timestamp 699857190 699857189>
15:13:15.908849 IP 127.0.0.1.37139 > 127.0.0.1.8806: F 670:670(0) ack 172 win 265 <nop,nop,timestamp 699857190 699857190>
15:13:15.908856 IP 127.0.0.1.8806 > 127.0.0.1.37139: . ack 671 win 267 <nop,nop,timestamp 699857190 699857190>

问题是定位到了,奈何功力不够,折腾了几天没能搞定,囧。期间有找过牛人帮忙,结果还是一样。倒不是牛人不够牛,而是太忙了,没时间顾及加上框架自身的复杂性(看了上面那篇博文就知道了),最终影响了问题的解决。

既然不能直接解决,那绕过去吧~我想牛人是这么想的,因为过了两天,牛人发给我一个链接,轻量级框架libevhtp。确实够轻量级,源文件数比spserver少了不少,周末在家翻看了部分源代码,主要是熟悉下里面的函数,以便完成后续的替换。

周一过来写好测试程序,链接出错,呃~

$ g++ -o mytest mytest.cpp  -I/usr/local/include -L/usr/local/lib -levent -levhtp -levent_openssl
/tmp/ccFfrEfs.o: In function `testcb(evhtp_request_s*, void*)':
mytest.cpp:(.text+0x153): undefined reference to `htparser_get_content_length(htparser*)'
mytest.cpp:(.text+0x1b5): undefined reference to `htparser_get_content_length(htparser*)'
mytest.cpp:(.text+0x1ea): undefined reference to `htparser_get_content_length(htparser*)'
collect2: ld returned 1 exit status

怎么回事,undefined reference,这个函数在源文件htparse.h明明就有定义啊?!

106 unsigned int   htparser_get_status(htparser *);
107 uint64_t       htparser_get_content_length(htparser *);
108 uint64_t       htparser_get_content_pending(htparser *);

有问题,搞不定,继续找牛人。牛人过来看了看,说是不是链接的静态库顺序不对,调整几种组合后依旧不行。通过命令ar查看静态库中目标文件,源文件htparse.c也在其中,不懂。

$ ar -t /usr/local/lib/libevhtp.a 
evhtp.c.o
htparse.c.o
evthr.c.o

后来,牛人离开了一会儿,又跑过来说文件htparse.c是c程序,在c++中调用时,需要在头文件中添加额外声明。事后我在网络找到一个类似问题的讨论c++ undefined references with static library。知道了问题所在,修复如下:

@@ -2,6 +2,10 @@
 #define __HTPARSE_H__

 struct htparser;
+
+#ifdef __cplusplus
+extern "C" {
+#endif

 enum htp_type {
     htp_type_request = 0,
@@ -110,5 +114,9 @@
 void           htparser_init(htparser *, htp_type);
 htparser     * htparser_new(void);

+#ifdef __cplusplus
+}
 #endif

+#endif

重新链接通过。用wget命令发送测试数据:

$ wget http://127.0.0.1:8081/statistics/ --header="fields:field1/field2/field3" --post-data="this is a msg for testing" -O "index.html"

server输出如下:

authority is null
request method is 2
uri is /statistics/
content length is 25
content is this is a msg for testing
fields are field1/field2/field3
header details:
User-Agent:Wget/1.12 (linux-gnu)
Accept:*/*
Host:127.0.0.1:8081
Connection:Keep-Alive
Content-Type:application/x-www-form-urlencoded
Content-Length:25
fields:field1/field2/field3

最后说下几个中间碰到的几个问题吧

  • htparse.h中定义的函数void * htparser_get_userdata(htparser *),被误用来获取客户端发送的数据。其实客户端发送的数据是在结构体evhtp_request_sbuffer_in域中,具体见源文件evhtp.c。在业务代码中,调用evbuffer_copyout函数就能获得客户端发送的数据了。
  • 在上面的测试程序中,返回给客户端的消息,是通过下面的代码片段发送的。
    evbuffer_add_reference(request->buffer_out, "foobar", 6, NULL, NULL);
    evhtp_send_reply(request, EVHTP_RES_OK);
    

    在业务代码中,先是创建了一个char数组用作缓冲区,将要返回的消息格式化到缓冲区中,然后调用类似上面片段中的代码。在测试过程中,发现返回的消息字节数是正确的,但是内容是不完整的。好吧,我又去问了牛人。牛人看了看这函数名,说道这是一个引用,想必在写事件触发的时候,缓冲区已经释放了,导致返回消息出错,改成调用函数evbuffer_add,问题解决。


测试程序:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>
#include <iostream>

#include <evhtp.h>

using namespace std;

int dump_header(evhtp_kv_t * kv, void * arg)
{
    char * buf = (char *)arg;
    int len = strlen(buf);
    len += snprintf(buf + len, 1024-len, "%s:%s\n", kv->key, kv->val);
    buf[len] = '\0';
    return 0;
}

void testcb(evhtp_request_t * request, void * a) {
    if (NULL == request->uri->authority) 
        cout << "authority is null" << endl;

    cout << "request method is " << evhtp_request_get_method(request) << endl;
    cout << "uri is " << request->uri->path->full << endl;
    cout << "content length is " << evhtp_request_content_len(request) << endl;
    char buf[1024] = {0};
    evbuffer_copyout(request->buffer_in, buf, evhtp_request_content_len(request));
    buf[evhtp_request_content_len(request)] = '';
    cout << "content is " << buf << endl;

    cout << "fields are " << evhtp_kv_find(request->headers_in, "fields") << endl; 
    char mybuf[1024];
    mybuf[0] = '';
    evhtp_kvs_for_each(request->headers_in, dump_header, (void *)mybuf);
    cout << "header details:\n" << mybuf << endl;

    evbuffer_add_reference(request->buffer_out, "foobar", 6, NULL, NULL);
    evhtp_send_reply(request, EVHTP_RES_OK);

}

int main(int argc, const char* argv[]) {
    evbase_t *evbase = event_base_new();
    evhtp_t  *htp    = evhtp_new(evbase, NULL);

    evhtp_set_cb(htp, "/statistics/", testcb, NULL);
    evhtp_set_glob_cb(htp, "/statistics/*", testcb, NULL);
    evhtp_use_threads(htp, NULL, 4, NULL);

    evhtp_bind_socket(htp, "0.0.0.0", 8081, 1024);

    event_base_loop(evbase, 0);
    return 0;
}

2014-01-19@桃苑公寓

  评论这张
 
阅读(4687)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017