前一節(jié)我們講到了如何用libuv實(shí)現(xiàn)一個TCP服務(wù)器壁公,用libuv實(shí)現(xiàn)一個客戶端與用libuv實(shí)現(xiàn)一個TCP服務(wù)器極為類似感论。不同的地方在于不需要進(jìn)行uv_tcp_bind操作,將uv_listen改為uv_tcp_connect。
實(shí)現(xiàn)一個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客戶端的實(shí)現(xiàn)介紹一個新的API函數(shù)uv_tcp_connect紊册。
1.uv_tcp_connect函數(shù)講解
int uv_tcp_connect(uv_connect_t* req,uv_tcp_t* handle,const struct sockaddr* addr,uv_connect_cb cb);
參數(shù)1:連接請求對象
參數(shù)2:TCP客戶端對象
參數(shù)3:填充好的struct sockaddr_in結(jié)構(gòu)體
參數(shù)4:回調(diào)函數(shù)
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回調(diào)函數(shù)介紹
void (uv_connect_cb)(uv_connect_t req, int status);
在連接成功/失敗后調(diào)用此函數(shù)比肄。
status:返回的狀態(tài),小于零代表出錯囊陡。
req:連接請求對象芳绩,req->handle指向TCP客戶端對象(mysocket),可直接將req->handle用于uv_write,uv_read_start等流操作中。
3.傳輸指定文件給服務(wù)器的代碼實(shí)現(xiàn)
#include <stdio.h>
#include <uv.h>
#include <stdlib.h>
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實(shí)現(xiàn)等知識。代碼的基本邏輯如下:
1.創(chuàng)建TCP客戶端對象痢畜,和TCP連接請求對象垛膝,并與服務(wù)器建立連接。
2.連接成功后打開要傳輸?shù)奈募?/p>
3.打開文件成功后讀取一定的文件內(nèi)容丁稀,并在讀取成功后將文件內(nèi)容利用stream操作發(fā)送給服務(wù)器吼拥。
4.發(fā)送成功后再次讀取文件內(nèi)容,并在讀取成功后發(fā)送給服務(wù)器线衫。
5.循環(huán)進(jìn)行第4步操作凿可,直到讀到文件末尾。
6.關(guān)閉文件授账、關(guān)閉流枯跑。
測試:
1.打開服務(wù)器,監(jiān)聽7000端口白热,并將輸出重定向到文件:
nc -l 7000 > test_recv.jpg
2.編譯并運(yùn)行程序:
gcc main.c -luv
./a.out test.jpg