【远程通信系统】服务端初始化

本文介绍了如何使用libevent构建一个实时聊天服务器,包括初始化服务器、事件集合、线程池、数据库连接(MySQL)以及处理在线用户和群组信息的操作,强调了libevent的事件监听和回调功能在服务器架构中的应用。
摘要由CSDN通过智能技术生成

服务器架构:libevent + 线程池
在这里插入图片描述
数据库:MySQL
有两张表:chat_user和chat_group,分别保存用户信息和群信息

在这里插入图片描述

在这里插入图片描述
在线用户和群的保存:

struct User
{
        std::string name;//账号(用户名
        struct bufferevent* bev;//客户端对应的事件
};

//保存在线用户信息
std::list<User> *online_user;
//保存所有群的信息
std::map<std::string,std::list<std::string>> *group_info;

在这里插入图片描述

ChatServer的初始化

在这里插入图片描述

ChatServer::ChatServer()
{
        //初始化事件集合
        this->base =event_base_new();

        //初始化数据库对象
        db = new DataBase();

        //初始化数据库表(chat_user,chat_group)
        if(!db->database_init_table())
        {
                std::cout<<"init table failure"<<std::endl;
                exit(1);
        }

        //初始化数据结构对象
        info = new ChatInfo();

        //初始化群信息:把群信息从数据库里读出来,放入map
        server_update_group_info();
        
		//初始化线程池
        thread_num = 3;
        cur_thread = 0;
        pool = new ChatThread[thread_num];

        for(int i=0;i<thread_num;i++)
        {
                pool[i].start(info,db);
        }
}

初始化事件集合,放入监听事件

ChatServer::ChatServer()
{
        this->base =event_base_new();
}

ChatServer::listen()

下载libevent源码并查看:
在这里插入图片描述
进入/xx/usr/share/doc/libevent-dev/examples,可查看libevent使用示例。
在这里插入图片描述
查看hello-world.c
在这里插入图片描述
去头文件查看evconnlistener_new_bind的用法。
头文件都在/usr/include/中。
在这里插入图片描述
进入listener.h
在这里插入图片描述
在这里插入图片描述

struct evconnlistener *evconnlistener_new_bind(
    struct event_base *base,//事件集合
    evconnlistener_cb cb,//一旦有客户端连接,就会触发回调函数
    void *ptr,
    unsigned flags,
    int backlog,//监听队列里的容量
    const struct sockaddr *sa,
    int socklen
);

unsigned flags:说明里flags Any number of LEV_OPT_* flags
于是搜索LEV_OPT_,得如下结果
在这里插入图片描述
写代码:

//创建监听对象
void ChatServer::listen(const char* ip, int port)
{
        struct sockaddr_in server_info;
        memset(&server_info,0,sizeof(server_info));//清空
        server_info.sin_family = AF_INET;
        server_info.sin_addr.s_addr = inet_addr(ip);
        server_info.sin_port = htons(port);

        struct evconnlistener *listener=evconnlistener_new_bind(base,
                        listener_cb,this,LEV_OPT_CLOSE_ON_FREE,5,
                        (struct sockaddr*)&server_info,sizeof(server_info));
        if(listener == NULL){
                std::cout<<"evconnlistener_new_bind error"<<std::endl;
                exit(1);
        }
        //监听集合
        event_base_dispatch(base);//死循环,如果集合没有事件,退出
        //释放对象
        evconnlistener_free(listener);
        event_base_free(base);
}

查看回调函数listenner_cb的声明:
在这里插入图片描述
把光标放在这,按下shift+8(也就是*),然后next
在这里插入图片描述

listener_cb

在回调函数中打印客户端的ip和端口,方便调试。
关于listener_cb的this参数的说明

struct evconnlistener *listener=evconnlistener_new_bind(base,
       listener_cb,   this   ,LEV_OPT_CLOSE_ON_FREE,5,
       (struct sockaddr*)&server_info,sizeof(server_info));

因为listener_cb是个静态函数,而静态函数只能通过对象来调用普通成员函数,所以listener_cb不能直接调用server_alloc_enevt()。
所以传入this参数,通过this来调用。(因为参数是void*,所以记得先把this强转回来)

