首页 > 编程语言 >c++ socket编程之服务端编写

c++ socket编程之服务端编写

时间:2023-04-01 11:46:29浏览次数:35  
标签:socket void ServerThread c++ server client ui 线程 服务端

开头

  • 想要写一个带界面、功能全面、传输高效、运行稳定的马儿,能够在生产环境下工作
  • 在cursor的帮助下,用一天时间完成了服务端和客户端的编写
  • 另外一天时间卡在了中文消息传输处理和大文件传输粘包、分包问题上

功能

  • 收发消息,支持中文消息
  • 发送命令执行并显示命令执行结果
  • 任意格式文件传输,支持大文件
  • 监控客户端状态
  • 多线程建立连接,非阻塞式通信,可同时完成多个传输任务,

服务端技术栈

  • qt ui界面设计
  • 使用QTcpSocket、QTcpServer类进行网络通信
  • 使用QThread类+moveToThread方法建立子线程
  • 主线程和子线程间通过信号和槽进行通信
  • 子线程访问ui的方法:ui线程创建子线程时,将ui指针传递给子线程的public成员,之后子线程就可以通过public成员来操作ui界面

服务端设计

  • 一共四个类,mainwindowserverthreadclientthreadconnectionlist
  • mainwindow是主窗口类,负责ui展示,用户交互,对serverthread进行创建和控制
  • serverthread是对QTcpSocket类的封装,负责建立连接,创建clientthread类,并转发mainwindow的消息
  • clientthread类是对QTcpServer类的封装,负责处理连接,接收来自上层的消息,以及数据收发
  • connectionlist类用于展示客户端状态,在主窗口中点击菜单栏按钮时弹出状态展示对话框
  • serverthread每创建一个clientthread,建立信号槽后,就将其move到线程中运行

服务端核心代码

  • mainwindow
#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    listen_status = new QLabel();
    listen_status->setObjectName("listenstatus");
    listen_status->setText(tr("断开监听"));
    ui->statusbar->addPermanentWidget(listen_status);
    ui->disconnect->setEnabled(false);
    ui->sendmsg->setEnabled(false);
    ui->sendcmd->setEnabled(false);
    ui->sendfile->setEnabled(false);
}

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

void MainWindow::on_openfile_clicked()
{
    QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"), QDir::homePath());
    if(!fileName.isEmpty()){
        ui->choosedfile->setText(fileName);
    }
}

void MainWindow::on_action_triggered()
{
    if (listen_status->text() == "正在监听" )
    {
        connectionlist *connectionList = new connectionlist(this);
        connectionList->setList(m_server->m_clients);
        connectionList->show();
    }
    else
    {
        QMessageBox::information(NULL, "警告", "未启动服务器!");
    }
}

void MainWindow::on_listen_clicked()
{
    m_server = new ServerThread(this);
    m_server->m_port = ui->port;
    m_server->m_maxminum = ui->maxmium;
    m_server->m_recvmsg = ui->recvmsg;
    m_server->m_inputcmd = ui->inputcmd;
    m_server->m_inputfile = ui->choosedfile;
    m_server->m_inputmsg = ui->inputmsg;
    connect(ui->sendmsg, &QPushButton::clicked, m_server, &ServerThread::onMsgButtonClicked);
    connect(ui->sendcmd, &QPushButton::clicked, m_server, &ServerThread::onCmdButtonClicked);
    connect(ui->sendfile, &QPushButton::clicked, m_server, &ServerThread::onFileButtonClicked);
    connect(m_server, &ServerThread::serverClosed, this, &MainWindow::onServerClosed);
    listen_status->setText(tr("正在监听"));
    m_server->startListen();
    ui->listen->setEnabled(false);
    ui->disconnect->setEnabled(true);
    ui->sendmsg->setEnabled(true);
    ui->sendcmd->setEnabled(true);
    ui->sendfile->setEnabled(true);
}

void MainWindow::on_disconnect_clicked()
{
    listen_status->setText(tr("断开监听"));
    m_server->stopListen();
    ui->listen->setEnabled(true);
    ui->disconnect->setEnabled(false);
    ui->sendmsg->setEnabled(false);
    ui->sendcmd->setEnabled(false);
    ui->sendfile->setEnabled(false);
}

void MainWindow::onServerClosed()
{
    if (m_server != nullptr)
    {
        delete m_server;
        m_server = nullptr;
        QMessageBox::information(NULL, "提示", "已释放所有连接资源!");
    }
}


void MainWindow::on_sendmsg_2_clicked()
{
    ui->recvmsg->clear();
}
  • serverthread
#include "serverthread.h"

ServerThread::ServerThread(QObject *parent) : QObject(parent)
{
    m_server = new QTcpServer(this);
}

void ServerThread::setStop()
{
    state = true;
}

