云计算百科
云计算领域专业知识百科平台

libevent服务器与qt开发第二期(附带源码)

本文是上一期文章的延伸

  • 源码
  • 本文核心点

上一期的文章的位置链接:
link
本期要做的是通过libevent设计一个服务器,该服务器遇到有客户端访问的时候,会用一个qt的listview控件来显示该客户端的ip和端口号,客户端断开后listview也会删除该客户端的ip和端口号,演示视频如下。

20250416_160008

源码

在这里插入图片描述 libeventuse.h

class globalSignal : public QObject {
Q_OBJECT
public:
static void sendSignal(QString ip) {
if (m_instance) emit m_instance->singnalIP(ip);
}
static globalSignal *m_instance;
~globalSignal();
signals:
void singnalIP(QString ip);

private:
explicit globalSignal(QObject *parent = nullptr) : QObject(parent) {}

};

#include <QThread>
class WorkThread : public QThread {
Q_OBJECT // 必须添加!!!为了能使用信号
public:
explicit WorkThread(QObject *parent = nullptr);
protected:
void run() override; // 虚函数声明
signals:
void WorkThread_signal(QString ip);//自定义的信号
void server_finish();
//static void semdSignal(QString ip);
};

libeventuse.cpp

#include "libeventuse.h"
#include"qdebug.h"

#include <event2/listener.h>
#include <event2/bufferevent.h>
#include <event2/util.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>

// 客户端信息结构体
struct ClientInfo {
evutil_socket_t fd; // 文件描述符
std::string ip; // 客户端IP
int port; // 客户端端口号
struct bufferevent *bev; // 关联的bufferevent
};

// 事件回调(处理断开和错误)
static void event_cb(struct bufferevent *bev, short events, void *arg) {
ClientInfo *client = static_cast<ClientInfo*>(arg);

if (events & BEV_EVENT_EOF) {
// 正常断开

char buf[300]={0};
sprintf(buf,"disconect:%s:%d",client->ip.c_str(),client->port);
globalSignal::sendSignal(buf);
} else if (events & BEV_EVENT_ERROR) {
// 错误断开
char buf[300]={0};
sprintf(buf,"error:%s:%d error:%s",client->ip.c_str(),client->port,evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR()));
globalSignal::sendSignal(buf);

}

// 释放资源
bufferevent_free(bev);
delete client;
}
// 新连接回调
static void accept_conn_cb(struct evconnlistener *listener, evutil_socket_t fd,
struct sockaddr *addr, int socklen, void *ctx) {
char ip[INET6_ADDRSTRLEN];
int port = 0;

// 解析IP和端口
if (addr->sa_family == AF_INET) { // IPv4
struct sockaddr_in *sin = (struct sockaddr_in *)addr;
inet_ntop(AF_INET, &sin->sin_addr, ip, sizeof(ip));
port = ntohs(sin->sin_port);
} else if (addr->sa_family == AF_INET6) { // IPv6
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)addr;
inet_ntop(AF_INET6, &sin6->sin6_addr, ip, sizeof(ip));
port = ntohs(sin6->sin6_port);
} else {
fprintf(stderr, "Unknown address family\\n");
return;
}

std::string datastr="connect:";
datastr +=ip;
datastr+=":"+std::to_string(port);
globalSignal::sendSignal(datastr.c_str());
// qDebug("New connection from: %s:%d\\n", ip, port);

// 创建bufferevent并设置回调
struct event_base *base = evconnlistener_get_base(listener);
struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);

// 保存客户端信息
ClientInfo *client = new ClientInfo;
client->fd = fd;
client->ip = ip;
client->port = port;
client->bev = bev;

// 设置事件回调(重点关注断开事件)
bufferevent_setcb(bev, NULL, NULL, event_cb, client);
bufferevent_enable(bev, EV_READ | EV_WRITE);
}

int main3() {
struct event_base *base = event_base_new();
if (!base) {
fprintf(stderr, "Could not initialize event base\\n");
return 1;
}
//qDebug()<<"=========0";
// 绑定地址(监听0.0.0.0:8080)
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(8080);
sin.sin_addr.s_addr = htonl(INADDR_ANY);

// 创建监听器
struct evconnlistener *listener = evconnlistener_new_bind(
base,
accept_conn_cb,
NULL,
LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE,
1,
(struct sockaddr*)&sin,
sizeof(sin));

if (!listener) {
fprintf(stderr, "Could not create listener: %s\\n",
evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR()));
event_base_free(base);
return 1;
}

// printf("Server listening on 0.0.0.0:8080\\n");
event_base_dispatch(base); // 进入事件循环

// 清理资源
evconnlistener_free(listener);
event_base_free(base);
return 0;
}

WorkThread::WorkThread(QObject *parent) : QThread(parent) {}

globalSignal* globalSignal::m_instance = new globalSignal;

globalSignal::~globalSignal()
{
if(m_instance)
{
delete m_instance;
}
}

void WorkThread::run() { // 必须实现 run()
main3();
emit this->server_finish();
}

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QStringListModel>
#include"libeventuse.h"
namespace Ui {
class Form;
}

class Form : public QWidget
{
Q_OBJECT

public:
explicit Form(QWidget *parent = 0);
~Form();
QStringListModel* m_pmodel=nullptr;
Ui::Form *ui;
private slots:
void on_pushButton_clicked();
signals:
void back();
private:

};

namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT

