当前位置:C++技术网 > 资讯 > libuv轻松实现维持服务器端300+连接和执行客户端命令控制

libuv轻松实现维持服务器端300+连接和执行客户端命令控制

更新时间:2017-08-13 13:35:09浏览次数:1+次

        当你接受到这样一个需求,你如何来实现:

    1、tcp服务端,响应和保持300+个tcp客户端连接,维护这些连接的状态;
2、当客户端有数据时,及时收下来;
3、定时向客户端发报文;
4、当有控制命令时,向指定客户端发报文;
    我们来整理一下需求,整理如下:

    1.实现一个TCP服务端,要保存所有TCP客户端连接,不让其断线。实现方法就可以是定时向客户端发送报文,俗称心跳报文。

    2.如果收到客户端发过来的数据,要接受保存下来。

    3.心跳报文的实现,就使用定时器来实现。

    4.TCP服务端不仅要接受数据,还要对数据进行解析,如果发过来的是控制命令,我们需要执行命令,并返回执行结果。

        这样细化之后,我们也就更好的使用编程的思路来转化。

        下面是实现的效果图:

    

        TCP服务端相必你应该知道如何实现。但是如果从最基本的socket去写,相必也是有点费劲的。所以我们这里就推荐用libuv来实现了。libuv是跨平台的网络通信库,使用很简单,核心机制就是处处回调。明白这个核心机制,后续的使用学习会有很大的帮助。

        这里我早已提供了libuv的封装类,见:《libuv服务器端包装类源代码分享》。

        然后我们只需要写一个使用这个类的代码就行了。我们就用控制台程序来实现。

        先将这个包装类的两个文件创建好,分别是tcpServer.h和tcpServer.cpp。然后再创建一个main函数所在的文件,如m.cpp。然后在m.cpp文件里这样写:

    

#include <map>
#include <stdlib.h>
#include <stdio.h>
#include "tcpServer.h" 
#include <vector>

using namespace std;

//服务器对象 
CTcpServer srv;
typedef struct
{
    char * pdata;
    int data_size;
}DATA;
vector<int> g_client_list;
vector<DATA> g_data_list;
bool is_stop=false;
void recv_cb(int client_id, const char* buf, int buf_size) 
{ 
    //处理接受消息
    if (buf[0]==0)//当数据最开头是0,显示所有接受的数据
    {
        printf("客户端%d:执行查看所有客户端发过来的消息。\n",client_id);
        vector<DATA>::iterator it=g_data_list.begin();
        for (int i=0;it!=g_data_list.end();++it,i++)
        {
            char* pdata= new char[it->data_size+2];
            memset(pdata,0,it->data_size+2);
            memcpy(pdata,it->pdata,it->data_size);
            pdata[it->data_size+1]='\n';
            pdata[it->data_size]='\r';
            srv.send(client_id, (const char*)pdata, it->data_size+2);
        }
        return;
    }
    if (buf[0]==1)//当数据最开头是1,清空所有接受的数据
    {
        printf("客户端%d:执行清空所有客户端发过来的消息。\n",client_id);
        vector<DATA>::iterator it=g_data_list.begin();
        for (int i=0;it!=g_data_list.end();++it,i++)
        {
            delete [] it->pdata;
        }
        g_data_list.clear();
        srv.send(client_id, (const char*)"clear finished\r\n", sizeof("clear finished\r\n"));
        return;
    }
    if (buf[0]==2)//当数据最开头是2,要求服务器停止定期向客户端发送消息
    {
        printf("客户端%d:执行停止服务器定期向所有客户端发送消息。\n",client_id);
        is_stop=true;
        srv.send(client_id, (const char*)"stop finished\r\n", sizeof("stop finished\r\n"));
        return;
    }
    if (buf[0]==3)//当数据最开头是3,要求服务器开始定期向客户端发送消息
    {
        printf("客户端%d:执行开启服务器定期向所有客户端发送消息。\n",client_id);
        is_stop=false;
        srv.send(client_id, (const char*)"start finished\r\n", sizeof("start finished\r\n"));
        return;
    }
    char* pdata = new char[buf_size];
    memset(pdata,0,buf_size);
    memcpy(pdata,buf,buf_size);
    DATA data;
    data.pdata = pdata;
    data.data_size = buf_size;
    g_data_list.push_back(data);
    return; 
} 
void new_conn_cb(int client_id) 
{ 
    srv.setrecvcb(client_id, recv_cb);
    vector<int>::iterator it=g_client_list.begin();
    bool is_exsit=false;
    for (;it!=g_client_list.end();++it)
    {
        if(*it==client_id)
        {
            is_exsit=true;
            break;
        }
    }
    if (!is_exsit)
    {
        g_client_list.push_back(client_id);
    }
} 
void callback_timer(uv_timer_t *handle) 
{  
    //定时发送消息
    if (is_stop)return;
    vector<int>::iterator it=g_client_list.begin();
    for (;it!=g_client_list.end();++it)
    {
        //向所有客户端定期发送消息,维持客户端连接
        int ret = srv.send(*it, (const char*)"is_alive\r\n", sizeof("is_alive\r\n"));
        if (ret)
        {
            //发送失败,移除旧连接,客户端重新链接后会自动加入到客户端队列
            g_client_list.erase(it);
            it=g_client_list.begin();
        }
    }
} 
int main(int argc, char **argv)
{
    //设置连接客户端时处理的回调函数 
    srv.setnewconnectcb(new_conn_cb); 
    uv_loop_t *loop = uv_default_loop();  
    uv_timer_t timer_req;  
    uv_timer_init(loop, &timer_req);  
    uv_timer_start(&timer_req, callback_timer, 1000, 1000);  

    //启动服务器 
    if(!srv.Start("0.0.0.0", 6000))
    {
        printf("Start Server error:%s\n",srv.GetLastErrMsg());
    }
    printf("server return on main.\n");
}
       代码里有几个主要的函数:
