基于TCP、QT制作的小型聊天系统

该博客介绍了使用QT和TCP协议开发的一款小型聊天系统,具备好友列表刷新、单聊(文字和图片)、消息时间记录、虚拟键盘、实时时钟、手写画板功能。手写画板支持痕迹粗细和颜色调整,并能截图保存。聊天窗口大小可调,但目前存在无法保存图片消息记录的问题。客户端登录界面限制错误密码输入次数,登录后能自动弹出和隐藏虚拟键盘。代码已上传至CSDN供下载学习。

实现功能:

  • 好友列表刷新功能
  • 单聊(发送消息、发送图片)
  • 接收到的消息均有系统时间记录
  • 虚拟键盘(利用事件过滤器,设置在点击编辑框时虚拟键盘才会自动弹出)
  • 实时时钟显示(利用定时器,持续获取系统时间)
  • 手写画板(利用了QT中的事件、能更改痕迹的粗细大小、更改笔的颜色、截图保存此绘画)
  • 收消息框、写消息框、好友列表框均可手动调整大小(QT中设置这些输入框布局  分列式垂直水平布局)
  • 截图 、消息记录

尚存BUG:

        保存消息记录时还不能保存图片数据。

Client  客户端登录界面代码

  • 如果密码输入三次错误、密码输入栏会自动锁死
  • 因为初学时为了理解学习、此界面没有使用UI设计师设计、而是采用纯代码编辑
  • 默认自动弹出虚拟键盘、如果点击输入栏以外的位置会隐藏虚拟键盘

 

#include "longinwindow.h"
#include "ui_longinwindow.h"
#include <QDebug>

LonginWindow::LonginWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::LonginWindow)
{
    ui->setupUi(this);


    //this->setStyleSheet("background-color: rgb(255, 255, 255);");
    this->setStyleSheet("LonginWindow{border-image: url(:/new/prefix1/1.jpg);}");

    //this->show();

    usrline=new QLineEdit(this);
    //指定输入框显示的位置
    usrline->setGeometry(100,20,250,30);
    usrline->show();

    //创建一个输入框框对象
    passline = new QLineEdit (this);
    //隐藏输入的密码
    passline->setEchoMode(QLineEdit::Password);
    //指定输入框显示的位置
    passline->setGeometry(100,80,250,30);
    passline->show();


    usrline->installEventFilter(this);      //给用户输入栏添加事件过滤器
    passline->installEventFilter(this);

    //创建一个标签表示用户名
    usrlb =new QLabel(this);
    //QPalette pa;
    //定义字体对象
    QFont myfont;
    myfont.setPixelSize(20);            //设置字体大小
    myfont.setFamily("KaiTi");          //将字体设置为楷体
    //myfont.setBold(75);       //也能设置字体加粗,只是会报错截断
    myfont.setWeight(75);       //设置字体加粗

    usrlb->setStyleSheet("color:red;");          //将字体设置为红色
    //color: rgb(255, 0, 0);  //设置字体颜色
    usrlb->setFont(myfont);
    usrlb->setText("用户:");
    usrlb->setGeometry(10,20,50,30);
    usrlb->show();

    //创建一个标签表示密码

    passlb = new QLabel("密码: ",this);
    myfont2.setPixelSize(20);
    passlb->setStyleSheet("color:red;");          //将字体设置为红色
    passlb->setFont(myfont2);
    passlb->setGeometry(10,80,50,30);            //设置标签位置
    passlb->show();

    //登陆按钮

    lgbt = new QPushButton(QIcon(":/new/prefix1/2.jpg"),"登陆",this);
    lgbt->setGeometry(140,150,70,30);
    lgbt->show();

    //注册按钮

    resbt = new QPushButton(QIcon(":/new/prefix1/3.jpg"),"注册",this);
    resbt->setGeometry(270,150,70,30);
    resbt->show();

    connect(lgbt, SIGNAL(clicked(bool)), this ,SLOT(LonginBtSlot()));
}

LonginWindow::~LonginWindow()
{
    delete ui;
    delete usrline;
    delete passline;
    delete usrlb;
    //QFont myfont;
    //QFont myfont2;
    delete passlb;
    delete lgbt;
    delete resbt;
}

