C++ 客户端-服务器通信架构及代码解析

本文将深入解析一个基于 C++ 的客户端-服务器通信架构,并提供完整的代码示例。该架构用于实现客户端与服务器之间的数据交互,并包含数据库操作功能。我们将分别从客户端和服务器端的角度进行讲解,并分析通信流程、数据解析、错误处理等方面的细节。

客户端代码解析

**1. 客户端头文件 procession.h**c++#ifndef PROCESSION_H#define PROCESSION_H

#include #include #include #include #include

namespace Ui {class Procession;}

class Procession : public QWidget{ Q_OBJECT

public: explicit Procession(int userid,QWidget *parent = nullptr); ~Procession(); void processionWidget();

private slots: void connected(); void readyRead(); void displayError(QAbstractSocket::SocketError error); void on_connectBtn_clicked();

private: Ui::Procession ui; int userid; QTcpSocket m_socket;};

#endif // PROCESSION_H

**2. 客户端源文件 procession.cpp**c++#include 'procession.h'#include 'ui_procession.h'#include #include

Procession::Procession(int userid,QWidget *parent) : QWidget(parent), ui(new Ui::Procession), userid(userid){ ui->setupUi(this); processionWidget(); m_socket = new QTcpSocket(this);

connect(m_socket, &QTcpSocket::readyRead, this, &Procession::readyRead);    connect(m_socket, &QTcpSocket::connected, this, &Procession::connected);

connect(m_socket, QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::error), this, &Procession::displayError);}

