c++ 如何在libuv中实现tcp服务器
1、说明
libuv中实现tcpserver的步骤和原生socket步骤类似,回忆一下linux下原生socket实现tcpserver的步骤:
- 初始化socket环境,获取socket套接字;
- bind()方法绑定套接字到本地IP;
- listen()方法监听socket,获取新连接;
- accept()方法接受客户端连接,返回客户端套接字;
- recv()方法接受客户端的数据;
- send()方法向客户端发送数据;
- closesocket()方法关闭套接字;
libuv和原生socket编程类似,步骤和API与原生socket编程步骤类似,但是使用却变得简单了,处处使用回调函数使得编程变得简单了。
2、libuv的tcpserver
libuv对于tcp消息的处理,同样是基于stream的,步骤如下:
- uv_tcp_init()建立tcp句柄;
- uv_tcp_bind()方法绑定ip;
- uv_listen()方法监听,有新连接时,调用回调函数;
- uv_accept()方法获取客户端套接字;
- uv_read_start()方法读取客户端数据;
- uv_write()方法想客户端发送数据;
- uv_close()关闭套接字;
3、API简介
附录是整个tcpserver的源代码,其中涉及到的一些API如下:
3.1、uv_tcp_init
初始化tcp对象
uv_tcp_tserver; uv_tcp_init(loop,&server);//初始化tcpserver对象
3.2、uv_ip4_addr
structsockaddr_inaddr; uv_ip4_addr("0.0.0.0",DEFAULT_PORT,&addr);
将给定的ip地址和端口转换成sockaddr_in结构体,原生编程的时候,设置ip和端口需要至少五行,用这个方法可以简化操作
3.3、uv_tcp_bind
等同于原生API的bind()方法
uv_tcp_bind(&server,(conststructsockaddr*)&addr,0);
uv_tcp_bind()的第三个参数flag一般是0,如果想使用IP6,可以使用UV_TCP_IPV6ONLY
enumuv_tcp_flags{ /*Usedwithuv_tcp_bind,whenanIPv6addressisused.*/ UV_TCP_IPV6ONLY=1 };
3.4、uv_listen
uv_listen((uv_stream_t*)&server,128,on_new_connection);
类似listen(),开始监听
第二个参数表明内核的排队数,最后指定有新连接时的回调函数
当有新的连接进来时,就会触发on_new_connection回调
3.5、uv_connection_cb
uv_connection_cb是uv_listen的回调函数,其声明如下:
typedefvoid(*uv_connection_cb)(uv_stream_t*server,intstatus);
server参数为服务器句柄
status表示状态,小于0表示新连接有误
3.6、uv_accept
新连接触发回调函数之后,按照一般流程,需要使用accept()方法获取客户端句柄,libuv中使用uv_accept(),其声明如下:
intuv_accept(uv_stream_t*server,uv_stream_t*client)
在调用之前,client参数必须被初始化
返回值<0表示有误
示例:
uv_tcp_t*client=(uv_tcp_t*)malloc(sizeof(uv_tcp_t));//为tcpclient申请资源 uv_tcp_init(loop,client);//初始化tcpclient句柄 if(uv_accept(server,(uv_stream_t*)client)==0){ do_some_thind(); }
3.7、uv_read_start
libuv中使用uv_read_start()方法从传入的stream中读取数据,声明如下:
intuv_read_start(uv_stream_t*stream,uv_alloc_cballoc_cb,uv_read_cbread_cb)
read_cb会被多次调用,直到数据读完,或者主动调用uv_read_stop()方法停止
该函数有两个回调函数,alloc_cb用于为新来的数据申请空间,申请的资源需要在read_cb中释放
这两个回调的声明如下:
typedefvoid(*uv_alloc_cb)(uv_handle_t*handle,size_tsuggested_size,uv_buf_t*buf);
typedefvoid(*uv_read_cb)(uv_stream_t*stream,ssize_tnread,constuv_buf_t*buf);
示例代码:
//负责为新来的消息申请空间 voidalloc_buffer(uv_handle_t*handle,size_tsuggested_size,uv_buf_t*buf){ buf->len=suggested_size; buf->base=static_cast(malloc(suggested_size)); } /** *@brief:负责处理新来的消息 *@param:client *@param:nread>0表示有数据就绪,nread<0表示异常,nread是有可能为0的,但是这并不是异常或者结束 */ voidread_cb(uv_stream_t*client,ssize_tnread,constuv_buf_t*buf){ do_somt_thing(); //释放之前申请的资源 if(buf->base!=NULL){ free(buf->base); } } uv_read_start((uv_stream_t*)client,alloc_buffer,read_cb);
3.8、uv_buf_t和uv_buf_init
uv_buf_t是libuv中的一种特殊的数据类型,和Redis的SDS有一点相似度,声明如下:
typedefstructuv_buf_t{ char*base; size_tlen; }uv_buf_t;
uv_buf_t可以使用uv_buf_init初始化
示例:
uv_buf_tuvBuf=uv_buf_init(buf->base,nread);//初始化write的uv_buf_t
3.9、uv_close
libuv中使用uv_close()方法关闭句柄,声明如下:
voiduv_close(uv_handle_t*handle,uv_close_cbclose_cb)
close_cb为关闭之后的回调,声明如下:
typedefvoid(*uv_close_cb)(uv_handle_t*handle);
代码示例:
voidon_close(uv_handle_t*handle){ if(handle!=NULL) free(handle); } ... uv_close((uv_handle_t*)client,on_close);
3.10、uv_write
libuv中使用uv_write()方法发送数据,声明如下:
intuv_write(uv_write_t*req,uv_stream_t*handle,constuv_buf_tbufs[], unsignedintnbufs,uv_write_cbcb);
req是需要传递给回调函数的数据,发送需要申请资源,并在回调函数中释放
handle是接受的客户端
bufs[]是一个uv_buf_t数组,可以一次添加多组数据,最终按照顺序发送
nbufs表示需要发送的数组元素个数,一般小于等于bufs的大小
3.11、uv_strerror
有些函数会有错误码,使用uv_strerror()方法获取错误码对应的描述
附录
源代码如下:
#include#include #include uv_loop_t*loop; #defineDEFAULT_PORT7000 //连接队列最大长度 #defineDEFAULT_BACKLOG128 //负责为新来的消息申请空间 voidalloc_buffer(uv_handle_t*handle,size_tsuggested_size,uv_buf_t*buf){ buf->len=suggested_size; buf->base=static_cast (malloc(suggested_size)); } voidon_close(uv_handle_t*handle){ if(handle!=NULL) free(handle); } voidecho_write(uv_write_t*req,intstatus){ if(status){ fprintf(stderr,"Writeerror%s\n",uv_strerror(status)); } free(req); } /** *@brief:负责处理新来的消息 *@param:client *@param:nread>0表示有数据就绪,nread<0表示异常,nread是有可能为0的,但是这并不是异常或者结束 *@author:sherlock */ voidread_cb(uv_stream_t*client,ssize_tnread,constuv_buf_t*buf){ if(nread>0){ //buf->base[nread]=0; fprintf(stdout,"recv:%s\n",buf->base); fflush(stdout); uv_write_t*req=(uv_write_t*)malloc(sizeof(uv_write_t)); uv_buf_tuvBuf=uv_buf_init(buf->base,nread);//初始化write的uv_buf_t //发送buffer数组,第四个参数表示数组大小 uv_write(req,client,&uvBuf,1,echo_write); return; }elseif(nread<0){ if(nread!=UV_EOF){ fprintf(stderr,"Readerror%s\n",uv_err_name(nread)); }else{ fprintf(stderr,"clientdisconnect\n"); } uv_close((uv_handle_t*)client,on_close); } //释放之前申请的资源 if(buf->base!=NULL){ free(buf->base); } } /** * *@param:serverlibuv的tcpserver对象 *@param:status状态,小于0表示新连接有误 *@author:sherlock */ voidon_new_connection(uv_stream_t*server,intstatus){ if(status<0){ fprintf(stderr,"Newconnectionerror%s\n",uv_strerror(status)); return; } uv_tcp_t*client=(uv_tcp_t*)malloc(sizeof(uv_tcp_t));//为tcpclient申请资源 uv_tcp_init(loop,client);//初始化tcpclient句柄 //判断accept是否成功 if(uv_accept(server,(uv_stream_t*)client)==0){ //从传入的stream中读取数据,read_cb会被多次调用,直到数据读完,或者主动调用uv_read_stop方法停止 uv_read_start((uv_stream_t*)client,alloc_buffer,read_cb); }else{ uv_close((uv_handle_t*)client,NULL); } } intmain(intargc,char**argv){ loop=uv_default_loop(); uv_tcp_tserver; uv_tcp_init(loop,&server);//初始化tcpserver对象 structsockaddr_inaddr; uv_ip4_addr("0.0.0.0",DEFAULT_PORT,&addr);//将ip和port数据填充到sockaddr_in结构体中 uv_tcp_bind(&server,(conststructsockaddr*)&addr,0);//bind intr=uv_listen((uv_stream_t*)&server,DEFAULT_BACKLOG,on_new_connection);//listen if(r){ fprintf(stderr,"Listenerror%s\n",uv_strerror(r)); return1; } returnuv_run(loop,UV_RUN_DEFAULT); }
以上就是c++如何在libuv中实现tcp服务器的详细内容,更多关于libuv中实现tcp服务器的资料请关注毛票票其它相关文章!