bool LonginWindow::eventFilter(QObject *watched, QEvent *event)
{
    if (event->type() == QEvent::FocusIn)
    {
          ui->widget->show();
    }
    else if(event->type() == QEvent::KeyPress)
    {
        QKeyEvent *kE = (QKeyEvent *)event;
        if (kE->key() == Qt::Key_Enter)
        {
            ui->widget->hide();
        }

    }
    else if(event->type() ==QEvent::FocusOut)
    {
          ui->widget->hide();
    }
    return QMainWindow::eventFilter(watched,event);
}

void LonginWindow::LonginBtSlot()
{
    qDebug()<<"我已经点击了登录按钮: ";
    static int num=0;               //记住输入错误的次数
    QString usrname=usrline->text();
    QString pas=passline->text();
    if(usrname=="xgh"&&pas=="123")
    {
        qDebug()<<"我已登录 ";
        this->hide();
        clientwin *clientw =new clientwin(this);
        clientw->show();
    }
    else
    {
        num++;
    }
    if(num>3)
    {
         passline->setEnabled(false);
    }
}

客户端登录功能界面设计

 Client 客户端功能代码,区分图片和消息数据,项目过程中遇到发送大图片会出现只接收到一部分的情况;最后采用

QDataStream类解决
#include "clientwin.h"
#include "ui_clientwin.h"
#include <QDebug>
#include <stdio.h>
#include "longinwindow.h"

clientwin::clientwin(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::clientwin)
{
    ui->setupUi(this);
    ui->widget->hide();
    ui->chatedit->installEventFilter(this);

    timer = new QTimer(this);
    timer->start(1000);

    timer->connect(timer, SIGNAL(timeout()), this, SLOT(timerUpdate()));

    qDebug()<<"定时器定时开始 ";

    mysocket = new QTcpSocket();

    mysocket->connectToHost(QHostAddress("192.168.24.151"), 10000);



    connect(mysocket,SIGNAL(connected()),this,SLOT(onconnect()));

    connect(mysocket,SIGNAL(disconnected()),this,SLOT(ondisconnect()));
    connect(mysocket,SIGNAL(readyRead()),this,SLOT(onreadyread()));
    //connect(mysocket,SIGNAL(error(QAbstractSocket::SocketError socketError)),this,SLOT(ondisconnect()));

    connect(ui->friendlist,SIGNAL(itemSelectionChanged()),this,SLOT(on_selectchat()));

    imageIndex=0;
    savepacksize=0;

}

clientwin::~clientwin()
{
    message_save_on_local();
    delete ui;
    delete timer;
    delete mysocket;
}



/*
    Html图片格式

<img src="file:///D|/images/tupian.jpeg"/>
这个是物理路径,如果是网站的话,用相对路径则为下面的:
<img src="images/tupian.jpeg"/>
这个为html文件和images同一目录的写法
<img src="tupian.jpeg"/>
这个为图片和html文件同一目录的写法
<img src="../images/tupian.jpeg"/>
*/