//回调函数,有客户端发起连接,会触发该函数
void ChatServer::listener_cb(struct evconnlistener *listener, evutil_socket_t fd,
                struct sockaddr *c, int socklen, void *arg)
{
        struct sockaddr_in *client_info = (struct sockaddr_in*)c;
        std::cout<<"[connection]";
        std::cout<<" client ip : " <<inet_ntoa(client_info->sin_addr);
        std::cout<<" port : " << client_info->sin_port<<std::endl;

        //创建事件,放入线程池
        ser->server_alloc_event(fd);
}

初始化数据库对象

查找头文件
在这里插入图片描述
包含头文件要把include后面都写上

#include<mysql/mysql.h>
class DataBase
{
private:
        MYSQL *mysql;
        std::mutex _mutex;
public:
        DataBase();
        ~DataBase();
        bool database_connect();
        void database_disconnect();
        bool database_init_table();
}

构造函数不需要做什么。

DataBase::DataBase()
{
}

初始化数据库表

是否要一直打开数据库——取决于项目对数据库的使用频繁程度。
远程通信系统对数据库使用不频繁,所以不用时将数据库关闭。

连接数据库:

在这里插入图片描述
在命令行敲的所有命令都可以通过mysql_query()来执行
在这里插入图片描述
mysql_query()如果查询成功,返回0。如果出现错误,返回非0值。

建议先在在mysql中测试语句的可执行性,再写入代码:

mysql -u root -p登录mysql
在这里插入图片描述

set names utf8;

在这里插入图片描述
显示query 成功。

bool DataBase::database_connect()
{
        //初始化数据库句柄
        mysql = mysql_init(NULL);//分配堆内存

        //连接数据库
        mysql = mysql_real_connect(mysql, "localhost","root","root",
                        "chat_database",0,NULL,0);
        if(mysql==NULL)
        {
                std::cout<<"mysql_real_connect error"<<std::endl;
                return false;
        }

        //设置编码格式   (防止中文乱码)
        if(mysql_query(mysql, "set names utf8;")!=0)
        {
                std::cout<<"set names utf8 error"<<std::endl;
                return false;
        }

        return true;
}

断开数据库:

直接调用mysql_close()函数

void DataBase::database_disconnect()
{
        mysql_close(mysql);
}

初始化数据库表:

创建chat_group的sql语句:

create table if not exists chat_group(
groupname varchar(128),
groupowner varchar(128),
groupmember varchar(4096)
)charset utf8;

创建chat_user的sql语句

create table if not exists chat_user(
username varchar(128),
password varchar(128),
friendlist varchar(4096),
grouplist varchar(4096)
)charset utf8;

初始化数据库表:

bool DataBase::database_init_table()
{
        database_connect();

        const char* g="create table if not exists chat_group(groupname varchar(128),groupowner varchar(128),groupmember varchar(4096))charset utf8;";
        if(mysql_query(mysql,g)!=0)
        {
                return false;
        }

        const char* u="create table if not exists chat_user(username varchar(128),password varchar(128),friendlist varchar(4096),grouplist varchar(4096))charset utf8;";

        if(mysql_query(mysql,u)!=0)
        {
                return false;
        }

        database_disconnect();

        return true;
}

初始化数据结构对象

struct User
{
        std::string name;//账号(用户名
        struct bufferevent* bev;//客户端对应的事件
};

class ChatInfo
{
private:
        //保存在线用户信息
        std::list<User> *online_user;
        //保存所有群的信息
        std::map<std::string,std::list<std::string>> *group_info;
        //访问在线用户的锁
        std::mutex list_mutex;
        //访问群信息的锁
        std::mutex map_mutex;
public:
        ChatInfo();
        ~ChatInfo();
        void list_update_group(std::string* ,int);
        void list_print_group();
};

ChatInfo::ChatInfo()
{
        online_user = new std::list<User>;
        group_info = new std::map<std::string,std::list<std::string>>;
}

初始化群信息

server_update_group_info

逻辑:从数据库获取群信息,然后写入list。
所以获取群信息的函数是属于DataBase的,写入list的函数时属于ChatInfo的

