前一节我们讲到了如何用libuv实现一个TCP服务器,用libuv实现一个客户端与用libuv实现一个TCP服务器极为类似。不同的地方在于不需要进行uv_tcp_bind操作,将uv_listen改为uv_tcp_connect。
实现一个TCP客户端的基本步骤为:
1.
uv_tcp_init
建立tcp句柄2.uv_tcp_connect建立tcp连接
3.使用stream操作来和客户端通信
所用到的API为:
1.uv_tcp_init
2.uv_ip4_addr
3.uv_tcp_connect
4.uv_write/uv_read_start
下面讲只针对TCP客户端的实现介绍一个新的API函数uv_tcp_connect。
1.uv_tcp_connect函数讲解
int uv_tcp_connect(uv_connect_t* req,uv_tcp_t* handle,const struct sockaddr* addr,uv_connect_cb cb);
参数1:连接请求对象
参数2:TCP客户端对象
参数3:填充好的struct sockaddr_in结构体
参数4:回调函数
struct sockaddr_in addr;
uv_connect_t* connect = (uv_connect_t*)malloc(sizeof(uv_connect_t));
uv_ip4_addr("192.168.65.205",DEFAULT_PORT,&addr);
int r = uv_tcp_connect(connect,&mysocket,(const struct sockaddr*)&addr,on_connect);
if(r)
{
fprintf(stderr, "connect error %s\n", uv_strerror(r));
return 1;
}
2.uv_tcp_connect回调函数介绍
void (uv_connect_cb)(uv_connect_t req, int status);
在连接成功/失败后调用此函数。
status:返回的状态,小于零代表出错。
req:连接请求对象,req->handle指向TCP客户端对象(mysocket),可直接将req->handle用于uv_write,uv_read_start等流操作中。
3.传输指定文件给服务器的代码实现
#include
#include
#include
uv_loop_t *loop;
#define DEFAULT_PORT 7000
uv_tcp_t mysocket;
char *path = NULL;
uv_buf_t iov;
char buffer[128];
uv_fs_t read_req;
uv_fs_t open_req;
void on_read(uv_fs_t *req);
void on_write(uv_write_t* req, int status)
{
if (status < 0)
{
fprintf(stderr, "Write error: %s\n", uv_strerror(status));
uv_fs_t close_req;
uv_fs_close(uv_default_loop(), &close_req, open_req.result, NULL);
uv_close((uv_handle_t *)&mysocket,NULL);
exit(-1);
}
else
{
uv_fs_read(uv_default_loop(), &read_req, open_req.result, &iov, 1, -1, on_read);
}
}
void on_read(uv_fs_t *req)
{
if (req->result < 0)
{
fprintf(stderr, "Read error: %s\n", uv_strerror(req->result));
}
else if (req->result == 0)
{
uv_fs_t close_req;
// synchronous
uv_fs_close(uv_default_loop(), &close_req, open_req.result, NULL);
uv_close((uv_handle_t *)&mysocket,NULL);
}
else
{
iov.len = req->result;
uv_write((uv_write_t *)req,(uv_stream_t *)&mysocket,&iov,1,on_write);
}
}
void on_open(uv_fs_t *req)
{
if (req->result >= 0)
{
iov = uv_buf_init(buffer, sizeof(buffer));
uv_fs_read(uv_default_loop(), &read_req, req->result,&iov, 1, -1, on_read);
}
else
{
fprintf(stderr, "error opening file: %s\n", uv_strerror((int)req->result));
uv_close((uv_handle_t *)&mysocket,NULL);
exit(-1);
}
}
void on_connect(uv_connect_t* req, int status)
{
if(status < 0)
{
fprintf(stderr,"Connection error %s\n",uv_strerror(status));
return;
}
fprintf(stdout,"Connect ok\n");
uv_fs_open(loop,&open_req,path,O_RDONLY,-1,on_open);
}
int main(int argc,char **argv)
{
if(argc < 2)
{
fprintf(stderr,"Invaild argument!\n");
exit(1);
}
loop = uv_default_loop();
path = argv[1];
uv_tcp_init(loop,&mysocket);
struct sockaddr_in addr;
uv_connect_t* connect = (uv_connect_t*)malloc(sizeof(uv_connect_t));
uv_ip4_addr("127.0.0.1",DEFAULT_PORT,&addr);
int r = uv_tcp_connect(connect,&mysocket,(const struct sockaddr *)&addr,on_connect);
if(r)
{
fprintf(stderr, "connect error %s\n", uv_strerror(r));
return 1;
}
return uv_run(loop,UV_RUN_DEFAULT);
}
这段代码利用了libuv文件操作、流操作、TCP实现等知识。代码的基本逻辑如下:
1.创建TCP客户端对象,和TCP连接请求对象,并与服务器建立连接。
2.连接成功后打开要传输的文件。
3.打开文件成功后读取一定的文件内容,并在读取成功后将文件内容利用stream操作发送给服务器。
4.发送成功后再次读取文件内容,并在读取成功后发送给服务器。
5.循环进行第4步操作,直到读到文件末尾。
6.关闭文件、关闭流。
测试:
1.打开服务器,监听7000端口,并将输出重定向到文件:
nc -l 7000 > test_recv.jpg
2.编译并运行程序:
gcc main.c -luv
./a.out test.jpg