void clientwin::onreadyread()
{

    QObject *obj=this->sender();
    QTcpSocket *socket=qobject_cast<QTcpSocket*>(obj);


    //当前缓冲区里面数据的大小,收到的数据大小。
    qint64 sizeNow=0;
    do
    {
        sizeNow = socket->bytesAvailable();
        QDataStream stream(socket);
        if(savepacksize==0)
        {

            if(sizeNow<sizeof(quint32))
            {

                return;

            }


            //quint32 sizePack=0;
            stream >> savepacksize;
        }
            //包不完整
        if(sizeNow < savepacksize -4 ) //64位和32位的区别少了4个字节 处理了半包情况
        {

            return;
        }
        savepacksize=0;
        //包已经完整
        qDebug()<<"full pack";
        QByteArray dataFull;
        stream >> dataFull;

        //判断剩下的字节数,是否会有粘包的情况。
         sizeNow = socket->bytesAvailable();
         QString dataTime=QDateTime::currentDateTime().toString("yy-MM-dd hh:mm:ss:");
         QString preindex = dataFull.mid(0,4);

         if(preindex=="TXT:")
         {
            ui->textEdit->append(dataTime);
            QString txtconcent=dataFull.mid(4);
            ui->textEdit->append(txtconcent);
          }
          else if(preindex=="IMG:")
          {

                QString htmlTag=QString ("<img src=\"%1\"></img>");
                QString index=QString::number(imageIndex);
                htmlTag=htmlTag.arg(index + ".png");

                QFile file(index+".png");
                file.open(QIODevice::WriteOnly);
                file.write(dataFull.mid(4));
                file.close();

                imageIndex=imageIndex+1;
                ui->textEdit->append(dataTime);
                ui->textEdit->insertHtml(htmlTag);

          }
          else if(preindex=="RIP:")                 //如果接收到服务器发来的所有已经连接上服务器的客户端Ip端口号
          {
             qDebug()<<"服务器已经发来客户端IP信息 ";
             int i;
             int n=ui->friendlist->count();//获取item的总数
             //删去所有item
             for(int i=0;i<n;i++)
             {
                 /*
                  使用takeItem(row)函数将QListWidget中的第row个item移除,
                  移除需要使用delete手动释放其在堆上占用的空间
                 */
                 QListWidgetItem *item = ui->friendlist->takeItem(0); //这里是0,不是i,因为每移除一个item都会导致每个item的row发生变化
                 delete item;
             }
             QString  tmpdata;
             tmpdata=dataFull.mid(4);

             if(!tmpdata.isEmpty())
             {

                 QStringList clientlist =tmpdata.split(":");
                 for(i=0;i<clientlist.count();i++)
                  {

                     ui->friendlist->addItem(clientlist.at(i));
                  }
             }

          }
    }while(sizeNow>0);  //如果剩余的字节数大于0的话
}

void clientwin::onconnect()
{
    qDebug()<<"我已经连接上服务器 ";
}

void clientwin::ondisconnect()
{
    qDebug()<<"我已经断开了连接 ";
    qDebug()<<"has disconnect";

    //message_save_on_local();
    qDebug()<<"111";
    QObject *obj=this->sender();
    QTcpSocket *socket=qobject_cast<QTcpSocket *>(obj);     //强转
    socket->deleteLater();
}

void clientwin::onerror(QAbstractSocket::SocketError socketError)
{
    qDebug()<<"error "<<socketError;
}

void clientwin::on_sendbt_clicked()     //发送TXT信息
{
    qDebug()<<"我已经按了发送按钮 ";
    QByteArray sendtextt;
    QString  text=ui->chatedit->toPlainText();
    if(text.isEmpty())   //判断如果输入框是空的返回不发送
        return;

    sendtextt="TXT:"+text.toLocal8Bit();

    qDebug()<<"sendtextt "<<sendtextt;


    //出现了丢包情况  封装包头
    QByteArray dataSend;    //封装的数据包

    QDataStream stream(&dataSend,QIODevice::WriteOnly);
    stream<<(quint32)0<< sendtextt;
    stream.device()->seek(0);
    stream<<dataSend.size();

    mysocket->write(dataSend);
}

void clientwin::on_sendpicBt_clicked()  //发送表情包按钮
{
    qDebug()<<"我已经按了发送表情包按钮 ";
    QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"),
                                                     "/",
                                                     tr("Images (*.png *.xpm *.jpg)"));

    if(fileName.isEmpty())
            return;

    QFile image(fileName);
    image.open(QIODevice::ReadOnly);
    QByteArray data="IMG:"+ image.readAll();
    image.close();

    //出现了丢包情况  封装包头
    QByteArray dataSend;    //封装的数据包

    QDataStream stream(&dataSend,QIODevice::WriteOnly);
    stream<<(quint32)0<< data;
    stream.device()->seek(0);
    stream<<dataSend.size();

    mysocket->write(dataSend);


}

void clientwin::on_selectchat()
{
    QString itemtextt;
    QStringList list;
    QByteArray dome2;
    itemtextt=ui->friendlist->currentItem()->text();

    qDebug()<<"item text "<<itemtextt;
    list=itemtextt.split(",");
    itemtextt=list[0];

    dome2="SLE:"+itemtextt.toLocal8Bit();

    //出现了丢包情况  封装包头
    QByteArray dataSend;    //封装的数据包

    QDataStream stream(&dataSend,QIODevice::WriteOnly);
    stream<<(quint32)0<< dome2;
    stream.device()->seek(0);
    stream<<dataSend.size();

    mysocket->write(dataSend);

}

