0

使用示例代码了解 libuv 我遇到了一个我不确定的副作用。该代码使用 malloc() 获取内存以存储来自网络上的客户端的数据,然后将相同的数据发送回,只是回显。然后它使用 free 释放内存。这通过回调循环一遍又一遍地重复。获取内存的代码行是:

uv_write_t *req = (uv_write_t *) malloc(sizeof(uv_write_t));

释放内存的行是:

free((char*) req->data);
free(req);

但是,如果您输入一个长字符串,例如“街上的单词是什么?” 要回显,然后放入较短的字符串,例如旧字符串的“Hi”片段,在较短的字符串回显后将重新出现。例如输出可以是这样的:

街上说什么?嗨嗨你好,他在街上说的话?

由于内存正在被释放,我不确定为什么旧片段会重新出现。我对这个主题的想法是,要么我对 malloc 和 free() 有一些我不了解的地方,要么在库中存在一个错误,它决定了传入数据所需的大小以及在使用更长的字符串后我得到了垃圾作为太大的内存块的一部分。如果是这样的话,那么它是我之前输入的一个片段的事实只是偶然的。这是可能的原因,还是我错过了什么?有没有其他信息。我应该包括澄清吗?

4

2 回答 2

1

malloc() 的实现会有所不同,但可以安全地假设对 malloc() 的调用可以返回指向先前 free() 的内存块的指针,并且返回的内存不会被清零。换句话说,malloc() 给我们一个指向包含先前初始化数据的数据的指针是完全正常的。

也就是说,我怀疑这里的根本问题将是一个未终止的字符串,这可能是您序列化字符串方式的产物。例如,如果您只是从客户端写入 strlen(str) 字节,则不会写入 NULL。因此,当服务器接收到消息时,它将有一个未终止的字符串。如果这是您计划传递字符串的方式,并且您计划将其视为普通的以空字符结尾的字符串,则服务器将需要将数据复制到足够大的缓冲区以容纳字符串和额外的 NULL 字符。

那么为什么你会看到过去消息的片段呢?大概是运气不好。如果这是一个非常简单的应用程序,那么 malloc() 很有可能返回一块与前一个请求重叠的内存。

So then why am I getting such clean output, shouldn't I see tons of garbled data, or a segfault for my string operations walking off into infinity? Again, dumb luck. Keep in mind that when the kernel first gives your application a page of memory, it will have first zeroed-out page out (this is done for security reasons). So, even though you might not have terminated the string, the page of heap memory where your string resides might be sitting in a relatively pristine zeroed-out state.

于 2013-03-29T23:36:41.170 回答
1

uv_write_t *req is not the data to be sent or received. It's just something like a handle to a write request.

Neither is req->data. That is a pointer to arbitrary private data for you. It might be used for example if you wanted to pass around some data related to the connection.

The actual payload data are sent through a write buffer (uv_buf_t) and received into a buffer that is allocated when a read request is served. That's why the read function wants an alloc parameter. Later that buffer is passed to the read callback.

The freeing of req->data assumes that 'data' pointed to some private data, typically a structure, that was malloc'd (by you).

As a rule of thumb, a socket is represented by a uv_xxx_t while reading and writing use 'request' structures. Writing a server (a typical uv use case) one doesn't know how many connections there will be, hence everything is allocated dynamically.

To make your life easier you might think in terms of pairs (open/close or start/done). So when accepting a new connection you start a cycle and allocate the client. When closing that connection you free it. When writing, you allocate the request as well as the payload data buffer. When done writing, you free them. When reading you allocate a read request and the payload data buffer is allocated behind the scene (through the alloc callback) when done reading (and having copied the payload data) you free them both.

There are ways to get the job done without all those malloc/free pairs (which aren't glourious performance wise) but for a novice I would agree with the uv docs; you should definitely start with the malloc/free route. To give you an idea: I pre-allocate everything for some ten or hundred thousand connections but that brings some administration and trickery with it, e.g. faking in the alloc call back to merely assign one of your pre-allocated buffers.

If asked to guess I'd suggest that avoiding malloc/free is only worth the trouble beyond well over 5k - 10k connections at any point in time.

于 2016-04-22T08:02:28.080 回答