main:主函数,在主函数里,我们设置一下客户端新连接执行的回调函数。然后创建一个定时器。然后启动TCP服务器监听。
new_conn_cb:新客户端连接调用的回调函数。在这个函数里面,我们将新连接的客户端id存入一个列表中,使用的是vector来盛放,因为vector使用简单。同时在这个函数里,我们需要设置接受客户端数据的回调函数。
recv_cb:接受客户端发送过来的数据的回调函数。在这个函数里,我们接受客户端数据并存放起来,也是存入vector容器中。我们这里是动态创建内存,存到容器的是指针,所以我们额外定义了一个结构体,增加了一个记录数据大小的成员。然后将这个结构体变量存入vecotr中。另外我们需要解析客户端发过来的数据是真正的数据还是命令。我这里就简单定义了第一个字节,分为4个值,0则是查看所有存储的客户端发过来的数据,1则是清空所有存储的客户端发过来的数据,2则是停止服务器定时发消息给所有客户端,3则是恢复服务器定时发送数据给所有客户端(心跳报文,维持连接作用)。而具体命令的执行的动作后,再将结果发送回给发命令的客户端。
callback_timer:定时发送消息函数。这个是服务器维持与客户端连接的函数。尽管是TCP长连接,但是如果长时间不通信,就系统很可能会让其断掉,以免浪费网络资源。定时发送报文,也就是维持连接的活跃状态,这也是心跳报文的来历。这是应用层的实现。

    至于要不要发送心跳报文,我们是用2和3两个命令来控制的。控制的对象就是要给全局变量,是不是很简单呀。本来就没有那么复杂。细节的代码实现,就不在这里说了。如果有不清楚的,就自己去查具体的代码的作用咯,毕竟这些代码并不复杂,就不解释了。
    而libuv的回调机制再这里有很直接的表现,那就是main->new_conn_cb->recv_cb,main->callback_timer。也就是这样一个回调函数链,让程序流程串联起来了。你只需要在回调函数里写你的代码就行了。
    我们这里存储客户端列表只是简单存储一个客户端的ID,你可以在这个基础上增加自己更多的信息,使用结构体来存放就行了。接受数据的存储以及命令的解释和执行,在这个基本模型下,你也可以自由发挥了。
    还是那句话,“师父引进门,修行在个人”。如果这个基本的模型不消化好,那就不要想着写更复杂的逻辑。所以,我建议你看完代码后,自己按照开始的需求,重头到尾自己写一遍实现。不要看着简单,实际上我也写了一段时间,才确保完全调试通过没有问题。只有动手了才知道问题在哪。
    最后要说的是,项目需要设置一下,要添加一下libuv头文件包含目录,即放在项目里的include目录。还要指定忽略LIBCMT.lib。另外libuv.lib文件要放在项目文件夹里,在tcpServer.h里自动导入了,不需要去项目里配置,但是文件不要弄丢了。至于libuv的include头文件,下载了libuv源码后,你自然就有了。