void clientwin::timerUpdate()
{

    QDateTime time = QDateTime::currentDateTime();
    QString str = time.toString("HH:mm:ss");
    ui->lcdNumber->display(str);

}


bool clientwin::eventFilter(QObject *watched, QEvent *event)       //配合事件过滤器使用
{
    if (event->type() == QEvent::FocusIn)
    {
          ui->widget->show();
    }
    else if(event->type() == QEvent::KeyPress)
    {
        QKeyEvent *kE = (QKeyEvent *)event;
        if (kE->key() == Qt::Key_Enter)
        {
            ui->widget->hide();
        }

    }
    else if(event->type() ==QEvent::FocusOut)
    {
          ui->widget->hide();
    }
    return QMainWindow::eventFilter(watched,event);
}

void clientwin::message_save_on_local()     //保存聊天记录到本地
{

    QByteArray massage=ui->textEdit->toPlainText().toLocal8Bit();
    QString filename=mysocket->peerAddress().toString()+".txt";
    QFile history(filename);
    history.open(QIODevice::ReadWrite|QIODevice::Append);
    history.write(massage);
    history.close();
}


void clientwin::on_friendBT_clicked()
{
    //点击按钮连接上服务器

    qDebug()<<"flash friend ";
    QString scmd="wofasongleshengqingcmd";
    QByteArray cmd="FLH:"+scmd.toLocal8Bit();

    //出现了丢包情况  封装包头
    QByteArray dataSend;    //封装的数据包

    QDataStream stream(&dataSend,QIODevice::WriteOnly);
    stream<<(quint32)0<<cmd;
    stream.device()->seek(0);
    stream<<dataSend.size();

    mysocket->write(dataSend);
}

void clientwin::on_catpicBt_clicked()       //截图按钮
{
    qDebug()<<"jie tu ";
    QPixmap pix=QApplication::screens().at(0)->grabWindow(QWidget::winId());
    //保存起来
    QString savefilepath=QFileDialog::getSaveFileName(this);
    //调用QPixmap中的save函数保存截图
    pix.save(savefilepath);
}

void clientwin::on_paintBt_clicked()    //画板
{
    message_save_on_local();        //在断开连接的时候把消息记录保存下来
    this->hide();

    Painter *p=new Painter(this);
    p->show();
}

void clientwin::on_backBt_clicked()
{
    ui->textEdit->append("我已经点击了返回按钮");
    qDebug()<<"我已经点击了返回按钮";
    this->hide();
    LonginWindow *ll =new LonginWindow(this);
    ll->show();
}

void clientwin::on_historicBt_clicked()
{
    QByteArray massage;
    QString massagestr;
    QString filename=mysocket->peerAddress().toString()+".txt";
    QFile history(filename);
    if(history.open(QIODevice::ReadOnly)==false)
    {
        return;
    }
    while(1)
    {
        massage.clear();
        massage=history.read(100);
        ui->textEdit->append(massage);
        if(massage.length()<100)
            break;
    }
    history.close();
}

 下载完整代码地址

https://download.csdn.net/download/switchandcase/11983903


 

