手手挤挤 发表于 2017-12-24 23:01:51

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

本帖最后由 ky123 于 2018-1-15 11:30 编辑

在树莓派3上开发Apache Traffic Server(ATS)
树莓派3+ATS——服务器插件
目录一、原理分析二、主要函数三、代码实现四、测试过程
-----------------------------------------------------------------------------------感谢E络盟提供的助赛基金。

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


二、主要函数
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函数,第二个参数为事件的代号
第三,接收有关数据的读、写操作的函数

当请求接入后,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来编译这个入口函数就有如下代码,同时我们再此将插件功能进行初始化extern "C" void TSPluginInit (int argc, const char *argv[]);
void TSPluginInit (int argc, const char *argv[])
{
      TSPluginRegistrationInfo info;
      info.plugin_name = "hello-world";
      info.vendor_name = "MyCompany";
      info.support_email = "ts-api-support@MyCompany.com";
      printf("开始运行插件\n");
      if (TSPluginRegister(&info) != TS_SUCCESS) {
                TSError("注册失败2");
      }
      helloinit();
      
}在初始化函数中,需要做的有四件事:创建continuations、绑定数据存放、绑定事件处理的主函数、绑定端口并监听。在这之前,先创建一用于保存数据的机构体(或者类)typedef struct {
TSIOBuffer bufp;
TSIOBuffer out_bufp;
TSIOBufferReader readerp;
TSIOBufferReader out_readerp;

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

      TSNetAccept(hello_appcep_cont_, 8899,AF_INET ,1);
      
}
int HelloAccpetHandler(TSCont cont,TSEvent event,void *data)
{
      printf("cont是:%d\n",cont);
      printf("回调连接的事件为:%d\n",event);
      switch ((int)event)
                {
                case TS_EVENT_NET_ACCEPT:
                        printf("连接正常接入\n");
                        ReadFromVConn(cont,data);
                        break;
                case TS_EVENT_VCONN_READ_COMPLETE:
                        printf("读取完毕\n");
                        //HandlerReadComplete(cont,data);
                        break;
                case TS_EVENT_VCONN_READ_READY:
                        printf("buffer尚未读满\n");
                        HandlerReadComplete(cont,data);
                }
      return 1;
}接下来,需要等待连接接入,为此我们需要写个客户端的脚本,方便采用python编写# -*- coding: utf-8 -*-
"""
Created on Tue Dec 05 16:35:24 2017

@author: linpc
"""
import socket
import sys
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"
TCP_IP = '192.168.31.138'
TCP_PORT = 8899

ADDR=(TCP_IP,TCP_PORT)
print ADDR

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

      cache_vconn->readerp=TSIOBufferReaderAlloc(cache_vconn->bufp);
      int avail=TSIOBufferReaderAvail(cache_vconn->readerp);
      printf("共有%d个数据\n",avail);
      if(avail>0)
      {
                string str;
                const char *buffer_temp=new char;
                int consumed=0;
                TSIOBufferBlock block = TSIOBufferReaderStart(cache_vconn->readerp);
                int64_t data_len;
                str.reserve(avail + 1);
                while (block)
                        {
                              buffer_temp=TSIOBufferBlockReadStart( block, cache_vconn->readerp, &data_len);
                              str.append(buffer_temp,data_len);
                              consumed+=data_len;
                              block=TSIOBufferBlockNext( block);
                        }
                printf("\n%s\n",str.c_str());
      }
}四、测试过程
为了测试这个插件的过程,编写一个脚本,自动编译、自动重启、自动删除日志、自动启动测试客户端、自动关闭ATS,命令如下/usr/local/ats/bin/tsxs -lpthread -o hello.so -c hello.cpp
sudo /usr/local/ats/bin/tsxs -o hello.so -i
sudo rm -f /usr/local/ats/var/log/trafficserver/*
sudo /usr/local/ats/bin/trafficserver restart
sleep 3s
python /home/carl/httpsend.py &
sleep 9s
sudo /usr/local/ats/bin/trafficserver stop
sleep 1s可以看到,客户端启动后,接收到了ATS发送过来的数据

同时查看ATS的日志,可以看到对应事件回调的过程



页: [1]
查看完整版本: 【连载二】树莓派3+ATS——服务器插件

Baidu
map