void ServerThread::setStart()
{
    state = false;
}

void ServerThread::startListen()
{
    quint16 port = m_port->toPlainText().toInt();
    quint16 maxmium = m_maxminum->toPlainText().toInt();
    qDebug("监听端口是: %d", port);
    qDebug("最大连接数是: %d", maxmium);
    m_server->setMaxPendingConnections(maxmium);
    connect(m_server, &QTcpServer::newConnection, this, &ServerThread::handleNewConnection);
    m_server->listen(QHostAddress::Any, port);
}

void ServerThread::stopListen()
{
    m_server->close();
    QList<QTcpSocket *> clients = m_server->findChildren<QTcpSocket *>();
    for (QTcpSocket *client : clients)
    {
        client->close();
    }
    for (QThread *thread : m_threads)
    {
        thread->exit(0);
    }
    QMessageBox::information(NULL, "提示", "已关闭所有连接和子线程");
    emit serverClosed();
}

void ServerThread::handleNewConnection()
{
    qDebug("新连接已建立");
    QThread *thread = new QThread(this);
    QTcpSocket *new_client = m_server->nextPendingConnection();
    ClientThread *client_thread = new ClientThread(new_client, this);
    client_thread->setStart();
    client_thread->m_recvmsg = this->m_recvmsg;
    client_thread->m_inputmsg = this->m_inputmsg;
    client_thread->m_inputcmd = this->m_inputcmd;
    client_thread->m_inputfile = this->m_inputfile;
    QMessageBox::information(nullptr, "新连接已建立", "新连接来自于: " + client_thread->getClientIP());
    m_clients.append(client_thread->getClientIP());
    client_thread->moveToThread(thread);
    thread->start();
    m_threads.append(thread);
    connect(this, &ServerThread::sendMsg, client_thread, &ClientThread::sendMsg);
    connect(this, &ServerThread::sendFile, client_thread, &ClientThread::sendFile);
    connect(this, &ServerThread::sendCmd, client_thread, &ClientThread::sendCmd);
}

void ServerThread::onMsgButtonClicked()
{
    qDebug("准备给各个client发消息......");
    emit sendMsg();
}

void ServerThread::onFileButtonClicked()
{
    qDebug("准备给各个client发文件......");
    emit sendFile();
}

void ServerThread::onCmdButtonClicked()
{
    qDebug("准备给各个client发命令......");
    emit sendCmd();
}
  • clientthread,实际上实现了一个很简单的数据传输协议
#include "clientthread.h"

ClientThread::ClientThread(QTcpSocket *client_socket, QObject *parent) : QObject(parent)
{
    m_client = client_socket;
    connect(m_client, &QTcpSocket::readyRead, this, &ClientThread::recvMsg);
}

void ClientThread::setStart()
{
    state = true;
}

void ClientThread::setStop()
{
    state = false;
}

void ClientThread::recvMsg()
{
    QByteArray recv_data = m_client->readAll();
    QString client_ip = getClientIP();
    QString data = "[来自" + client_ip + "的消息]\n" + QString::fromUtf8(recv_data);
    qDebug("接收数据: %s", qPrintable(data));
    m_recvmsg->append(QString(data));
    qDebug("已接收到消息并推送至窗口");
}

void ClientThread::sendMsg()
{
    QString data = m_inputmsg->toPlainText();
    QString prefix = "MSG|";
    data = prefix + data;
    QByteArray byteArray = data.toUtf8();
    qDebug("发送数据: %s", qPrintable(data));
    m_client->write(byteArray);
    qDebug("已发送消息至目标机器");
}

void ClientThread::sendCmd()
{
    QString data = m_inputcmd->toPlainText();
    QString prefix = "CMD|";
    data = prefix + data;
    QByteArray byteArray = data.toUtf8();
    qDebug("发送命令: %s", qPrintable(data));
    m_client->write(byteArray);
    qDebug("已发送命令至目标机器");
}

void ClientThread::sendFile()
{
    QString filepath = m_inputfile->toPlainText();
    QFile file(filepath);
    if (!file.open(QIODevice::ReadOnly))
    {
        QMessageBox::warning(NULL, "错误", "打开文件:" + file.errorString() + " 失败,无法发送!");
        return;
    }
    QByteArray fileData = file.readAll();
    qDebug("发送文件大小为: %s", qPrintable(fileData.size()));
    QString prefix = "FILE|";
    QString preFileData = prefix + QString::number(fileData.size()) + "|";
    m_client->write(preFileData.toUtf8());
    m_client->write(fileData);
    qDebug("已发送文件至目标机器");
}

QString ClientThread::getClientIP()
{
    return m_client->peerAddress().toString();
}

