lua for循环_0026 lwip+lua做一个web服务(一)

本文介绍了使用lua和lwip构建一个简单的Web服务器的过程,包括通过nginx代理、处理HTTP请求、lua协程处理Web请求、动态内容输出等。文章探讨了性能、内存管理及存在的问题,并预告了后续的改进方向。
摘要由CSDN通过智能技术生成

上一篇已经做到了http收数据,先来做个最简单的响应。

kernel/test.c

static err_t test_recv(void* arg, struct tcp_pcb* pcb, struct pbuf* p, err_t err)
{
    printk("test_recv:%lx,buf:%lxn",pcb,p);
    if (!pcb||!p) return 0;
    dump_mem(p->payload, p->len);
    char *resp = "HTTP/1.1 200 OKnContent-Length:5nContent_Type:text/htmlnnhello";
    tcp_write(pcb, resp,strlen(resp),TCP_WRITE_FLAG_COPY);
    tcp_output(pcb);
    return 0;
}

curl "http://192.168.122.98:8080/aaaa"

hello

可以正常显示。在宿主机器上开启nginx代理。

server
{
  listen 8080;
  location /
  {
    proxy_pass http://192.168.122.98:8080;
    proxy_set_header       Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_redirect         off;
    proxy_connect_timeout 60s;
    proxy_read_timeout 60s;
    proxy_send_timeout 60s;

  }
}

在浏览器打开:

3125f3a6f17ddc8cfc31216d0a402f67.png

lwip源码下面有apps/httpd,复制到module/net/httpd下面。

初始化那里添加httpd_init。

curl "http://192.168.122.98:80/index.html"

把host nginx代理端口改成80

server
{
  listen 8080;
  location /index.html
  {
    root /dev/shm;
  }
  location /
  {
    proxy_pass http://192.168.122.98:80;
    proxy_set_header       Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_redirect         off;
    proxy_connect_timeout 60s;
    proxy_read_timeout 60s;
    proxy_send_timeout 60s;

  }

}

dcd2091e24028379574b11cad62c86c8.png

打开是内置的index.html。

发送包那里有个bug,lwip传过来pbuf可能是个链表,即一个包的内容分多块,不连续。

static err_t lwip_tx_func(struct netif *netif, struct pbuf *p)
{
    struct pbuf *q;
    //err_t  ret;
    struct iovec vecs[MAX_VECS];
    int nr_vec = 0;
    for (q = p; q != NULL; q = q->next) {
        //printk("q->payload:%016lx,size:%xn",q->payload,q->len);
        ulong pageend = ((ulong)q->payload+q->len)&~0xfff;
        if (((ulong)q->payload & ~0xfff) == pageend ) {
            //if ((ret = virtio_net_send_packet(q->payload, q->len))!=0) return ret;
            vecs[nr_vec].iov_base = q->payload;
            vecs[nr_vec].iov_len = q->len;
            nr_vec++;
            ASSERT(nr_vec<MAX_VECS);
        } else {
            printk("try to send not same pagen");
            //ASSERT(0);
            vecs[nr_vec].iov_base = q->payload;
            vecs[nr_vec].iov_len = pageend - (ulong)q->payload;
            nr_vec++;
            vecs[nr_vec].iov_base = (void *)pageend;
            vecs[nr_vec].iov_len = ((ulong)q->payload+q->len)&0xfff;
            nr_vec++;

        }
       //dump_mem(q->payload, q->len);
    }

    return (nr_vec && virtio_net_send_packets(vecs, nr_vec) ) || ERR_OK;
}

用ab测试:(去掉调试之后,main循环去掉nsleep)

ab -n 20 "http://192.168.122.98:80/index.html"
This is ApacheBench, Version 2.3 <$Revision: 1706008 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, Welcome to The Apache Software Foundation!

Benchmarking 192.168.122.98 (be patient).....done


Server Software:        lwIP/2.0.3d
Server Hostname:        192.168.122.98
Server Port:            80

Document Path:          /index.html
Document Length:        1751 bytes

