Basic usage of libuv
libuv and its relation with nodejs
When working with Javascript, there is the concept of event loop and the non-blocking model that we should aware of. Every Javascript runtime needs to have its own event loop implementation.
An event loop is like a job queue where events will be pushed into it and be processed in time. Those events can be an IO event, a user’s input, etc. Each event will be associated with a callback(or handler) that the event loop calls when it proccess the event.
NodeJS use libuv as its event loop library. In this post, I will make some simple program with libuv.
Programming model of livuv
- Initialize the event loop
- Create event handler
- Run event loop
Initiate event loop
The center part of event loop’s initialization is uv_loop_t
struct and uv_loop_init
function.
uv_loop_init signature:
int uv_loop_init(uv_loop_t* loop)
Each event loop has its own data structure called uv_loop_t
.
To init a loop, we use uv_loop_init
.
So our code could be like:
#include <uv.h>
int main() {
uv_loop_t loop;
uv_loop_init(&loop);
}
You may notice that all libuv public function and data structure have uv_
as its prefix.
Create event handler
libuv support various type of IO events, including:
- File IO operations such as open/read/write file, pipe,…
- Networking operations such as socket(TCP, UDP)
File operation is just the same as normal synchronous version of libc. We have 3 main functions for file operations:
uv_fs_open
This function is use to open a file, its signature:
int uv_fs_open(uv_loop_t* loop,
uv_fs_t* req,
const char* path,
int flags,
int mode,
uv_fs_cb cb)
loop
is the one we’ve created at step 1.req
: everytime we create an event handler, we need to create its coresponding request. The request depends on IO operation. In file IO, request’s type isuv_fs_t
. We will need to create and initialize this variable before passing it touv_fs_open
- path, flags, mode is the same as open(2)
- that last parameter is our callback function. It needs to have a signature of
void callback(uv_fs_t* req)
So to open a file, we’ll use it as:
void open_cb(uv_fs_t *req) {
// a do nothing callback
}
uv_fs_t open_req;
uv_loop_t loop;
int main(){
uv_loop_init(&loop);
uv_fs_open(&loop, &open_req, filepath, O_RDONLY, NULL, open_cb);
}
The open_cb
is our callback function which will be called after file-open operation finished. File descriptor will pass to req->result
.
Notice that until we start the event loop, no operation will be run yet.
After open a file, we can read and write to it using uv_fs_read
and uv_fs_write
. Their signature are as follow:
int uv_fs_read(uv_loop_t* loop,
uv_fs_t* req,
uv_file file,
const uv_buf_t bufs[],
unsigned int nbufs,
int64_t offset,
uv_fs_cb cb)`
int uv_fs_write(uv_loop_t* loop,
uv_fs_t* req,
uv_file file,
const uv_buf_t bufs[],
unsigned int nbufs,
int64_t offset,
uv_fs_cb cb)`
loop
,cb
andreq
is the same asuv_fs_open
file
is the file descriptor(result of open request,open_req.result
)bufs
is an array ofuv_buf_t
each element represents a buffer that we’ll read from/write to. This allows us to read/write into multiple buffers at the same time.nbufs
is length of bufsoffset
is the position to read from/write to. -1 for current file pointer.
Now let add a file-read operation. It will happen inside our open_cb
.
char buffer[1024];
void open_cb(uv_fs_t *req) {
// req->result is file descriptor
iov = uv_buf_init(buffer, sizeof(buffer));
uv_fs_t read_req;
uv_fs_read(uv_default_loop(), &read_req, open_req.result,
&iov, 1, -1, read_cb);
}
void read_cb(uv_fs_t *req) {
// req->result is number of character read
// let just write it back to stdout
if (req->result > 0) {
printf("%.*s", req->result, buffer);
// continue reading till EOF
iov = uv_buf_init(buffer, sizeof(buffer));
uv_fs_t read_req;
uv_fs_read(uv_default_loop(), &read_req, open_req.result,
&iov, 1, -1, read_cb);
} else if (req->result == 0) {
// we've reached EOF
}
}
Run event loop
To start event loop, we’ll use uv_run
uv_run(loop, UV_RUN_DEFAULT);
uv_loop_close(loop); // also clean up after event loop return