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

发布于:2025-04-21 ⋅ 阅读:(102) ⋅ 点赞:(0)

本文是上一期文章的延伸


上一期的文章的位置链接: 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里,这样你的服务器的功能就会更强壮。


网站公告

今日签到

点亮在社区的每一天
去签到