内容概要:本文系统研究了含混合式抽水蓄能的梯级水电站在源-网-荷-储协同系统中的日前优化调度问题,并提供了基于Matlab的代码实现。研究聚焦于高比例可再生能源接入背景下,如何通过优化调度提升电力系统的灵活性与经济性。建立了综合考虑梯级水电站水力耦合关系、抽水蓄能电站双向调节能力、电网潮流约束、负荷需求响应及新能源出力不确定性的主体协同优化模型,采用数学规划方法求解,旨在实现系统运行成本最小化或清洁能源消纳最大化。研究强调了能互补与时空协调的重要性,为现代电力系统的低碳高效运行提供了技术路径与仿真工具。; 适合群:具备电力系统分析、优化理论基础及Matlab编程能力的科研员、电气工程及相关专业的研究生,以及从事电网调度、能源规划与综合能源系统设计的工程技术员。; 使用场景及目标:①用于学习和复现源网荷储协同调度的建模方法与求解流程;②为含大规模水电与储能的区域电网日前调度提供决策支持;③支撑学术研究、学位论文撰写或实际工程项目中的调度方案评估与优化设计。; 阅读建议:建议结合所提供的Matlab代码与相关学术文献,深入理解模型构建的物理意义与数学表达,通过调整系统参数、边界条件或扩展模型结构(如引入更不确定性因素)进行仿真验证与二次开发,以深化对综合能源系统优化运行机制的理解。
内容概要:本文针对传统三电平并网逆变器存在的谐波含量高、电网不平衡工况适应性差及动态响应慢等问题,以有源中点箝位(ANPC)三电平逆变器为研究对象,提出了一套融合双极性倍频脉宽调制(DPWMA)、正负序分离锁相与电网电压前馈控制的高性能一体化并网控制策略。通过深入分析ANPC拓扑结构在开关损耗均衡、输出波形质量与中点电位控制方面的显著优势,奠定了系统高性能运行的硬件基础。在此基础上,DPWMA调制策略有效提升了等效开关频率,显著降低输出谐波含量;正负序分离锁相技术实现了电网不平衡工况下的精确相位同步,有效抑制负序分量干扰;电网电压前馈控制则大幅增强了系统的动态响应能力,有效抑制外部扰动影响。通过层次、场景的仿真验证,该复合控制策略在稳态运行、电网不平衡及动态切换等工况下均展现出卓越的电能质量、运行稳定性与强鲁棒性,为大功率新能源并网系统提供了先进的技术解决方案。; 适合群:从事电力电子、新能源并网、智能电网等相关领域的科研员、电气工程专业研究生及具备一定Matlab/Simulink仿真基础的工程技术员。; 使用场景及目标:①应用于大功率新能源并网系统中,提升逆变器在复杂电网环境下的运行性能;②为高电能质量要求的工业变频、电能治理设备提供先进的控制策略参考;③为电力电子系统仿真教学与科研项目的技术方案范例,深化对电平逆变器及其先进控制技术的理解。; 阅读建议:建议读者结合文中提到的Matlab/Simulink仿真模型进行实践验证,重点关注DPWMA调制的实现细节、正负序分离算法的设计原理以及前馈-反馈复合控制结构的搭建方法,深入理解各模块间的协同工机制,以全面掌握高性能并网逆变器的设计要点与核心技术。
内容概要:本文针对间歇性光伏出力导致的48V直流母线电压波动问题,开展离网光伏直流微网系统的电压稳定控制与储能系统双向充放电闭环调控研究。通过Simulink搭建包含光伏阵列、Boost升压变换器、负载、双向DC-DC变换器及锂离子电池储能的完整仿真系统,重点研究在光照不稳、负载变化等动态条件下系统的能量管理策略。采用光伏MPPT技术最大化太阳能捕获效率,结合储能系统的双向功率调节能力,实现“削峰填谷”与功率动态平衡。研究构建了分层控制架构,涵盖母线电压外环控制与储能充放电内环控制,确保直流母线电压稳定在额定水平,有效抑制功率供需失衡,提升微网电能质量与运行可靠性。; 适合群:具备电力电子、新能源系统、自动控制或电气工程等相关专业知识的科研员、高校研究生,以及从事光伏储能系统、微电网设计与仿真的工程技术员。; 使用场景及目标:①应用于偏远地区、海岛或独立建筑等无电网接入场景下的光伏直流微网系统设计与优化;②为解决光伏发电间歇性引发的电压波动与功率失衡问题提供有效的控制策略与仿真验证方案;③指导研究员开展储能系统在微网中参与电压支撑与能量调度的建模仿真与实验研究。; 阅读建议:建议结合Simulink仿真环境动手复现模型,重点关注MPPT算法与储能双向DC-DC变换器控制策略的协同工机制,深入理解分层控制结构中各模块的信号传递与动态响应特性,以全面掌握系统在不同工况下的稳定控制机理。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值