skynet clientsocket 导致 io.read 无法正确工作的问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gneveek/article/details/78940693

问题描述

在使用 clientsocket 之后,使用 io.read() 无法正确读取用户输入。

issue 里的讨论

关于这个问题,在 skynet 的 issue 里也有人提到:
require “clientsocket” 后 io.read()行为改变 #539

io.read()等待读取一行命令行的输入,以回车结束;
require “clientsocket”后,io.read() 需要先按回车,然后输入,再按两次回车,输入才会生效。
请问这是怎么回事?

--测试代码
package.cpath = "./luaclib/?.so;"
--local socket = require "clientsocket"
local CMD = {}
local loop_flag = true

function CMD.exit(...)
print("reach the end...")
loop_flag = false
end

local function start(cmd, ...)
local f = assert(CMD[cmd], "command " .. cmd .. " not found")
f(...)
end

while loop_flag do
local cmd = io.read()
local ok, status = pcall(start, cmd)
if not ok then
print(status)
end
end
--------------------------测试结果---------------------
注释require "clientsocket"前:
dongsongtao:skynet ((v1.0.0-rc5))$ lua examples/loop_test.lua
exit(回车)
(回车)
(回车)
examples/loop_test.lua:12: command not found
(回车)
exit(回车)
(回车)
reach the end...
注释require "clientsocket"后:
dongsongtao:skynet ((v1.0.0-rc5))$ lua examples/loop_test.lua
exit(回车)
reach the end...

对此云风的回复是:

不要使用 clientsocket , 仅用于 examples 。请使用更成熟的 socket 库或自己实现。

问题原因

其实很简单,看下代码就知道为什么会这样了, 代码位于 lua-clientsocket.c 内。

原因就是在这个库里,云风简单的实现了一个非阻塞的io.read接口: socket.readline() , 这个函数可以读取用户的输入,并且不阻塞当前协程。

这个函数的原理:
1. 创建一个线程,不断的从 stdin 内读取输入,并把读取到的内容保存到一个队列中。
2. lua 层,每次调用 socket.readline() 接口,从上述队列中取出一条用户输入。

由此不难发现 io.read 不能正确工作的原因,因为所有的输入都被readline_stdin线程给“抢”去了,通过 lua 的 io.read 自然不可能得到任何结果。

// quick and dirty none block stdin readline

#define QUEUE_SIZE 1024

// 存放用户输入的队列
struct queue {
    pthread_mutex_t lock;
    int head;
    int tail;
    char * queue[QUEUE_SIZE];
};

// 循环读取用户输入的线程执行函数
static void *
readline_stdin(void * arg) {
    struct queue * q = arg;
    char tmp[1024];
    while (!feof(stdin)) {
        if (fgets(tmp,sizeof(tmp),stdin) == NULL) {
            // read stdin failed
            exit(1);
        }
        int n = strlen(tmp) -1;

        char * str = malloc(n+1);
        memcpy(str, tmp, n);
        str[n] = 0;

        pthread_mutex_lock(&q->lock);
        q->queue[q->tail] = str;

        if (++q->tail >= QUEUE_SIZE) {
            q->tail = 0;
        }
        if (q->head == q->tail) {
            // queue overflow
            exit(1);
        }
        pthread_mutex_unlock(&q->lock);
    }
    return NULL;
}

// socket.readstdin()
static int
lreadstdin(lua_State *L) {
    struct queue *q = lua_touserdata(L, lua_upvalueindex(1));
    pthread_mutex_lock(&q->lock);
    if (q->head == q->tail) {
        pthread_mutex_unlock(&q->lock);
        return 0;
    }
    char * str = q->queue[q->head];
    if (++q->head >= QUEUE_SIZE) {
        q->head = 0;
    }
    pthread_mutex_unlock(&q->lock);
    lua_pushstring(L, str);
    free(str);
    return 1;
}

如何解决?

知道了问题的原因,解决起来就太简单了。

1. 不改 lua-clientsocket.c 源码

不要直接使用 io.read(), 而是改为使用 socket.readstdin() , 如果还是想要“阻塞”,可以使用 socket.usleep() .

使用范例:

skynet自身带了一个client demo,在 examples/client.lua 内,这个里面已经给出了这种用法。

while true do
    dispatch_package()
    local cmd = socket.readstdin()
    if cmd then
        if cmd == "quit" then
            send_request("quit")
        else
            send_request("get", { what = cmd })
        end
    else
        socket.usleep(100)
    end
end

2. 修改源码

如果就是想用 io.read(), 并且也不想用非阻塞的IO, 可以把创建线程的代码注释掉,就不会把 stdin 的输入全吞掉了。

LUAMOD_API int
luaopen_client_socket(lua_State *L) {
    luaL_checkversion(L);
    luaL_Reg l[] = {
        { "connect", lconnect },
        { "recv", lrecv },
        { "send", lsend },
        { "close", lclose },
        { "usleep", lusleep },
        { NULL, NULL },
    };
    luaL_newlib(L, l);

    /*struct queue * q = lua_newuserdata(L, sizeof(*q));
    memset(q, 0, sizeof(*q));
    pthread_mutex_init(&q->lock, NULL);
    lua_pushcclosure(L, lreadstdin, 1);
    lua_setfield(L, -2, "readstdin");

    pthread_t pid ;
    pthread_create(&pid, NULL, readline_stdin, q);*/

    return 1;
}

猜你喜欢

转载自blog.csdn.net/gneveek/article/details/78940693