Concurrency Level:      1
Time taken for tests:   0.004 seconds
Complete requests:      20
Failed requests:        0
Total transferred:      37620 bytes
HTML transferred:       35020 bytes
Requests per second:    4671.81 [#/sec] (mean)
Time per request:       0.214 [ms] (mean)
Time per request:       0.214 [ms] (mean, across all concurrent requests)
Transfer rate:          8581.71 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.0      0       0
Processing:     0    0   0.0      0       0
Waiting:        0    0   0.0      0       0
Total:          0    0   0.1      0       0

Percentage of the requests served within a certain time (ms)
  50%      0
  66%      0
  75%      0
  80%      0
  90%      0
  95%      0
  98%      0
  99%      0
 100%      0 (longest request)

-n不能太大,太大会丢链接,原因未知。

对比下主机端的nginx:

ab -n 20 "http://192.168.10.2:8080/index.html"
This is ApacheBench, Version 2.3 <$Revision: 1706008 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, Welcome to The Apache Software Foundation!

Benchmarking 192.168.10.2 (be patient).....done


Server Software:        openresty/1.7.10.2
Server Hostname:        192.168.10.2
Server Port:            8080

Document Path:          /index.html
Document Length:        1751 bytes

Concurrency Level:      1
Time taken for tests:   0.003 seconds
Complete requests:      20
Failed requests:        0
Total transferred:      40280 bytes
HTML transferred:       35020 bytes
Requests per second:    7843.14 [#/sec] (mean)
Time per request:       0.128 [ms] (mean)
Time per request:       0.128 [ms] (mean, across all concurrent requests)
Transfer rate:          15425.86 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.0      0       0
Processing:     0    0   0.0      0       0
Waiting:        0    0   0.0      0       0
Total:          0    0   0.0      0       0

Percentage of the requests served within a certain time (ms)
  50%      0
  66%      0
  75%      0
  80%      0
  90%      0
  95%      0
  98%      0
  99%      0
 100%      0 (longest request)

Requests per second: 7843.14 [#/sec] (mean)对比:

Requests per second: 4671.81 [#/sec] (mean)

性能还可以,毕竟目前只用了一个核心,只有BP在工作,其他核心都在打酱油。

主机端nginx用的是root /dev/shm,用的是内存。

lwip用的也是内存。

location /index.html
  {
    root /dev/shm;
  }

接下来要做的就是嵌入lua进行web处理。

主要参考openresty的ngx_lua模块进行移植。

对于一个http链接,启动一个lua协程进行处理。一个核心一个lua虚拟机。每个核心有多个lua线程(协程)。每个http链接对应lua协程有一个链接相关的数据(ctx)。

每个核心有个核心全局的lua虚拟机vmL

DEFINE_PER_CPU(lua_State *,vmL);

每个链接有个lua的修改数据,叫http_lua_ctx_t。可以通过链接获取ctx。

ngx_lua还有一个协程co_ctx,没有暂不支持用户创建协程。即ngx_lua有多个lua协程对应一个链接。而我们只有一个lua协程对应一个链接。

module/httpd/httpd_request.c:

http_request_t * http_create_request(struct http_state *hs)
{
    http_request_t * p = yaos_malloc(sizeof(http_request_t));
    if (!p) return p;
    p->hs = hs;
    for (int i=0; i< MAX_HTTP_MODULE; i++) p->ctx[i] = NULL;
    return p;
}
int httpd_lua_handle(struct http_state *hs, http_request_t *r)
{
    lua_State * L = getL();
    printk("r:%016lx, L:%016lxn", r, L);
    int http_lua_content_by_file(const char *name, lua_State *L, http_request_t *r);
    return http_lua_content_by_file("httpd", L, r);
}

httpd_request实现httpd跟lua的交互。

httpd.c里面:

  if (hs->r == NULL) hs->r = http_create_request(hs);
  return httpd_lua_handle(hs, hs->r);

为每个链接创建http_request_t。

http_lua_content_by_file调用lua/httpd.lua进行处理。

module/lua/http/content.c:

int http_lua_content_by_file(const char *name, lua_State *L, http_request_t *r)
{
    int co_ref;
    lua_State *co;
    http_lua_ctx_t  *ctx = http_get_module_ctx(r, HTTP_LUA_MODULE);
    if (ctx == NULL) {
       ctx = http_lua_create_ctx(r);
       if (ctx == NULL) {
           return ENOMEM;
       }
    } else {
       http_lua_reset_ctx(r, L, ctx);
    }
    if (load_lua_file(L, (char *)name) != 0) return ESRCH;
     /*  {{{ new coroutine to handle request */
    co = http_lua_new_thread(L, &co_ref);
    printk("co:%lxn",co);
    if ( co == NULL ) {
        return ENOMEM;
    }
    lua_xmove(L, co, 1);
    http_lua_get_globals_table(co);
    lua_setfenv(co, -2);
    http_lua_set_req(co, r);
    ctx->cur_co_ctx = &ctx->entry_co_ctx;
    ctx->cur_co_ctx->co = co;
    ctx->cur_co_ctx->co_ref = co_ref;
    int rc = http_lua_run_thread(L, r, ctx, 0);

    return rc;
}

lua_xmove实现load_lua_file加载的lua/httpd.lua复制到协程co上面。

这样httpd.c有数据收到之后,就会调用lua/httpd.lua进行处理。lua/httpd.lua就要做个状态机,来处理请求。

local resps = {ngx.get_req()}
yaos.show_dump(resps);
local ctx = ngx.ctx
local getn = table.getn
local cut = string.sub
local split = yaos.split
local find = string.find
local lower = string.lower
local inserttable = table.insert
--yaos.show_dump(ctx);
if not ctx.status then
    ctx.status = 0
    ctx.req = {headers = {},bodys = {}, content_len = 0, body_len = 0, method = "GET"}
end
local req = ctx.req
if getn(resps)<1 then
    return 1
end
--yaos.show_dump(ctx);
--yaos.show_dump(split(resps[1] or "testrntest2","rn"));
local function parse_line(linestr)
    if linestr =="" then
        if ctx.status == 1 then
            ctx.status = 2
        end
    elseif ctx.status == 1 then
        local pos = find(linestr,": ")
        if pos then
            local headername = lower(cut(linestr,1,pos-1))
            req.headers[headername] = cut(linestr,pos+2)
            if headername == "content-length" then
                 req.content_len = req.headers[headername] - 0
            end
        end
    elseif ctx.status == 2 then
       inserttable(req.bodys, linestr);
       req.body_len = req.body_len + #linestr;
       if req.body_len >= req.content_len then
           ctx.status = 3
       end
    end

end
for _,resp in ipairs(resps) do
    if ctx.status == 0 then
        local arr = split(resp, "rn");
        local firstline = arr[1] or "";
        local firstarr = split(firstline,"%s")
        req.method = firstarr[1]
        req.uri = firstarr[2]
        req.ver = firstarr[3]
        ctx.status = 1
        local idx = 2
        while idx <= getn(arr) do
           local linestr = arr[idx]
           parse_line(linestr)
           idx = idx +1
        end
    elseif ctx.status == 1 then
        local arr = split(resp, "rn");
        local idx = 1
        while idx <= getn(arr) do
           local linestr = arr[idx]
           parse_line(linestr)
           idx = idx +1
        end
    elseif ctx.status == 2 then
        parse_line(resp)

    end
end
if ctx.status == 2 and req.content_len == 0 then
    ctx.status = 3
end
if ctx.status == 3 then
    yaos.show_dump(ctx);
else
    yaos.say(ctx.status..req.body_len)
end

ngx.get_req实现从httpd里面的输入数据复制到lua。由于pbuf可能有多个,因此ngx.get_req返回值不固定。local resps = {ngx.get_req()},这样就可以把1个或者多个返回值放到resps数组里面。

static int ngx_lua_get_req(lua_State *L)
{
    http_request_t *r = http_lua_get_req(L);
    if (r == NULL) {
        return luaL_error(L, "request object not found");
    }

    struct pbuf * p = get_req_buf(r->hs);
    if (!p) {
       lua_pushnil(L);
       return 1;

    }
    if (p->next == NULL) {
        lua_pushlstring(L, (char *) p->payload, p->len);
        return 1;

    }
    int nr = 0;
    while (p) {
        lua_pushlstring(L, (char *) p->payload, p->len);
        nr++;
        p = p->next;

    }
    return nr;
}

运行的时候,根据ngx.ctx.status,进行处理。

status=0表示初始值,处理http请求第一行,即request method,url,http版本。

status=1表示处理了第一行,开始处理头部

status=2表示头部处理结束,记录content_len

status=3表示body处理结束,body_len==content_len

请求数据可能分多次到达,尤其是post一个大文件的时候,把每次的body数据加入到req.bodys数组里面。

发送一个json数据进行测试:

curl -X GET -d '{"user_id": "123", "coin":100, "success":1, "msg":"OK!" }' 192.168.122.98:80/test?dd=2

运行结果

ctx->ctx_ref:fffffffe,fffffffe _ctx_arr
{
        "status" = 3,
        "req" = {
                "ver" = "HTTP/1.1",
                "method" = "GET",
                "body_len" = 57,
                "content_len" = 57,
                "uri" = "/test?dd=2",
                "bodys" = {
                        [1] = "{"user_id": "123", "coin":100, "success":1, "msg":"OK!" }",
                },
                "headers" = {
                        "host" = "192.168.122.98",
                        "content-type" = "application/x-www-form-urlencoded",
                        "content-length" = "57",
                        "accept" = "*/*",
                        "user-agent" = "curl/7.47.0",
                },
        },
}

现在来测试发送一个文件:

curl -d@README 192.168.122.98:80/te

ctx->ctx_ref:fffffffe,fffffffe _ctx_arr
{
        "status" = 3,
        "req" = {
                "ver" = "HTTP/1.1",
                "method" = "POST",
                "body_len" = 437,
                "content_len" = 437,
                "uri" = "/te",
                "bodys" = {
                        [1] = "README for LuaJIT 2.1.0-beta3-----------------------------LuaJIT is a Just-In-Time (JIT) compiler for the Lua programming language.Project Homepage: http://luajit.org/LuaJIT is Copyright (C) 2005-2020 Mike Pall.LuaJIT is free software, released under the MIT license.See full Copyright Notice in the COPYRIGHT file or in luajit.h.Documentation for LuaJIT is available in HTML FORMAT - ZAKŁAD PRODUKCJI MEBLI",
                        [2] = "ease point your favorite browser to: doc/luajit.html",
                },
                "headers" = {
                        "host" = "192.168.122.98",
                        "content-type" = "application/x-www-form-urlencoded",
                        "content-length" = "437",
                        "accept" = "*/*",
                        "user-agent" = "curl/7.47.0",
                },
        },
}

现在只有处理请求,还没有输出。

现在来添加一个输出

lua/web/index.lua

local html= [=[
<html>
<head><title>FROM LUA:lwIP - A Lightweight TCP/IP Stack</title></head>
<body bgcolor="white" text="black">

    <table width="100%">
      <tr valign="top"><td width="80">
          <a href="RISE"><img src="/img/sics.gif"
          border="0" alt="SICS logo" title="SICS logo"></a>
        </td><td width="500">
          <h1>lwIP - A Lightweight TCP/IP Stack</h1>
          <p>
            The web page you are watching was served by a simple web
            server running on top of the lightweight TCP/IP stack <a
            href="RISE">lwIP</a>.
          </p>
          <p>
            lwIP is an open source implementation of the TCP/IP
            protocol suite that was originally written by <a
            href="RISE">Adam Dunkels
            of the Swedish Institute of Computer Science</a> but now is
            being actively developed by a team of developers
            distributed world-wide. Since it's release, lwIP has
            spurred a lot of interest and has been ported to several
            platforms and operating systems. lwIP can be used either
            with or without an underlying OS.
          </p>
          <p>
            More information about lwIP can be found at the lwIP
            homepage at <a
            href="lwIP - A Lightweight TCP/IP stack - Summary">lwIP - A Lightweight TCP/IP stack - Summary</a>
            or at the lwIP wiki at <a
            href="lwIP Wiki">lwIP Wiki</a>.
          </p>
        </td><td>
          &nbsp;
        </td></tr>
      </table>
</body>
</html>

]=]
local headers = ""HTTP/1.1 200 OKrnContent-Type: text/html; charset=UTF-8;rnContent-Length: "..#html.."rnrn"
ngx.say(headers)
ngx.say(html)

module/lua/lua_files.c

添加该文件:

DEFINE_LUA_FILE(dump);
DEFINE_LUA_FILE(split);
DEFINE_LUA_FILE(test2);
DEFINE_LUA_FILE(test);
DEFINE_LUA_FILE(test3);
DEFINE_LUA_FILE(httpd);
DEFINE_LUA_FILE(web_index);

httpd.c添加输出函数:

err_t
http_send_content(struct http_state *hs, char *p, size_t len)
{
    hs->file = p;
    hs->left = len;
    hs->handle = &hs->file_handle;
    hs->file_handle.len = len;
    hs->file_handle.index = len;
    hs->file_handle.data = p;
    http_send_data_nonssi(hs->pcb, hs);
    return ERR_OK;

}

module/lua/http/content.c添加ngx.say函数

static int ngx_lua_say(lua_State *L)
{
    http_request_t *r = http_lua_get_req(L);
    if (r == NULL) {
        return luaL_error(L, "request object not found");
    }
    int argc = lua_gettop(L);
    if (argc != 1) return luaL_error(L, "expecting one argument");
    uchar *p;
    size_t len;
    p = (uchar *) luaL_checklstring(L, 1, &len);
    err_t http_send_content(struct http_state *hs, char *p, size_t len);

    err_t err = http_send_content(r->hs, p, len);
    printk("http_send_content:%dn",err);
    return 0;

}

lua/httpd.lua添加输出

if ctx.status == 3 then
    yaos.do_file("web_index");
    yaos.show_dump(ctx);
else
    yaos.say(ctx.status..req.body_len)
end

ctx.status为3,即request body读取完成之后

curl -d@t.jar 192.168.122.98:80/te

<html>
<head><title>FROM LUA:lwIP - A Lightweight TCP/IP Stack</title></head>
<body bgcolor="white" text="black">

    <table width="100%">
      <tr valign="top"><td width="80">
          <a href="RISE"><img src="/img/sics.gif"
          border="0" alt="SICS logo" title="SICS logo"></a>
        </td><td width="500">
          <h1>lwIP - A Lightweight TCP/IP Stack</h1>
          <p>
            The web page you are watching was served by a simple web
            server running on top of the lightweight TCP/IP stack <a
            href="RISE">lwIP</a>.
          </p>
          <p>
            lwIP is an open source implementation of the TCP/IP
            protocol suite that was originally written by <a
            href="RISE">Adam Dunkels
            of the Swedish Institute of Computer Science</a> but now is
            being actively developed by a team of developers
            distributed world-wide. Since it's release, lwIP has
            spurred a lot of interest and has been ported to several
            platforms and operating systems. lwIP can be used either
            with or without an underlying OS.
          </p>
          <p>
            More information about lwIP can be found at the lwIP
            homepage at <a
            href="lwIP - A Lightweight TCP/IP stack - Summary">lwIP - A Lightweight TCP/IP stack - Summary</a>
            or at the lwIP wiki at <a
            href="lwIP Wiki">lwIP Wiki</a>.
          </p>
        </td><td>
          &nbsp;
        </td></tr>
      </table>

10bd86f59b15dd878047cc4a35813f0b.png

下面实践动态内容输出:

修改httpd.lua,添加querystring

    if ctx.status == 0 then
        local arr = split(resp, "rn");
        local firstline = arr[1] or "";
        local firstarr = split(firstline,"%s")
        req.method = firstarr[1]
        if firstarr[2] then
            local querypos = find(firstarr[2],'?');
            if querypos then
                req.uri = cut(firstarr[2], 1, querypos - 1);
                req.query_string = cut(firstarr[2], querypos+1)
            else
                req.uri = firstarr[2]
            end
        end
        req.ver = firstarr[3]
        ctx.status = 1
        local idx = 2
        while idx <= getn(arr) do
           local linestr = arr[idx]
           parse_line(linestr)
           idx = idx +1
        end
...
if ctx.status == 3 then
    local route = {test="web_test",json="web_json"}
    yaos.do_file(route[cut(req.uri,2)] or "web_index");
    yaos.show_dump(ctx);
else
    yaos.say(ctx.status..req.body_len)
end

添加2个路由,test,json,其他转到web_index

curl -X GET -d '{"user_id": "123", "coin":100, "success":1, "msg":"OK!" }' 192.168.122.98:80/test?dd=2

query body:
{"user_id": "123", "coin":100, "success":1, "msg":"OK!" }

test把body部分回显。

test.lua:

local ctx = ngx.ctx
local concat = table.concat
local req = ctx.req
local html = "query body:n"..concat(req and req.body_arr or {})
local headers = "HTTP/1.1 200 OKrnContent-Type: text/html; charset=UTF-8;rnContent-Length: "..#html.."rnrn"
ngx.say(headers)
ngx.say(html)

curl -X GET -d '{"user_id": "123", "coin":100, "success":1, "msg":"OK!" }' 192.168.122.98:80/json?dd=2

{"query_string":"dd=2","method":"GET","body_len":57,"content_len":57,
"body_arr":["{"user_id": "123", "coin":100, "success":1, "msg":"OK!" }"],
"ver":"HTTP/1.1","uri":"/json","headers":{"host":"192.168.122.98","content-type":
"application/x-www-form-urlencoded",
"content-length":"57","accept":"*/*","user-agent":"curl/7.47.0"}}

json.lua:

local cjson=require("cjson")
local ctx = ngx.ctx
local concat = table.concat
local req = ctx.req
local cut = string.sub
local body = cjson.encode(req)
local html = body
local headers = "HTTP/1.1 200 OKrnContent-Type: application/json; charset=UTF-8;rnContent-Length: "..#html.."rnrn"
ngx.say(headers)
ngx.say(html)

json.lua把请求数据以json格式输出

用了cjson包,移植的openresty下面的lua-cjson-2.1.0.7

同时添加了lua系统的package支持,并且添加了自己的loader。

int luaopen_cjson(lua_State *l);
static const luaL_Reg lua_module[] = {
  { "cjson",                 luaopen_cjson },
  { NULL,               NULL }
};

static int lua_package_loader_yaos(lua_State *L)
{
  const char *name = luaL_checkstring(L, 1);
  printk("package_loader_yaos:%sn",name);
  const luaL_Reg *lib;
  for (lib = lua_module; lib->func; lib++) {
      if (strcmp(lib->name, name)==0) {
          lua_pushcfunction(L, lib->func);
          return 1;
      }
  }
  if( 0==load_lua_file(L, (char *)name)) return 1;
  lua_pushnil(L);
  return 1;
}

先查找lua_module数组,有的话就返回。这个是c语言写的包。

然后再试图load_lua_file,这个是lua文件。

运行本篇:

lwip需要重新编译。

git clone https://github.com/saneee/x86_64_kernel.git
cd lwip-2.1.2
make clean
make
cd ../luajit2
make clean
make
cd ../lua-cjson-2.1.0.7
make
cd ../0026
make qemu

此篇只进行功能性实践,还有很多东西需要完善,也存在bug。

目前用的是0拷贝发送,但是有可能数据还没发送成功,内存就被释放了。需要添加清理函数,virtio-net驱动发送成功了,再释放相应内存。如果遇到没反应重新启动多试几次。

另外lua协程也没清理,只有创建,没有删除。

下一篇将完善内存清理,以及进行性能测试。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值