遇到的问题

  • 线程回收问题:qt线程必须手动回收,建立线程时,将线程加入到线程列表中,在断开连接后,需要从线程列表中取出线程手动退出
  • socket资源回收问题:建立socket时,将clientsocket加入到列表中,断开连接后取出socket关闭
  • 线程安全问题:本文采用的子线程直接访问ui的方式不太安全,更安全的方法是在子线程中发送带参数的消息,在ui线程中进行处理
  • 外层对象和里层对象通信的问题:两边都可以采用信号槽的机制通信,但只能在外层对象创建里层对象后,在外层对象中connnect,不能再里层对象中connenct
  • 中文消息传输的问题:中文消息乱码的原因是通信两端采用的字符编码不一致,本文中在服务端读取中文字符串后,按照UTF-8编码格式转化为字节数组发送,在客户端接收到字节数组后,转化为Unicode字符串

效果展示

  • 主界面
  • 监控主机列表

标签:socket,void,ServerThread,c++,server,client,ui,线程,服务端
From: https://www.cnblogs.com/z5onk0/p/17278234.html

相关文章

  • c++基本用法学习
    1.保留小数的方式:cout<<setprecision(2)<<fixed<<p[0].sum<<endl;其中setprecision(n)填入想要保留的数字,fixed设置后可以保证保留的小数不会省略末尾的0同时也可以写成cout<<setprecision(2)<<fixed;cout<<p[0].sum<<endl;在程序中集体设置输出保留n......
  • 渡一教育_Java每日一练:建立Statement的作用是什么、前端Console.log( Boolean(‘‘))
    系列文章目录文章目录系列文章目录题目1java部分建立Statement的作用是什么(答案在最后公布)题目1-答案==解析====答案==题目2前端js部分==答案==题目3前端js部分如下代码输出的是什么答案和解析如下==解析==题目4如果希望1监听TCP端口为9000,服务端应该怎样创建socket题目答......
  • C++面试必备:常见C++面试题汇总及详细解析
    C++作为一门重要的编程语言,其在面试中常常是热门的考察对象。本文将会介绍一些常见的C++面试题,帮助C++面试者避免很多不必要的困惑和迷惑。每个问题都有相对应的答案,以便各位同学快速查阅。C++和C的区别是什么?C++是C的超集,也就是说,C++包括了C的所有基础特性,并且还增加了一些新的......
  • 关于linux环境下配置c/c++程序的编译器
    第一步:切换root用户     命令为:suroot然后输入密码即可第二步:输入命令  yum installgcc 和 yuminstall g++  第三步:通过查找路径来检查是否安装成功  whichgcc和 whichg++......
  • learn C++ for infrastructure software
    TolearnC++forinfrastructuresoftware,youcanfollowthesesteps:LearnthebasicsofC++:StartbylearningthebasicsofC++programminglanguage,includingsyntax,datatypes,controlstructures,functions,andobject-orientedprogrammingconcept......
  • jmeter中测试websocket接口
    一、jmeter安装对应的插件1、Jmeter不自带WebSocket功能,需要先安装WebSocket的插件,选项中选择pluginsManager; 2、在availableplugins搜索WebSocket进行插件下载即可,我是将搜索到的2个插件都安装了,待Jmeter重启后插件就是安装成功。二、WebSocketSampler组件添加方式:1、......
  • c++ 多线程编程std::thread, std::shared_mutex, std::unique_lock
    在C++11新标准中,可以简单通过使用thread库,来管理多线程,使用时需要#include<thread>头文件。简单用例如下:1std::thread(Simple_func);2std::threadt(Simple_func);3t.detach();第一行是直接启动一个新线程来执行Simple_func函数,而第二行先声明一个线程函数t(返回类型为......
  • Thrift TSocket::write_partial() send() errno = 10053问题记录分析
    场景南浔项目,服务器日志打印大量的异常日志:TSocket::write_partial()send()<Host:::ffff:41.230.95.17Port:63165>errno=10053提示服务器发送数据给客户端失败,然后主动断开连接。根据日志可以接收到客户端的登陆请求和心跳信息因为客户端每一次登陆过来,服务器都无法成功发送......
  • 浅析C++11 lambda表达式用法
    Lambda表达式(匿名函数、Lambda函数)是现代C++在C++11和更高版本中的一个新的语法糖,可以让我们快速便捷的创建一个函数。[capture-list](params)mutableexceptionattribute->return-type{body}capture-list:捕获列表,一般在Lambda表达式开头,捕获上下文中的变量,用......
  • HTTP,TCP,SOCKET区别
    1、TCP连接要想明白Socket连接,先要明白TCP连接。手机能够使用联网功能是因为手机底层实现了TCP/IP协议,可以使手机终端通过无线网络建立TCP连接。TCP协议可以对上层网络提供接口,使上层网络数据的传输建立在“无差别”的网络之上。建立起一个TCP连接需要经过“三次握手”:第一次......