void ChatServer::server_update_group_info()
{
        //连接数据库
        if(!db->database_connect())
        {
                exit(1);
        }

        std::string groupinfo[1024];//最多1024个群
        int num = db->database_get_group_info(groupinfo);
        std::cout<<"group num : "<<num<<std::endl;

        //断开数据库
        db->database_disconnect();

        info->list_update_group(groupinfo,num);

        //info->list_print_group();//测试用
}

database_get_group_info

在这里插入图片描述
在这里插入图片描述
mysql_store_result()用于从服务器获取结果集并将其存储在客户端中以供检索和处理,返回值是MYSQL_RES*
在这里插入图片描述
mysql_fetch_row()用于逐行获取查询结果集中的数据,返回值是MYSQL_ROW
在这里插入图片描述
MYSQL_ROW是个数组
在这里插入图片描述
用竖线|间隔每个数据。

int DataBase::database_get_group_info(std::string *g)
{
        if(mysql_query(mysql,"select * from chat_group;")!=0)
        {
                std::cout<<"select error"<<std::endl;
                return -1;
        }
        MYSQL_RES *res = mysql_store_result(mysql);
        if(res==NULL)
        {
                std::cout<<"store result error"<<std::endl;
                return -1;
        }
        MYSQL_ROW r;
        int idx=0;
        while(r = mysql_fetch_row(res))
        {
                g[idx] += r[0];
                g[idx] +='|';
                g[idx] += r[2];
                //std::cout<<g[idx]<<std::endl;
                idx++;
        }

        mysql_free_result(res);
        return idx;
}

list_update_group()

按database_get_group_info中的格式,查找竖线|,已得到每个数据。

void ChatInfo::list_update_group(std::string* g, int size)
{
        int idx=0,start =0;
        std::string groupname,membername;
        std::list<std::string> l;

        for(int i=0;i<size;i++)
        {
                idx = g[i].find('|');
                groupname = g[i].substr(0,idx);
                //std::cout<<groupname<<std::endl;

                start = idx +1;
                while(1)//idx查找竖线,找不到是-1
                {
                        idx = g[i].find('|',idx+1); //从idx开始查找
                        if(idx==-1)break;
                        membername = g[i].substr(start,idx-start);
                        l.push_back(membername);
                        start = idx +1;
                }

                membername = g[i].substr(start,idx - start);
                l.push_back(membername);

                this->group_info->insert(std::pair<std::string,std::list<std::string>>(groupname,l));

                l.clear();
        }
}            

初始化线程池

class ChatThread
{
private:
        std::thread *_thread;
        std::thread::id _id;
        struct event_base *base;
        ChatInfo *info;
        DataBase *db;
public:
        ChatThread();
        ~ChatThread();
        void start(ChatInfo *,DataBase *);
        void run();
        static void worker(ChatThread*);

};

ChatThread()

ChatThread::ChatThread()
{
        _thread = new std::thread(worker,this);
        _id = _thread->get_id();//get_id()时线程标准库里的

        base = event_base_new();
}

回调函数worker

因为静态成员函数worker只能访问静态成员变量,不能访问普通成员变量,却可以通过对象调用普通成员函数,所以再写一个普通成员函数run,同时在构造时传入this参数以调用run

void ChatThread::worker(ChatThread *t)
{
        t->run();
}

run()

因为event_base_dispatch()当集合中无事件时自动退出,所以随便放一个事件进集合。
比如放一个定时器事件,查找示例:
在这里插入图片描述
在这里插入图片描述
查看main函数中的使用示例

在这里插入图片描述

写代码:

void ChatThread::run()
{
        //集合中放入一个定时器事件
        struct event timeout;
        struct timeval tv;

        //将事件与集合绑定
        //base是构造函数初始化的base
        //EV_PERSIST表示定时器永远都有用
        event_assign(&timeout, base, -1, EV_PERSIST, timeout_cb,this);

        evutil_timerclear(&tv);
        tv.tv_sec=3;
        event_add(&timeout,&tv);

        std::cout<<"--- thread "<<_id<<" start working ---"<<std::endl;

        event_base_dispatch(base);//死循环,当集合中没有事件的时候退出

        event_base_free(base);

}
void ChatThread::timeout_cb(evutil_socket_t fd, short event, void *arg)
{
        ChatThread *t=(ChatThread *)arg;
        //std::cout<<"-- thread "<<t->thread_get_id()<<" is listening --"<<std::endl;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值