public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
Form* login_win=nullptr;
WorkThread*workthread=nullptr;
private slots:
void on_pushButton_clicked();
void on_pushButton_2_clicked();

private:
Ui::MainWindow *ui;
};

#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "ui_form.h"
#include"QString"
#include"qdebug.h"
#include"qmessagebox.h"
#include"libeventuse.h"

#include <QMutex>
#include <QTime>
#include <QWidget>

MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{

ui->setupUi(this);
login_win=new Form;
login_win->hide();//登录窗口打开

workthread=new WorkThread;
workthread->start();

connect(globalSignal::m_instance,&globalSignal::singnalIP,login_win,[=](QString ip){

if(ip.toStdString().find("connect")!=1)//处理连接部分
{
std::string newstr=ip.toStdString().substr(8);
int row=login_win->m_pmodel->rowCount();
login_win->m_pmodel->insertRow(row);
QModelIndex index=login_win->m_pmodel->index(row);
login_win->m_pmodel->setData(index,newstr.c_str());
login_win->ui->listView->setCurrentIndex(index);
}
else if(ip.toStdString().find("disconect")!=1)//处理断开部分
{
std::string newstr=ip.toStdString().substr(10);
int row=login_win->m_pmodel->rowCount();
for(int i=0;i<row;i++)
{
QModelIndex index=login_win->m_pmodel->index(i);
QVariant value = index.data(Qt::DisplayRole);
if(value.toString().toStdString().find(newstr)!=1)
{
login_win->m_pmodel->removeRow(i);
break;
}
}

}
else//处理异常部分
{
QMessageBox::information(this, tr("服务器异常"), tr(ip.toStdString().c_str()));

}

});

connect(this->workthread,&WorkThread::server_finish,[=](){
qDebug()<<"服务器关闭";
});
connect(this->workthread,&WorkThread::finished,this->workthread,&QObject::deleteLater);

connect(this->login_win,&Form::back,[=](){//槽函数
this->show();//返回登录界面
this->login_win->hide();//主执行界面关闭
});

//只要客户端连接到服务器就会发送hello word

}

MainWindow::~MainWindow()
{
delete login_win;
delete ui;
}

Form::Form(QWidget *parent) :
QWidget(parent),
ui(new Ui::Form)
{
ui->setupUi(this);
m_pmodel=new QStringListModel(ui->listView);
ui->listView->setModel(m_pmodel);

// m_pmodel->setStringList(QStringList()
// << "第一行"
// << "第二行"
// << "第三行");

}

Form::~Form()
{

delete ui;
}

void MainWindow::on_pushButton_clicked()
{
QString RootName=ui->rootName->text();
QString Password=ui->password->text();
if(RootName.size()==0)
{
QMessageBox::information(this, tr("提示"), tr("用户名没有输入"));
return ;
}
else if(RootName.size()==0)
{
QMessageBox::information(this, tr("提示"), tr("密码没有输入"));
}

//if(RootName=="i"&&Password=="1")//验证用户和密码
{
qDebug()<<RootName<<Password;
login_win->show();//主执行界面打开
this->hide();//登录界面关闭
}
// else
// {
// QMessageBox::information(this, tr("提示"), tr("用户或密码输入错误"));
// }

}

void Form::on_pushButton_clicked()
{
// ui->listView->in
//emit this->back();//发送信号
int row=m_pmodel->rowCount();
m_pmodel->insertRow(row);
QModelIndex index=m_pmodel->index(row);
m_pmodel->setData(index,"caonima");
ui->listView->setCurrentIndex(index);

}

void MainWindow::on_pushButton_2_clicked()
{

}

本文核心点

1.qt的核心点信号 该信号和系统的信号类似但是该信号只能对应的类构建后才能发送 所以要写一个全局的信号这样就能在需要的地方发送 class globalSignal实现在libeventuse.h 使用静态变量 static globalSignal *m_instance让所有调用的人用同一个类 使用静态函数static void sendSignal(QString ip)发送信号 调用方式globalSignal::sendSignal(buf); 2.qt如何知道信号来了之后如何处理该信号 qt的核心槽函数connect(发送者的类,发送者对应的信号,接收信号的对象,处理函数(槽函数))接下来我用一个例子展示你也可以看源码 globalSignal::m_instance 全局的信号类 &globalSignal::singnalIP 对应的信号 login_win 对应的窗口,这个窗口在本文就是listview控件 [=](QString ip) lamda表达式负责写处理逻辑,这个是最简单的实现,【=】这样写的意思就是能拿login_win 的所有参数 connect(globalSignal::m_instance,&globalSignal::singnalIP,login_win,[=](QString ip) 3.libevent服务器,libevent也是事件类模式,只要写好对应事件和对应的处理函数就能实现对应效果和qt的槽和信号很类似不过是复杂一点

基本流程的都是1.先初始化一个类似于调度器函数 struct event_base *base = event_base_new(); 2.创建监听器你可以理解为槽函数 struct evconnlistener *listener = evconnlistener_new_bind 3.启动事件循环 event_base_dispatch(base); 4.清理资源 evconnlistener_free(listener); event_base_free(base); 流程都是固定的大家都按着流程来写对应的处理逻辑即可即accept_conn_cb就是evconnlistener_new_bind绑定的处理函数。你也可以优化我写的代码,添加更多的事件到调度器base里,这样你的服务器的功能就会更强壮。

赞(0)
未经允许不得转载:网硕互联帮助中心 » libevent服务器与qt开发第二期(附带源码)
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!