查看: 3325|回复: 0

[原创] 【连载二】树莓派3+ATS——服务器插件

[复制链接]
  • TA的每日心情
    开心
    2017-12-20 11:14
  • 签到天数: 5 天

    连续签到: 1 天

    [LV.2]偶尔看看I

    发表于 2017-12-24 23:01:51 | 显示全部楼层 |阅读模式
    分享到:
    本帖最后由 ky123 于 2018-1-15 11:30 编辑

    树莓派3上开发Apache Traffic Server(ATS)

    树莓派3+ATS——服务器插件

    目录
    一、原理分析
    二、主要函数
    三、代码实现
    四、测试过程

    -----------------------------------------------------------------------------------
    感谢E络盟提供的助赛基金。

    一、原理分析
    ATS是做服务器最好的选择,服务器最重要的两件事就是接收处理用户的请求并发送反馈的信息给用户。这个帖子的目的在于介绍几个接收、发送数据的处理函数的使用。
    先来看下,整体的结构
    装逼的功能:我希望在ATS的基础上,开发一个插件,这个插件能够接收用户的连接请求,同时能发送数据给用户。
    实际的功能:这不就是个Socket的发送、接收功能吗。。。
    socket归socket,不同的是ATS如何利用Socket+Event+Continuations这三种机制实现这一最基本的功能。

    2017-12-24_000532.jpg

    二、主要函数
    ATS中插件是运行在continuations(协程)的机制上的,可以说一个插件就是一个或多个continuations组成的,所以先介绍的两个函数是continuations的创建与销毁
    TSCont TSContCreate(TSEventFunc funcp, TSMutex mutexp);
    ###funcp是指continuations用来处理事件的主要函数,创建函数是用来创建并绑定事件处理函数
    void TSContDestroy(TSCont contp);
    第二,之前介绍到,数据会随着continuations的挂起而保存,有点像中断,接下来的这个函数能将要保存的数据与协程绑定起来

    void TSContDataSet(TSCont contp, void *data);
    ###void *data即要保存的数据,可以是类、结构体等等。
    void *TSContDataGet(TSCont contp);
    ###获取dataset所保存的数据
    TSAction TSNetAccept(TSCont contp, int port, int domain, int accept_threads);
    ###绑定并监听端口,第3个参数domain的值可参考socket的值,一般是设置AF_INET,最后一个是回调函数的线程ID,可设置回调可不设置

    int TSContCall(TSCont contp, TSEvent event, void *edata);
    ###回调与cont绑定的eventhandler函数,第二个参数为事件的代号

    第三,接收有关数据的读、写操作的函数
    2017-12-24_214808.jpg
    当请求接入后,TSNetAccept的data是一个TSVconn的类,也就是通过这个类在两者之间建立联系
    数据的读取(
    写入)需要通过IObuffer、IOBufferReader、TSIOBufferBlock相互配合才能将数据从buffer中读取(写入)出来。
    主要的几个函数(以读取函数为例)
    TSVIO TSVConnRead(TSVConn connp, TSCont contp, TSIOBuffer bufp, int64_t nbytes);

    ###将连接发送给协程数据关联至IOBuffer中,同时nbytes设置IObuffer的空间大小,当有数据写入时,触发该协程的回调事件函数。
    const char *TSIOBufferBlockReadStart(TSIOBufferBlock blockp, TSIOBufferReader readerp, int64_t *avail);
    ###将数据读出,avail表示用于存放读书数据字节数的地址
    TSIOBufferBlock TSIOBufferBlockNext(TSIOBufferBlock blockp);
    ###每次读取的数据有限,通过block的方式多次读取,这里的block相当于指针一样,随着读取不断向后移动
    三、代码实现
    最后,是通过C++编写一个小插件。
    首先,我们需要将这个插件挂载到ATS上,因此需要声明TSPluginInit函数,由于是在CPP的文件里写的,因此需要声明用gcc来编译这个入口函数就有如下代码,同时我们再此将插件功能进行初始化
    1. extern "C" void TSPluginInit (int argc, const char *argv[]);
    2. void TSPluginInit (int argc, const char *argv[])
    3. {
    4.         TSPluginRegistrationInfo info;
    5.         info.plugin_name = "hello-world";
    6.         info.vendor_name = "MyCompany";
    7.         info.support_email = "ts-api-support@MyCompany.com";
    8.         printf("开始运行插件\n");
    9.         if (TSPluginRegister(&info) != TS_SUCCESS) {
    10.                 TSError("注册失败2");
    11.         }
    12.         helloinit();
    13.         
    14. }
    复制代码
    在初始化函数中,需要做的有四件事:创建continuations、绑定数据存放、绑定事件处理的主函数、绑定端口并监听。在这之前,先创建一用于保存数据的机构体(或者类)
    1. typedef struct {
    2.   TSIOBuffer bufp;
    3.   TSIOBuffer out_bufp;
    4.   TSIOBufferReader readerp;
    5.   TSIOBufferReader out_readerp;

    6.   TSVConn write_vconnp;
    7.   TSVConn read_vconnp;
    8.   TSVIO read_vio;
    9.   TSVIO write_vio;
    10. } CacheVConnStruct;
    复制代码
    于是,helloinit、以及主事件处理函数的代码为
    1. int helloinit()
    2. {
    3.         printf("绑定端口\n");
    4.         hello_appcep_cont_=TSContCreate(HelloAccpetHandler, TSMutexCreate());
    5.         printf("cont是:%d\n",hello_appcep_cont_);
    6.         CacheVConnStruct *cache_vconn = (CacheVConnStruct *)TSmalloc(sizeof(CacheVConnStruct));
    7.         
    8.         TSContDataSet( hello_appcep_cont_, cache_vconn);

    9.         TSNetAccept(hello_appcep_cont_, 8899,AF_INET ,1);
    10.         
    11. }
    12. int HelloAccpetHandler(TSCont cont,TSEvent event,void *data)
    13. {
    14.         printf("cont是:%d\n",cont);
    15.         printf("回调连接的事件为:%d\n",event);
    16.         switch ((int)event)
    17.                 {
    18.                 case TS_EVENT_NET_ACCEPT:
    19.                         printf("连接正常接入\n");
    20.                         ReadFromVConn(cont,data);
    21.                         break;
    22.                 case TS_EVENT_VCONN_READ_COMPLETE:
    23.                         printf("读取完毕\n");
    24.                         //HandlerReadComplete(cont,data);
    25.                         break;
    26.                 case TS_EVENT_VCONN_READ_READY:
    27.                         printf("buffer尚未读满\n");
    28.                         HandlerReadComplete(cont,data);
    29.                 }
    30.         return 1;
    31. }
    复制代码
    接下来,需要等待连接接入,为此我们需要写个客户端的脚本,方便采用python编写
    1. # -*- coding: utf-8 -*-
    2. """
    3. Created on Tue Dec 05 16:35:24 2017

    4. @author: linpc
    5. """
    6. import socket
    7. import sys
    8. datatosend="GET / HTTP/1.1\r\nHost: 192.168.31.138:8899\r\nConnection: keep-alive\r\nCache-Control: max-age=0\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.84 Safari/537.36\r\nUpgrade-Insecure-Requests: 1\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\nAccept-Encoding: gzip, deflate\r\nAccept-Language: zh-CN,zh;q=0.9\r\n\r\n"
    9. TCP_IP = '192.168.31.138'
    10. TCP_PORT = 8899

    11. ADDR=(TCP_IP,TCP_PORT)
    12. print ADDR

    13. try:
    14.     #create an AF_INET, STREAM socket (TCP)
    15.     s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    16. except socket.error, msg:
    17.     print 'Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
    18.     sys.exit();
    19. #s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    20. print 'Socket Created'
    21. try:
    22.         s.connect((TCP_IP,TCP_PORT))
    23. except socket.error , msg:
    24.         print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    25.         sys.exit()
    26. s.send(datatosend)
    27. i=i+1
    复制代码
    当客户端连接服务器后,ATS会触发事件函数202表接受了请求,建立连接,这个时候就绑定IOBuffer并声明buffer的空间,同时我直接发送给对端一个链接的信号,代码如下:
    1. void ReadFromVConn(TSCont cont,void *data)
    2. {
    3.         CacheVConnStruct *recv_sm=(CacheVConnStruct*)TSContDataGet( cont);
    4.         recv_sm->bufp =TSIOBufferCreate();
    5.         recv_sm->read_vconnp=(TSVConn)data;
    6.         TSVConnRead((TSVConn)data, cont, recv_sm->bufp, 500);
    7.         TSVConn input_request=(TSVConn)data;
    8.         printf("%s\n",input_request);
    9.         TSIOBuffer input_bufp=TSIOBufferCreate();
    10.         TSIOBufferReader input_bufp_reader=TSIOBufferReaderAlloc(input_bufp);
    11.         TSIOBuffer output_bufp=TSIOBufferCreate();
    12.         TSIOBufferReader output_bufp_reader=TSIOBufferReaderAlloc(output_bufp);
    13.         /////////发送数据部分
    14.         TSIOBufferBlock blockp;
    15.         char *ptr_block;
    16.         int64_t avail;
    17.         blockp    = TSIOBufferStart(output_bufp);
    18.         ptr_block = TSIOBufferBlockWriteStart(blockp, &avail);
    19.         memcpy(ptr_block, "i love ats", 11);
    20.         TSIOBufferProduce(output_bufp, 11);
    21.         TSVIO write_io=TSVConnWrite(input_request, cont,output_bufp_reader, 11);
    22. }
    复制代码
    当IOBuffer中有数据的时候就会触发事件102或者103,102表示发送过来的数据尚未填满buffer,103表示buffer已经满了。那么将数据读出的代码如下:
    1. int HandlerReadComplete(TSCont cont,void *data)
    2. {
    3.         CacheVConnStruct *cache_vconn=(CacheVConnStruct*)TSContDataGet( cont);

    4.         cache_vconn->readerp=TSIOBufferReaderAlloc(cache_vconn->bufp);
    5.         int avail=TSIOBufferReaderAvail(cache_vconn->readerp);
    6.         printf("共有%d个数据\n",avail);
    7.         if(avail>0)
    8.         {
    9.                 string str;
    10.                 const char *buffer_temp=new char[1024];
    11.                 int consumed=0;
    12.                 TSIOBufferBlock block = TSIOBufferReaderStart(cache_vconn->readerp);
    13.                 int64_t data_len;
    14.                 str.reserve(avail + 1);
    15.                 while (block)
    16.                         {
    17.                                 buffer_temp=TSIOBufferBlockReadStart( block, cache_vconn->readerp, &data_len);
    18.                                 str.append(buffer_temp,data_len);
    19.                                 consumed+=data_len;
    20.                                 block=TSIOBufferBlockNext( block);
    21.                         }
    22.                 printf("\n%s\n",str.c_str());
    23.         }
    24. }
    复制代码
    四、测试过程
    为了测试这个插件的过程,编写一个脚本,自动编译、自动重启、自动删除日志、自动启动测试客户端、自动关闭ATS,命令如下
    1. /usr/local/ats/bin/tsxs -lpthread -o hello.so -c hello.cpp  
    2. sudo /usr/local/ats/bin/tsxs -o hello.so -i
    3. sudo rm -f /usr/local/ats/var/log/trafficserver/*
    4. sudo /usr/local/ats/bin/trafficserver restart
    5. sleep 3s
    6. python /home/carl/httpsend.py &
    7. sleep 9s
    8. sudo /usr/local/ats/bin/trafficserver stop
    9. sleep 1s
    复制代码
    可以看到,客户端启动后,接收到了ATS发送过来的数据
    2017-12-24_223738.jpg
    同时查看ATS的日志,可以看到对应事件回调的过程

    2017-12-24_224045.jpg

    回复

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /2 下一条



    手机版|小黑屋|与非网

    GMT+8, 2024-4-26 02:51 , Processed in 0.118246 second(s), 18 queries , MemCache On.

    ICP经营许可证 苏B2-20140176  苏ICP备14012660号-2   苏州灵动帧格网络科技有限公司 版权所有.

    苏公网安备 32059002001037号

    Powered by Discuz! X3.4

    Copyright © 2001-2024, Tencent Cloud.