Procession::~Procession(){ delete ui; // 在程序结束前关闭套接字 m_socket->close();}

void Procession::processionWidget(){ setWindowTitle('服务器通信'); setAutoFillBackground(true); QPalette palette=this->palette(); QPixmap pixmap(':/user/image/image/net.jpg'); palette.setBrush(QPalette::Window, QBrush(pixmap)); setPalette(palette); setFixedSize(600,400);}

void Procession::connected(){ ui->message->append('连接成功'); QByteArray block; QDataStream out(&block, QIODevice::WriteOnly); out << static_cast(this->userid); qDebug()<<static_cast(this->userid); if (m_socket->write(block) == -1) { ui->message->append('发送userid失败'); m_socket->close(); }}

void Procession::readyRead(){ QByteArray data = m_socket->readAll(); if (data.isEmpty()) { ui->message->append('读取的数据为空'); return; } /// 解析服务器返回的数据 QString suggestion = QString::fromUtf8(data); ui->message->append(suggestion);}

void Procession::displayError(QAbstractSocket::SocketError error){ QString errorMessage; switch (error) { case QAbstractSocket::ConnectionRefusedError: errorMessage = '连接被拒绝'; break; case QAbstractSocket::HostNotFoundError: errorMessage = '未找到主机'; break; case QAbstractSocket::SocketTimeoutError: errorMessage = '连接超时'; break; case QAbstractSocket::NetworkError: errorMessage = '网络错误'; break; default: errorMessage = '未知错误'; break; } ui->message->append(errorMessage);}

void Procession::on_connectBtn_clicked(){ if (m_socket->state() == QAbstractSocket::ConnectedState) { m_socket->disconnectFromHost(); // 先关闭套接字 }

QString ip = ui->IP->text();    QString port = ui->port->text();

if (ip.isEmpty() || port.isEmpty()) {        ui->message->clear();        ui->message->append('请输入有效的IP地址和端口号');        return;    }

ui->message->clear();    ui->message->append('正在连接中...');    m_socket->connectToHost(ip, static_cast<quint16>(ui->port->text().toInt()));}

客户端代码解析:

  • 客户端使用 QTcpSocket 类进行网络连接,通过连接到服务器指定的 IP 地址和端口进行通信。* 客户端发送 userid 给服务器,以便服务器获取对应用户的设备状态。* 客户端接收来自服务器的建议信息,并将其显示在用户界面上。* 客户端处理连接错误,并根据不同的错误类型显示相应的错误信息。

服务器代码解析

**1. 服务器头文件 server.h**c++#ifndef SERVER_H#define SERVER_H

#include <netinet/in.h>#include <sqlite3.h>

typedef struct { int uid; char device_name[20]; char device_state[20]; char value[20]; char mode[20];} Status;

typedef struct { int sockfd; struct sockaddr_in client_addr; socklen_t client_addr_len; sqlite3 *db;} ServerContext;

void startServer(); // 启动服务器int createSocket(); // 创建套接字void bindSocket(int sockfd, struct sockaddr_in server_addr); // 绑定套接字void listenSocket(int sockfd); // 监听套接字sqlite3 openDatabase(); // 打开数据库连接void closeSocket(int sockfd); // 关闭套接字void closeDatabase(sqlite3 *db); // 关闭数据库连接void sig_handler(int signo) ;//信号处理函数

#endif

**2. 服务器源文件 server.cpp**c++#include <stdio.h>#include <stdlib.h>#include <sys/socket.h>#include <unistd.h>#include <arpa/inet.h>#include <netinet/in.h>#include <sqlite3.h>#include <signal.h>#include 'server.h'#include 'handle.h'

int createSocket() { int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd == -1) { perror('连接失败 socket'); exit(1); } return sockfd;}

void bindSocket(int sockfd, struct sockaddr_in *server_addr) { if (bind(sockfd, (struct sockaddr *)server_addr, sizeof(*server_addr)) == -1) { perror('连接失败 bind'); close(sockfd); exit(1); }}

void listenSocket(int sockfd) { if (listen(sockfd, 5) == -1) { perror('连接失败 listen'); close(sockfd); exit(1); }}

void closeSocket(int sockfd) { if (close(sockfd) == -1) { perror('套接字关闭失败 close'); exit(1); }}

sqlite3* openDatabase() { sqlite3 *db; int rc = sqlite3_open('/mnt/g/Qt/Client/Smarthome_Client/database/database.db', &db);//根据实际数据库路径进行更改,此路径挂载G盘共享文件夹 if (rc != SQLITE_OK) { fprintf(stderr, '数据库打开失败: %s ', sqlite3_errmsg(db)); exit(1); } return db;}

void closeDatabase(sqlite3 *db) { if (sqlite3_close(db) != SQLITE_OK) { fprintf(stderr, '数据库关闭失败: %s ', sqlite3_errmsg(db)); exit(1); }}

void sig_handler(int signo) { if (signo == SIGTSTP) { printf('服务器登出 '); exit(-1); }}

void startServer() { printf('正在连接中... '); // 注册信号处理函数 signal(SIGTSTP, sig_handler); int sockfd = createSocket();

struct sockaddr_in server_addr;    server_addr.sin_family = AF_INET;    server_addr.sin_addr.s_addr = inet_addr('172.21.91.176');    server_addr.sin_port = htons(12345);

bindSocket(sockfd, &server_addr);    listenSocket(sockfd);

sqlite3 *db = openDatabase();

ServerContext context;    context.sockfd = sockfd;    context.client_addr_len = sizeof(context.client_addr);    context.db = db;

handleClientRequests(&context, sockfd);

closeSocket(sockfd);    closeDatabase(db);}

**3. 处理请求头文件 handle.h**c++#ifndef HANDLE_H#define HANDLE_H

#include <string.h>#include <stdlib.h>#include <sys/socket.h>#include <netinet/in.h>#include <sqlite3.h>#include 'server.h'#define MAX_BUFFER_SIZE 1024

void handleClientRequests(ServerContext *context,int sockfd);// 处理客户端请求void handleClientRequest(ServerContext context);// 处理单个客户端请求int receiveUserId(int sockfd);// 接收客户端发送的useridsqlite3_stmt prepareStatement(sqlite3 *db, int userid); // 准备数据库查询语句void initializeStatus(Status *status);// 初始化设备状态void updateAcStatus(Status *acStatus, int uid, const unsigned char *device_state, const unsigned char *value) ;// 更新设备状态void generateAcSuggestion(const Status *acStatus, char *suggestion);// 生成空调建议void generateHumidifierSuggestion(const Status *humidifierStatus, char suggestion);//生成加湿器建议char combineSuggestions(const char *suggestion, const char *humidifierSuggestion); // 合并建议void sendSuggestion(int sockfd,char *suggestion) ;// 发送建议

#endif

**4. 处理请求源文件 handle.cpp**c++#include <stdio.h>#include <unistd.h>#include <sys/socket.h>#include <arpa/inet.h>#include 'handle.h'

int receiveUserId(int sockfd) { int userid; ssize_t recvSize = recv(sockfd, &userid, sizeof(int), 0); if (recvSize == -1 || recvSize == 0 || recvSize != sizeof(int)) { perror('接收userid失败'); return -1; } return ntohl(userid);}

sqlite3_stmt* prepareStatement(sqlite3 *db, int userid) { char sql[100]; snprintf(sql, sizeof(sql), 'SELECT * FROM Status WHERE uid = %d', userid); sqlite3_stmt *stmt = NULL; int rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); if (rc != SQLITE_OK) { fprintf(stderr, '无法执行的语句: %s ', sqlite3_errmsg(db)); return NULL; } return stmt;}

void initializeStatus(Status *status) { memset(status, 0, sizeof(Status)); status->uid = 0; status->device_name[0] = '�';}

void updateAcStatus(Status *acStatus, int uid, const unsigned char *device_state, const unsigned char *value) { acStatus->uid = uid; strncpy(acStatus->device_state, device_state, sizeof(acStatus->device_state) - 1); acStatus->device_state[sizeof(acStatus->device_state) - 1] = '�'; strncpy(acStatus->value, value, sizeof(acStatus->value) - 1); acStatus->value[sizeof(acStatus->value) - 1] = '�';

}

void generateAcSuggestion(const Status *acStatus, char *suggestion) { int temperature = atoi(acStatus->value); if (temperature < 24) { snprintf(suggestion, MAX_BUFFER_SIZE, '空调温度过低,建议提高温度至26℃'); } else if(temperature>35){ snprintf(suggestion, MAX_BUFFER_SIZE, '空调温度过高,建议降低温度至26℃'); } else{ snprintf(suggestion, MAX_BUFFER_SIZE, '空调温度适宜'); }}

void generateHumidifierSuggestion(const Status *humidifierStatus, char *suggestion) { int humidity = atoi(humidifierStatus->value); if (humidity < 40) { snprintf(suggestion, MAX_BUFFER_SIZE, '加湿器湿度过低,建议调整加湿器湿度'); } else if (humidity > 70) { snprintf(suggestion, MAX_BUFFER_SIZE, '加湿器湿度过高,建议调整加湿器湿度'); } else { snprintf(suggestion, MAX_BUFFER_SIZE, '加湿器湿度适宜'); }}

char* combineSuggestions(const char *suggestion, const char *humidifierSuggestion) { int combinedSuggestionSize = strlen(suggestion) + strlen(humidifierSuggestion) + 2; char *combinedSuggestion = malloc(combinedSuggestionSize); snprintf(combinedSuggestion, combinedSuggestionSize, '%s %s', suggestion, humidifierSuggestion); return combinedSuggestion;}

void sendSuggestion(int sockfd, char *suggestion) { ssize_t sendSize = send(sockfd, suggestion, strlen(suggestion)+1, 0); if (sendSize == -1) { perror('发送失败 send'); free(suggestion); return; } else { printf('建议已发送 '); }}

void handleClientRequest(ServerContext *context) { int userid = receiveUserId(context->sockfd); if (userid == -1) { perror('接收userid失败'); return; }

printf('客户端已连接

'); printf('userid:%d ', userid);

sqlite3_stmt *stmt = prepareStatement(context->db, userid);    if (stmt == NULL) {        fprintf(stderr, '无法执行的语句: %s

', sqlite3_errmsg(context->db)); return; }

Status acStatus, humidifierStatus;    initializeStatus(&acStatus);    initializeStatus(&humidifierStatus);

char suggestion[MAX_BUFFER_SIZE] = '';    char humidifierSuggestion[MAX_BUFFER_SIZE] = '';

int acOpened = 0;    int humidifierOpened = 0;

while (sqlite3_step(stmt) == SQLITE_ROW) {        int uid = sqlite3_column_int(stmt, 1);        const unsigned char *device_name = sqlite3_column_text(stmt, 2);        const unsigned char *device_state = sqlite3_column_text(stmt, 3);        const unsigned char *value = sqlite3_column_text(stmt, 4);

    if (strcmp(device_name, '空调') == 0) {            if (strcmp(device_state, '开启') == 0){                updateAcStatus(&acStatus, uid, device_state, value);                generateAcSuggestion(&acStatus, suggestion);                acOpened = 1;            } else if (strcmp(device_state, '关闭') == 0) {                updateAcStatus(&acStatus, uid, device_state, value);                // 更新设备状态为关闭                acOpened = 0;            }        } else if (strcmp(device_name, '加湿器') == 0) {            if (strcmp(device_state, '开启') == 0){                updateAcStatus(&humidifierStatus, uid, device_state, value);                generateHumidifierSuggestion(&humidifierStatus, humidifierSuggestion);                humidifierOpened = 1;            } else if (strcmp(device_state, '关闭') == 0) {                updateAcStatus(&humidifierStatus, uid, device_state, value);                // 更新设备状态为关闭                humidifierOpened = 0;            }        }    }

sqlite3_finalize(stmt);

if (!acOpened) {        snprintf(suggestion, MAX_BUFFER_SIZE, '空调未开启');        }

if (!humidifierOpened) {        snprintf(humidifierSuggestion, MAX_BUFFER_SIZE, '加湿器未开启');    }

char *combinedSuggestion = combineSuggestions(suggestion, humidifierSuggestion);    sendSuggestion(context->sockfd, combinedSuggestion);    printf('建议为:

%s ', combinedSuggestion);

free(combinedSuggestion);

// 关闭连接    shutdown(context->sockfd, SHUT_RDWR);    printf('按下Ctrl + Z登出...

');}

void handleClientRequests(ServerContext *context, int sockfd) { fd_set readfds; int maxfd = sockfd;

while (1) {        FD_ZERO(&readfds);        FD_SET(sockfd, &readfds);

    int activity = select(maxfd + 1, &readfds, NULL, NULL, NULL);        if (activity == -1) {            perror('select');            break;        }

    if (FD_ISSET(sockfd, &readfds)) {            int clientSockfd = accept(sockfd, (struct sockaddr *)&context->client_addr, &context->client_addr_len);            if (clientSockfd == -1) {                perror('套接字接收失败

accept'); break; }

        context->sockfd = clientSockfd;            handleClientRequest(context);

        close(clientSockfd);        }    }}

服务器代码解析:

  • 服务器使用 socket 函数创建套接字,bind 函数绑定到指定的地址和端口,listen 函数开始监听连接。* 服务器使用 accept 函数接受客户端连接,并创建新的套接字与客户端进行通信。* 服务器接收客户端发送的 userid,并根据 userid 从数据库中查询设备状态信息。* 服务器根据设备状态生成建议信息,并通过 send 函数将建议信息发送给客户端。* 服务器使用 shutdown 函数关闭与客户端的连接。

接口函数说明

函数名 | 文件名 | 功能概要 | 参数 | 返回值 | 详细说明 | 使用注意事项 ---|---|---|---|---|---|---|startServer | server.h | 启动服务器 | 无 | void | 该函数用于启动服务器,接收客户端请求并处理。函数内部会调用其他函数来完成创建套接字、绑定套接字、监听套接字、打开数据库连接、处理客户端请求等操作。函数执行完毕后会关闭套接字和数据库连接。 | 无createSocket | server.h | 创建套接字 | 无 | int | 该函数用于创建套接字,返回套接字描述符。 | 无bindSocket | server.h | 绑定套接字 | int sockfd, struct sockaddr_in *server_addr | void | 该函数用于将套接字绑定到指定的地址和端口。 | 无listenSocket | server.h | 监听套接字 | int sockfd | void | 该函数用于将套接字设置为监听状态,等待客户端连接。 | 无openDatabase | server.h | 打开数据库连接 | 无 | sqlite3* | 该函数用于打开数据库连接,返回数据库连接句柄。 | 无closeSocket | server.h | 关闭套接字 | int sockfd | void | 该函数用于关闭套接字。 | 无closeDatabase | server.h | 关闭数据库连接 | sqlite3 *db | void | 该函数用于关闭数据库连接。 | 无sig_handler | server.h | 信号处理函数 | int signo | void | 该函数用于处理服务器进程接收到的信号,例如 Ctrl + Z 信号。 | 无handleClientRequests | handle.h | 处理客户端请求 | ServerContext *context, int sockfd | void | 该函数用于处理来自多个客户端的请求,它使用 select 函数监控多个套接字,并根据活动套接字调用 handleClientRequest 函数处理单个客户端请求。 | 无handleClientRequest | handle.h | 处理单个客户端请求 | ServerContext *context | void | 该函数用于处理单个客户端请求,它接收来自客户端的 userid,从数据库中查询设备状态,根据设备状态生成建议信息并发送给客户端。 | 无receiveUserId | handle.h | 接收客户端发送的 userid | int sockfd | int | 该函数用于接收来自客户端的 userid,并返回 userid 值。 | 无prepareStatement | handle.h | 准备数据库查询语句 | sqlite3 *db, int userid | sqlite3_stmt* | 该函数用于准备数据库查询语句,根据 userid 查询设备状态信息。 | 无initializeStatus | handle.h | 初始化设备状态 | Status *status | void | 该函数用于初始化设备状态结构体。 | 无updateAcStatus | handle.h | 更新设备状态 | Status *acStatus, int uid, const unsigned char *device_state, const unsigned char *value | void | 该函数用于更新设备状态信息,例如空调或加湿器的状态和值。 | 无generateAcSuggestion | handle.h | 生成空调建议 | const Status *acStatus, char *suggestion | void | 该函数根据空调的当前状态生成建议信息。 | 无generateHumidifierSuggestion | handle.h | 生成加湿器建议 | const Status *humidifierStatus, char *suggestion | void | 该函数根据加湿器的当前状态生成建议信息。 | 无combineSuggestions | handle.h | 合并建议 | const char *suggestion, const char *humidifierSuggestion | char* | 该函数将空调和加湿器的建议信息合并成一个字符串。 | 无sendSuggestion | handle.h | 发送建议 | int sockfd, char *suggestion | void | 该函数将建议信息发送给客户端。 | 无

代码总结

本文详细分析了 C++ 客户端-服务器通信架构,并提供了完整的代码示例。客户端使用 QTcpSocket 进行网络连接,发送 userid 并接收服务器的建议信息。服务器使用 socket、bind、listen、accept 等函数进行网络通信,并通过数据库查询设备状态信息,生成建议并发送给客户端。该架构可以用于智能家居、远程控制等场景

C++ 客户端-服务器通信架构及代码解析

原文地址: https://www.cveoy.top/t/topic/qmrG 著作权归作者所有。请勿转载和采集!

免费AI点我,无需注册和登录