通过socket发送文件怎么消除警告?

65 views
Skip to first unread message

Heibor Zheng

unread,
Aug 13, 2020, 1:19:23 PM8/13/20
to skynet...@googlegroups.com
各位好:
        有个问题请教下, 我通过socket给客户端发送文件,代码如下:

local function read_block(path)
    local size = 2 ^ 13
    local file = assert(io.open(path, "rb"))
    return function()
        local block = file:read(size)
        if block then
            -- may need to sleep at here to release computing resources
            skynet.sleep(2)
            return block
        else
            file:close()
        end
    end
end

我在通过在循环中调用这个read_block函数每次向客户端发送8192字节数据,直到文件发送完毕后关闭文件.

但是我发现会产生一些警告:

[:0000000a] WARNING: 1031 K bytes need to send out (fd = 3)
[:0000000a] WARNING: 2050 K bytes need to send out (fd = 3)
[:0000000a] WARNING: 4103 K bytes need to send out (fd = 3)
[:0000000a] WARNING: 8193 K bytes need to send out (fd = 3)

难道是我误用skynet.sleep? 请问有没有什么办法消除这个警告?
--
with kind regards

云风 Cloud Wu

unread,
Aug 13, 2020, 10:11:36 PM8/13/20
to skynet...@googlegroups.com
Heibor Zheng <kyt...@gmail.com> 于2020年8月14日周五 上午1:19写道:
>
> 各位好:
> 有个问题请教下, 我通过socket给客户端发送文件,代码如下:
>
> local function read_block(path)
> local size = 2 ^ 13
> local file = assert(io.open(path, "rb"))
> return function()
> local block = file:read(size)
> if block then
> -- may need to sleep at here to release computing resources
> skynet.sleep(2)
> return block
> else
> file:close()
> end
> end
> end
>
> 我在通过在循环中调用这个read_block函数每次向客户端发送8192字节数据,直到文件发送完毕后关闭文件.

你写漏了发送调用吗?这里没有看到。

>
> 但是我发现会产生一些警告:
>
> [:0000000a] WARNING: 1031 K bytes need to send out (fd = 3)
> [:0000000a] WARNING: 2050 K bytes need to send out (fd = 3)
> [:0000000a] WARNING: 4103 K bytes need to send out (fd = 3)
> [:0000000a] WARNING: 8193 K bytes need to send out (fd = 3)

首先说说为什么会有警告。

skynet 最初设计的时候,并未考虑大量下行数据的场合。另外,所有对外推送数据的行为,都假定是非阻塞的。(阻塞调用有可能引起服务重入,导致状态改变)

所以,socket.write 会无条件的把待发数据推送给 socket 线程。

这就引出了一个潜在的问题:如果你推送的速度比实际系统能发出的速度(同时取决于 client
接收的速度/带宽),那么在不停歇的发送大块数据的时候,就会挤压很多临时内存在 socket 线程。这有可能撑爆内存(OOM) 。

为了让应用层有办法解决内存问题,所以后来又引入了 warning 制度。当发送 buffer 太大时,服务会收到 warning
消息。这样就有机会暂停发送,等待发送 buffer 被 socket 线程处理完毕。

https://github.com/cloudwu/skynet/wiki/Socket * socket.warning(id,
callback) 当 id 对应的 socket 上待发的数据超过 1M 字节后,系统将回调 callback
以示警告。`function callback(id, size)` 回调函数接收两个参数 id 和 size ,size 的单位是 K
。如果你不设回调,那么将每增加 64K 利用 skynet.error
写一行错误信息。一旦产生过至少一次超出警告,那么在待发缓冲区清空时,还会再产生一个 size 为 0 的消息,示意缓冲区已空。

写 log 只是一个 socket 模块的默认行为,如果仅仅只是不想产生 log ,最直接的方法是定义一个空的 warning 函数。

但如果是下发大量数据(文件),我们就应该利用这个消息做流量控制。这里给出一段示范代码做参考:

local function new_sender(fd)
local co = coroutine.running()
local overload = false
local waiting = false
local sendfile = cacheserver_io.sendfile

socket.warning(fd,
function (id, size)
if size > 0 then
overload = true
else
overload = false
if waiting then
waiting = false
skynet.wakeup(co)
end
end
end)
return function(filename, size)
local offset = 0
while offset < size do
offset = sendfile(fd, filename, offset)
if not offset then
-- read file error
return
end
if overload then
waiting = true
skynet.wait(co)
else
skynet.yield()
end
end
return true
end
end

我们为每个连接 fd 生成一个 sender 函数,再用 sender 函数发送文件。
其中 sendfile 可以把 filename 从 offset 处发送一部分。下面是 C 版本的实现,也可以用 lua 实现。

static int
lsendfile(lua_State *L) {
int fd = luaL_checkinteger(L, 1);
const char * filename = luaL_checkstring(L, 2);
size_t offset = (size_t)luaL_optinteger(L, 3, 0);
int i;

FILE *f = fopen(filename, "rb");
if (f == NULL) {
return 0;
}
fseek(f, offset, SEEK_SET);

for (i=0;i<SENDLIMIT;i++) {
// don't send the whole file at once
void * buf = malloc(FILECHUNK);
size_t sz = fread(buf, 1, FILECHUNK, f);
if (sz == 0) {
free(buf);
break;
}
if (skynet_socket_send(NULL, fd, buf, sz) < 0) {
// socket error
fclose(f);
return 0;
}
offset += sz;
if (sz < FILECHUNK)
break;
}
fclose(f);
lua_pushinteger(L, offset);
return 1;
}
Reply all
Reply to author
Forward
0 new messages