C++ 客户端-服务器通信架构及代码解析
C++ 客户端-服务器通信架构及代码解析
本文将深入解析一个基于 C++ 的客户端-服务器通信架构,并提供完整的代码示例。该架构用于实现客户端与服务器之间的数据交互,并包含数据库操作功能。我们将分别从客户端和服务器端的角度进行讲解,并分析通信流程、数据解析、错误处理等方面的细节。
客户端代码解析
**1. 客户端头文件 procession.h**c++#ifndef PROCESSION_H#define PROCESSION_H
#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
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
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 等函数进行网络通信,并通过数据库查询设备状态信息,生成建议并发送给客户端。该架构可以用于智能家居、远程控制等场景
原文地址: https://www.cveoy.top/t/topic/qmrG 著作权归作者所有。请勿转载和采集!