01.服务器端代码
1.dbhelper.cpp
#include "dbhelper.h"
#include <iostream>
#include <cstring>using std::string;
using std::cerr;
using std::cout;
using std::endl;template <typename T>
std::vector<T>& operator<<(std::vector<T>& v,const T& val){v.push_back(val);return v;
}dbHelper::dbHelper(const string& dbname) {if (sqlite3_open(dbname.data(), &db) != SQLITE_OK) {cerr << "无法打开数据库: " << sqlite3_errmsg(db) << endl;} else {cout << "成功打开数据库: " << dbname << endl;// 创建用户表const char* create_user_table = "CREATE TABLE IF NOT EXISTS user (name TEXT PRIMARY KEY, pswd TEXT)";char* err_msg = nullptr;if (sqlite3_exec(db, create_user_table, nullptr, nullptr, &err_msg) != SQLITE_OK) {cerr << "创建用户表失败: " << err_msg << endl;sqlite3_free(err_msg);}// 创建病历表const char* create_medical_table = "CREATE TABLE IF NOT EXISTS medical (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, data TEXT NOT NULL, sympton TEXT, result TEXT, treat TEXT)";if (sqlite3_exec(db, create_medical_table, nullptr, nullptr, &err_msg) != SQLITE_OK) {cerr << "创建病历表失败: " << err_msg << endl;sqlite3_free(err_msg);}}
}dbHelper::~dbHelper() {sqlite3_close(db);
}bool dbHelper::regist(const string& name, const string& pswd) {const char* sql = "INSERT INTO user(name, pswd) VALUES(?, ?)";sqlite3_stmt* stmt = nullptr;if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) {cerr << "准备注册语句失败: " << sqlite3_errmsg(db) << endl;return false;}sqlite3_bind_text(stmt, 1, name.c_str(), -1, SQLITE_TRANSIENT);sqlite3_bind_text(stmt, 2, pswd.c_str(), -1, SQLITE_TRANSIENT);int res = sqlite3_step(stmt);sqlite3_finalize(stmt);if (res != SQLITE_DONE) {cerr << "注册失败: " << sqlite3_errmsg(db) << endl;return false;}cout << "用户注册成功: " << name << endl;return true;
}bool dbHelper::login(const string& name, const string& pswd) {const char* sql = "SELECT pswd FROM user WHERE name = ?";sqlite3_stmt* stmt = nullptr;if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) {cerr << "准备登录语句失败: " << sqlite3_errmsg(db) << endl;return false;}sqlite3_bind_text(stmt, 1, name.c_str(), -1, SQLITE_TRANSIENT);int res = sqlite3_step(stmt);if (res == SQLITE_ROW) {const char* db_pswd = (const char*)sqlite3_column_text(stmt, 0);bool success = (db_pswd != nullptr && pswd == db_pswd);sqlite3_finalize(stmt);cout << "登录尝试: " << name << " - " << (success ? "成功" : "失败") << endl;return success;}sqlite3_finalize(stmt);cerr << "用户不存在: " << name << endl;return false;
}// 实现添加病历方法
bool dbHelper::addMedicalRecord(const std::string& name, const std::string& data, const std::string& sympton, const std::string& result, const std::string& treat) {const char* sql = "INSERT INTO medical(name, data, sympton, result, treat) VALUES(?, ?, ?, ?, ?)";sqlite3_stmt* stmt = nullptr;cout << "尝试添加病历: " << name << endl;if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) {cerr << "准备添加病历语句失败: " << sqlite3_errmsg(db) << endl;return false;}sqlite3_bind_text(stmt, 1, name.c_str(), -1, SQLITE_TRANSIENT);sqlite3_bind_text(stmt, 2, data.c_str(), -1, SQLITE_TRANSIENT);sqlite3_bind_text(stmt, 3, sympton.c_str(), -1, SQLITE_TRANSIENT);sqlite3_bind_text(stmt, 4, result.c_str(), -1, SQLITE_TRANSIENT);sqlite3_bind_text(stmt, 5, treat.c_str(), -1, SQLITE_TRANSIENT);int res = sqlite3_step(stmt);sqlite3_finalize(stmt);if (res != SQLITE_DONE) {cerr << "添加病历失败: " << sqlite3_errmsg(db) << endl;return false;}cout << "病历添加成功" << endl;return true;
}// 实现更新病历方法
bool dbHelper::updateMedicalRecord(int id, const std::string& data, const std::string& sympton, const std::string& result, const std::string& treat) {const char* sql = "UPDATE medical SET data=?, sympton=?, result=?, treat=? WHERE id=?";sqlite3_stmt* stmt = nullptr;cout << "尝试更新病历: ID=" << id << endl;if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) {cerr << "准备更新病历语句失败: " << sqlite3_errmsg(db) << endl;return false;}sqlite3_bind_text(stmt, 1, data.c_str(), -1, SQLITE_TRANSIENT);sqlite3_bind_text(stmt, 2, sympton.c_str(), -1, SQLITE_TRANSIENT);sqlite3_bind_text(stmt, 3, result.c_str(), -1, SQLITE_TRANSIENT);sqlite3_bind_text(stmt, 4, treat.c_str(), -1, SQLITE_TRANSIENT);sqlite3_bind_int(stmt, 5, id);int res = sqlite3_step(stmt);sqlite3_finalize(stmt);if (res != SQLITE_DONE || sqlite3_changes(db) == 0) {cerr << "更新病历失败: " << sqlite3_errmsg(db) << endl;return false;}cout << "病历更新成功" << endl;return true;
}// 实现删除病历方法
bool dbHelper::deleteMedicalRecord(int id) {const char* sql = "DELETE FROM medical WHERE id=?";sqlite3_stmt* stmt = nullptr;cout << "尝试删除病历: ID=" << id << endl;if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) {cerr << "准备删除病历语句失败: " << sqlite3_errmsg(db) << endl;return false;}sqlite3_bind_int(stmt, 1, id);int res = sqlite3_step(stmt);sqlite3_finalize(stmt);if (res != SQLITE_DONE || sqlite3_changes(db) == 0) {cerr << "删除病历失败: " << sqlite3_errmsg(db) << endl;return false;}cout << "病历删除成功" << endl;return true;
}// 实现查询病历方法
std::vector<std::string> dbHelper::queryMedicalRecords(const std::string& name) {std::vector<std::string> result;sqlite3_stmt* stmt = nullptr;cout << "执行病历查询: name=" << name << endl;const char* sql = "SELECT id, name, data, sympton, result, treat FROM medical WHERE name=? ORDER BY id DESC";if (sqlite3_prepare_v2(db, sql, -1, &stmt, nullptr) != SQLITE_OK) {cerr << "准备查询语句失败: " << sqlite3_errmsg(db) << endl;return result;}sqlite3_bind_text(stmt, 1, name.c_str(), -1, SQLITE_TRANSIENT);while (sqlite3_step(stmt) == SQLITE_ROW) {std::string id = (const char*)sqlite3_column_text(stmt, 0);std::string name = (const char*)sqlite3_column_text(stmt, 1);std::string data = (const char*)sqlite3_column_text(stmt, 2);std::string sympton = (const char*)sqlite3_column_text(stmt, 3);std::string result_text = (const char*)sqlite3_column_text(stmt, 4);std::string treat = (const char*)sqlite3_column_text(stmt, 5);result << id << name << data << sympton << result_text << treat;}sqlite3_finalize(stmt);if (result.empty()) {cout << "未查询到匹配的病历" << endl;} else {cout << "共查询到 " << result.size() << " 条病历" << endl;}return result;
}
2.dbhelper.h
#ifndef _dbhelper_h_
#define _dbhelper_h_#include <sqlite3.h>
#include <iostream>
#include <vector>// 一个数据库刀层负责:打开关闭读取写入一个数据中所有表单的所有操作class dbHelper{
private:sqlite3* db;sqlite3_stmt* stmt;
public:dbHelper(const std::string& dbname = "");~dbHelper();// dbHelper 操作的数据库中所有能够操作的表单的函数bool regist(const std::string& name,const std::string& pswd);bool login(const std::string& name,const std::string& pswd);// 写针对别的表单的操作函数// 如果说要上设计模式,什么开闭原则之类的,也在这里派生就行了// 总而言之,数据库刀层目的很简单:将针对数据库的所有操作,集中在一起,方便查询修改/*** @brief 添加病历记录* @param name 宠物名称* @param data 就诊日期* @param sympton 症状* @param result 诊断结果* @param treat 治疗方案* @return 是否成功*/bool addMedicalRecord(const std::string& name, const std::string& data, const std::string& sympton, const std::string& result, const std::string& treat);/*** @brief 更新病历记录* @param id 病历ID* @param data 就诊日期* @param sympton 症状* @param result 诊断结果* @param treat 治疗方案* @return 是否成功*/bool updateMedicalRecord(int id, const std::string& data, const std::string& sympton, const std::string& result, const std::string& treat);/*** @brief 删除病历记录* @param id 病历ID* @return 是否成功*/bool deleteMedicalRecord(int id);/*** @brief 查询病历记录* @param name 宠物名称* @return 病历列表*/std::vector<std::string> queryMedicalRecords(const std::string& name);
};#endif
3.epoll_server.cpp
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <wait.h>
#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/un.h>
#include <sys/epoll.h>
#include <iostream>
#include <map>
#include <algorithm>
#include <vector>
#include <climits>
#include <errno.h>#include "pack.h"
#include "dbhelper.h"using namespace std;bool client_handler(int client,dbHelper& db);struct User{string username;FILE* fp;// 处理分包时需要以下3个数据Pack readed_pack; // 用来暂存已经读取到的部分协议包 以及 用来拼接未读取的协议包int readed_pack_size = 0; // 用来记录已读协议包的大小int unreaded_pack_size = 0; // 用来记录未读协议包的大小// 处理4个字节的size 发生分包的可能性int readed_size = 0;int readed_size_size = 0;int unreaded_size_size = 0;
};template <class T1,class T2>
class mymap:public map<T1,T2>{
public:int operator[](const string& name){for(auto ele:*this){if(ele.second.username == name){return ele.first;}}return -1;}T2& operator[](const T1& key){return this->map<T1,T2>::operator[](key);}
};mymap<int,User> m;// 键为:客户端套接字,值为 User 对象
// 这个map的作用为:存储所有在线用户的套接字信息和用户的相关信息int main(int argc, const char *argv[])
{if(argc < 2){printf("请末端口号\n");return 1;}dbHelper db("user.db");short port = atoi(argv[1]);// "abc123" -> 0int server = socket(AF_INET,SOCK_STREAM,0);struct sockaddr_in addr = {0};addr.sin_family = AF_INET; addr.sin_port = htons(port);addr.sin_addr.s_addr = inet_addr("0.0.0.0");if(bind(server,(struct sockaddr*)&addr,sizeof(addr)) == -1){perror("bind");return 1;}listen(server,10);// 创建epoll监视列表int epfd = epoll_create1(EPOLL_CLOEXEC);// 将 0 和 server 添加进入监视列表//struct epoll_event epoll_stdin = {.events = EPOLLIN ,data:{fd:server}};struct epoll_event epoll_stdin = {events : EPOLLIN , data:{fd:0}};struct epoll_event epoll_server = {events : EPOLLIN , data:{fd : server}};epoll_ctl(epfd,EPOLL_CTL_ADD,0,&epoll_stdin);epoll_ctl(epfd,EPOLL_CTL_ADD,server,&epoll_server);int eplen = 2;while(1){// 提前准备一个激活列表struct epoll_event list[20] = {0};int count = epoll_wait(epfd,list,eplen,-1);for(int i=0;i<count;i++){// list里面全都是激活的描述符,最多判断一下 ,以何种方式激活的// 将激活的具体描述符单独取出int fd = list[i].data.fd;if(fd == 0){char buf[1024] = "";scanf("%s",buf);getchar();printf("键盘输入数据:%s\n",buf);continue;}if(fd == server){printf("有客户端连接\n");struct sockaddr_in client_addr = {0};socklen_t client_len = sizeof(client_addr);int client = accept(server,(struct sockaddr*)&client_addr,&client_len);printf("新连接的客户端的ip = %s\n",inet_ntoa(client_addr.sin_addr));printf("新连接的客户端的port = %d\n",ntohs(client_addr.sin_port));struct epoll_event epoll_client = {events : EPOLLIN , data:{fd : client}};epoll_ctl(epfd,EPOLL_CTL_ADD,client,&epoll_client);eplen ++;continue;}// 剩下的都是客户端描述符bool res = client_handler(fd,db);// 这个函数用来写处理客户端的逻辑if(res == false){cout << "移除客户端" << endl;epoll_ctl(epfd,EPOLL_CTL_DEL,fd,nullptr);}}}return 0;
}// 辅助函数:直接从Pack数据解析字符串
vector<string> parseStringsFromHex(const Pack& pack) {vector<string> result;// 跳过包头(12字节:4字节size + 4字节type + 4字节back)const char* data = ((const char*)&pack) + 12;int total_size = pack.size() - 12;if (total_size <= 0) {cout << "数据包无有效内容" << endl;return result;}cout << "========================================" << endl;cout << "十六进制原始数据分析:" << endl;cout << "总数据大小: " << total_size << " 字节" << endl;// 十六进制显示前64字节(如果数据很大)int display_size = std::min(total_size, 64);cout << "数据内容(十六进制): " << endl;for(int i = 0; i < display_size; ++i) {printf("%02X ", (unsigned char)data[i]);if(i % 16 == 15) cout << endl;}if (total_size > 64) {cout << "... (数据过长,仅显示前64字节)";}cout << endl;cout << "尝试解析字符串:" << endl;int pos = 0;int string_count = 0;while (pos < total_size && string_count < 10) {if (pos + 2 > total_size) {cout << "剩余数据不足2字节,无法读取长度" << endl;break;}// 读取字符串长度(2字节,小端格式)short str_len = 0;memcpy(&str_len, data + pos, 2);cout << "位置 " << pos << ": 长度字段 = " << str_len;pos += 2;if (str_len <= 0 || str_len > 1000) {cout << " (异常长度,停止解析)" << endl;pos -= 2; // 回退位置break;}if (pos + str_len > total_size) {cout << " (长度超出范围)" << endl;pos -= 2; // 回退位置break;}cout << " (有效)" << endl;// 提取字符串string str(data + pos, str_len);// 检查是否为有效的UTF-8字符串bool is_valid_utf8 = true;for (char c : str) {if (c < 0 && (unsigned char)c < 0x80) {// 可能是非UTF-8字符is_valid_utf8 = false;break;}}cout << " 字符串[" << string_count << "]: " << str;if (!is_valid_utf8) cout << " (警告: 可能不是有效UTF-8)";cout << endl;result.push_back(str);pos += str_len;string_count++;}if (result.empty()) {cout << "标准解析失败,尝试备用方法..." << endl;// 方法1: 尝试寻找以0结尾的C字符串for (int i = 0; i < total_size; ++i) {if (data[i] == '\0' || i == total_size - 1) {int str_len = (data[i] == '\0') ? i : total_size;if (str_len > 0 && str_len <= total_size) {string str(data, str_len);cout << "找到C风格字符串: " << str << endl;result.push_back(str);break;}}}// 方法2: 直接读取整个数据作为单个字符串if (result.empty()) {string raw_str(data, total_size);// 移除前导和尾随的空格和控制字符size_t start = 0;size_t end = raw_str.length();while (start < end && (isspace(raw_str[start]) || raw_str[start] < 32)) {start++;}while (end > start && (isspace(raw_str[end-1]) || raw_str[end-1] < 32)) {end--;}if (start < end) {string clean_str = raw_str.substr(start, end - start);cout << "直接读取清理后的字符串: " << clean_str << endl;result.push_back(clean_str);}}}cout << "解析完成,共得到 " << result.size() << " 个字符串" << endl;cout << "========================================" << endl;return result;
}bool client_handler(int client,dbHelper& db){while(1){int size = 0;int res = 0;Pack pack;if(m[client].unreaded_pack_size != 0){res=recv(client,(char*)&m[client].readed_pack + m[client].readed_pack_size,m[client].unreaded_pack_size,MSG_DONTWAIT);if(res != m[client].unreaded_pack_size){// 这里只要更新下已读大小和未读大小,保证下一次进入当前分支的时候,能够根据已读大小和未读大小,读取对应的数据以及存放到对应的地方m[client].readed_pack_size += res;m[client].unreaded_pack_size -= res;break;}pack = m[client].readed_pack;// 处理完分包记得 unreaded_pack_size = 0;m[client].unreaded_pack_size = 0;}else{if(m[client].unreaded_size_size != 0){recv(client,(char*)&m[client].readed_size + m[client].readed_size_size,m[client].unreaded_size_size,MSG_DONTWAIT);size = m[client].readed_size;m[client].unreaded_size_size = 0;}else{// else 部分为正常读取数据:先读4字节,再读剩余字节数,可能处理分包res = recv(client,(char*)&size,4,MSG_DONTWAIT);if(res == -1){//cout << "退出循环" << endl;return true;}else if(res == 0){cout << "4字节recv结束" << endl;return false;}if(res != 4){m[client].readed_size_size = res;m[client].unreaded_size_size = 4 - res;memcpy(&m[client].readed_size,&size,m[client].readed_size_size);break;}}res = recv(client,(char*)&pack+4,size-4,MSG_DONTWAIT);if(res == 0){cout << "size-4字节recv结束" << endl;return false;}else if(res == -1){return true;}cout << "size = " << size << endl;pack.setSize(size);if(res != size-4){// 如果实际读取到的字节数 != 想要读取的字节数// 说明发生了分包cout << "发生分包" << endl;m[client].readed_pack_size = res + 4;m[client].unreaded_pack_size = size - m[client].readed_pack_size;memcpy(&m[client].readed_pack,&pack,m[client].readed_pack_size);// 将已经读取的协议包缓存在每个客户端专属的缓存区 readed_pack 里面break;}}cout << "type = " << pack.getType() << endl;switch(pack.getType()){case TYPE_REGIST:{vector<string> list = parseStringsFromHex(pack);if (list.size() < 2) {cout << "注册请求参数不足" << endl;pack.setBack(BACK_ERR);pack >> client;break;}string name = list[0];string pswd = list[1];bool res = db.regist(name,pswd);if(res == true){pack.setBack(BACK_SUCCESS);}else{pack.setBack(BACK_ERR);}pack >> client; // 将协议包回给客户端break;}case TYPE_LOGIN:{vector<string> list = parseStringsFromHex(pack);if (list.size() < 2) {cout << "登录请求参数不足" << endl;pack.setBack(BACK_ERR);pack >> client;break;}string name = list[0];string pswd = list[1];bool res = db.login(name,pswd);if(res == true){pack.setBack(BACK_SUCCESS);m[client].username = name;}else{pack.setBack(BACK_ERR);}pack >> client; // 将协议包回给客户端break;}case TYPE_FILE_UPLOAD_REQUEST:{cout << "文件上传请求" << endl;vector<string> list = parseStringsFromHex(pack);if (list.empty()) {cout << "文件上传请求无文件名" << endl;break;}string filename = list[0];m[client].fp = fopen(filename.data(),"w");break;}case TYPE_FILE_UPLOADING:{cout << "文件上传中" << endl;char buf[4096] = "";pack.readAll(buf,pack.size()-12);// 到此为止,客户端发来的一部分的文件内容,保存到了buf里面fwrite(buf,1,pack.size()-12,m[client].fp);break;}case TYPE_FILE_UPLOAD_END:{cout << "文件上传完成" << endl;fclose(m[client].fp);break;}case TYPE_SENDING_CAMERA:case TYPE_SEND_CAMERA_END:{// 服务器只做一个事情:将接受到的所有图片内容,转发给其他所有客户端cout << "转发camera图片..." << endl;for(auto& ele:m){if(ele.first != client){pack >> ele.first;}}break;}case TYPE_TEXT:{vector<string> list = pack.readAll();string sender = list[0];string targetUser = list[1];string text = list[2];// 打印接收到的消息cout << "收到消息:" << sender << " -> " << targetUser << ": " << text << endl;if(targetUser == "all"){for(auto ele:m){if(ele.first != client){cout << "向 " << client << " 发送数据" << endl;pack >> ele.first;}}}else{}break;}// 在这里新写 2 个case,用来转发桌面画面case TYPE_SENDING_DESKTOP:case TYPE_SEND_DESKTOP_END:{cout<<"转发桌面画面.."<<endl;for(auto& ele:m){if(ele.first!=client){pack>>ele.first;}}break;} // 添加病历相关case分支case TYPE_MEDICAL_RECORD_ADD: {cout<<"<@@>"<<endl;vector<string> list = parseStringsFromHex(pack);//vector<string> list = pack.readAll();cout<<list.size()<<endl;if (list.size() < 5) {cout << "添加病历参数不足,需要5个参数" << endl;pack.setBack(BACK_ERR);pack >> client;break;}string name = list[0];string data = list[1];string sympton = list[2];string result = list[3];string treat = list[4];bool res = db.addMedicalRecord(name, data, sympton, result, treat);pack.setBack(res ? BACK_SUCCESS : BACK_ERR);pack >> client;break;}/*case TYPE_MEDICAL_RECORD_QUERY: {cout << "========================================" << endl;cout << "收到病历查询请求数据包" << endl;cout << "数据包大小: " << pack.size() << endl;cout << "数据包类型: " << pack.getType() << endl;// 使用新的十六进制解析方法vector<string> strings = parseStringsFromHex(pack);//vector<string> list = pack.readAll();if (strings.empty()) {cout << "警告: 无法解析任何字符串数据" << endl;pack.setBack(BACK_ERR);pack >> client;break;}// 获取宠物名string name = strings[0];cout << "查询宠物名: " << name << endl;auto records = db.queryMedicalRecords(name);cout << "查询到 " << records.size() << " 条病历" << endl;// 详细打印查询到的数据cout << "--- 病历数据详情 ---" << endl;int recordIndex = 1;for (const auto& record : records) {cout << "第 " << recordIndex << " 条病历:" << endl;cout << " ID: " << (record.size() > 0 ? record[0] : "N/A") << endl;cout << " 宠物名: " << (record.size() > 1 ? record[1] : "N/A") << endl;cout << " 日期: " << (record.size() > 2 ? record[2] : "N/A") << endl;cout << " 症状: " << (record.size() > 3 ? record[3] : "N/A") << endl;cout << " 诊断结果: " << (record.size() > 4 ? record[4] : "N/A") << endl;cout << " 治疗方案: " << (record.size() > 5 ? record[5] : "N/A") << endl;cout << " 创建时间: " << (record.size() > 6 ? record[6] : "N/A") << endl;cout << " 更新时间: " << (record.size() > 7 ? record[7] : "N/A") << endl;cout << "---" << endl;recordIndex++;}// 构造返回给前端的数据Pack response;response.setType(pack.getType());response.setBack(BACK_SUCCESS);// 先返回记录总数response << to_string(records.size());// 然后返回每条记录的字段数据for (const auto& record : records) {for (const auto& field : record) {response << field;}}cout << "正在将查询结果返回给客户端..." << endl;response >> client;// 使用send函数替代运算符重载,增加错误处理// if (send(client, (char*)&response, response.size(), MSG_NOSIGNAL) == -1) {// cout << "发送数据包失败,客户端可能已断开连接" << endl;// // 处理发送失败的情况// }cout << "查询结果已发送给客户端" << endl;break;}*/case TYPE_MEDICAL_RECORD_QUERY:{cout << "用户查询病历请求" <<endl;vector<string> list = pack.readAll();string name = list[0];vector<string> result = db.queryMedicalRecords(name);for(auto ele : result){pack << ele;}pack >> client;break;}case TYPE_MEDICAL_RECORD_UPDATE: {vector<string> list = parseStringsFromHex(pack);if (list.size() < 5) {cout << "更新病历参数不足,需要5个参数" << endl;pack.setBack(BACK_ERR);pack >> client;break;}int id = stoi(list[0]); // 转换为整数类型string data = list[1];string sympton = list[2];string result = list[3];string treat = list[4];bool res = db.updateMedicalRecord(id, data, sympton, result, treat);pack.setBack(res ? BACK_SUCCESS : BACK_ERR);pack >> client;break;}case TYPE_MEDICAL_RECORD_DELETE: {vector<string> list = parseStringsFromHex(pack);if (list.empty()) {cout << "删除病历参数不足,需要病历ID" << endl;pack.setBack(BACK_ERR);pack >> client;break;}int id = stoi(list[0]); // 转换为整数类型bool res = db.deleteMedicalRecord(id);pack.setBack(res ? BACK_SUCCESS : BACK_ERR);pack >> client;break;}}//cout << "数据处理完毕" << endl;}return true;
}
4.pack.cpp
#include "pack.h"
#include <unistd.h> // 包含 usleep 函数的声明Pack::Pack()
{}void Pack::setType(Type type)
{this->type = type;
}void Pack::setBack(Back back)
{this->back = back;
}void Pack::setSize(int size)
{pack_size = size;
}int Pack::size() const
{return pack_size;
}Type Pack::getType() const
{return type;
}Back Pack::getBack() const
{return back;
}void Pack::append(const string &val)
{const char* p = val.data();short len = strlen(p); // strlen ( char*)*(short*)(buf+used) = len;used += 2;memcpy(buf+used,p,len);used += len;pack_size = 12 + used;
}void Pack::append(const char *p, int size)
{memset(buf,0,4096);memcpy(buf,p,size);pack_size = 12 + size;
}vector<string> Pack::readAll()
{vector<string> list;int readed_size = 0;while(1){short size = *(short*)(buf+readed_size);if(size <=0 || readed_size + size >= 4096){break;}readed_size += 2;char* temp = new char[size + 1];memset(temp,0,size+1);memcpy(temp,buf+readed_size,size);readed_size += size;string str(temp);list.push_back(str);delete[] temp;}return list;
}void Pack::readAll(char* buf,int size){// 参数buf需要调用者提前准备一下memcpy(buf,this->buf,size);
}Pack &Pack::operator<<(const string &val)
{append(val);return *this;
}void Pack::operator>>(int client)
{
// int sent=0;
// while(sent<pack_size){
// int res=send(client,(const char*)this+sent,pack_size-sent,0);
// std::cout<<client<<std::endl;
// std::cout<<pack_size <<" "<< type << " " << back<< " " << buf <<std::endl;// if(res==-1){
// if(errno==EAGAIN || errno==EWOULDBLOCK){
// std::cout<<"缓冲区满"<<std::endl;
// usleep(1000);
// continue;
// }
// else{
// perror("发送失败");
// break;
// }
// }
// sent+=res;
// }send(client,(char*)this,pack_size,0);
}
5.pack.h
#ifndef PACK_H
#define PACK_H#include <cstring>
#include <vector>
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>enum Type{TYPE_REGIST,TYPE_LOGIN,TYPE_FILE_UPLOAD_REQUEST,TYPE_FILE_UPLOADING,TYPE_FILE_UPLOAD_END,TYPE_SENDING_CAMERA,TYPE_SEND_CAMERA_END,TYPE_SENDING_DESKTOP,TYPE_SEND_DESKTOP_END,TYPE_MEDICAL_RECORD_ADD, // 添加病历TYPE_MEDICAL_RECORD_QUERY, // 查询病历TYPE_MEDICAL_RECORD_UPDATE, // 更新病历TYPE_TEXT,TYPE_MEDICAL_RECORD_DELETE, // 删除病历TYPE_SENDING_AUDIO, // 发送音频数据TYPE_SEND_AUDIO_END // 音频数据发送结束
};// 协议包发给服务器之后,服务器要处理,如果处理成功,服务器发给客户端的协议包里面的Back,就是SUCCESS
enum Back{BACK_SUCCESS,BACK_ERR
};using std::string;
using std::vector;class Pack
{private:int pack_size = 12;// 记录整个协议包大小,方便服务器知道当前发过去的协议包有多大Type type;Back back;char buf[4096] = "";int used = 0;
public:Pack();void append(const string& val);// 将外部的字符串数据,写入协议包的buf里面void append(const char* p,int size);vector<string> readAll();// 读取协议包的buf所有字符串void readAll(char* buf,int size);Pack& operator<<(const string& val);void operator>>(int client);void setType(Type type);void setBack(Back back);void setSize(int size);int size() const; // pack_size的get接口Type getType() const;Back getBack() const;
};#endif // PACK_H

02.客户端代码
Headers
1.abs.h
#ifndef ABS_H
#define ABS_H#include <QObject>
#include <QAbstractVideoSurface>
#include <QCamera>
#include <QCameraInfo>
#include <QDebug>
#include <QImage>class Abs : public QAbstractVideoSurface
{Q_OBJECT
public:Abs(QObject* parent = nullptr);virtual bool present(const QVideoFrame &frame);virtual QList<QVideoFrame::PixelFormat> supportedPixelFormats(QAbstractVideoBuffer::HandleType type = QAbstractVideoBuffer::NoHandle) const;void camera_start();void camera_stop();signals:void sndImage(QImage& image);private:QCamera* camera;
};#endif // ABS_H
2.audioinput.h
#ifndef AUDIOINPUT_H
#define AUDIOINPUT_H#include <QAudioFormat>
#include <QAudioInput>
#include <QAudioOutput>
#include <QObject>
#include <QDebug>class AudioInput:public QObject
{Q_OBJECT
public:AudioInput(QObject* parent = nullptr);QAudioInput* input;QIODevice* io;void start();
public slots:void onReadyRead();signals:void sndVoice(const QByteArray& arr);
};#endif // AUDIOINPUT_H
3.audiooutput.h
#ifndef AUDIOOUTPUT_H
#define AUDIOOUTPUT_H#include <QObject>
#include <QAudioFormat>
#include <QAudioInput>
#include <QAudioOutput>
#include <QDebug>// 这个类是用来输出音频数据的class AudioOutput : public QObject
{Q_OBJECT
public:explicit AudioOutput(QObject *parent = nullptr);void outPutVoice(const QByteArray& arr);
private:QAudioOutput* output;QIODevice* io;signals:};#endif // AUDIOOUTPUT_H
4.medicalrecordquery.h
#ifndef MEDICALRECORDQUERY_H
#define MEDICALRECORDQUERY_H#include <QWidget>
#include <QTcpSocket> // 添加这行代码
#include <QByteArray> // 添加 QByteArray 支持
#include <QTimer> // 添加 QTimer 支持
#include "pack.h"namespace Ui {
class MedicalRecordQuery;
}class MedicalRecordQuery : public QWidget {Q_OBJECT
public:explicit MedicalRecordQuery(QWidget *parent = nullptr);~MedicalRecordQuery();void setClient(QTcpSocket *client);void drawTable(Pack& pack); // 处理服务端响应signals:// 添加返回信号void returnToUserInterface();private slots:
// void handleReadyRead();void handleQueryButtonClicked();void on_pushButtonReturn_clicked();void on_pushButton_2_clicked(); // 填写病历按钮槽函数void processMedicalRecords(const QJsonArray &records); // 声明处理医疗记录的槽函数void on_pushButtonQuery_clicked();protected:void resizeEvent(QResizeEvent *event) override;void showEvent(QShowEvent *event) override;private:
// void initConnection(); // 连接初始化函数声明Ui::MedicalRecordQuery *ui;QTcpSocket *client = nullptr;QByteArray receivedData; // 用于存储接收的数据QTimer *queryTimeoutTimer = nullptr; // 查询超时定时器
};#endif // MEDICALRECORDQUERY_H
5.myqlabel.h
#ifndef MYQLABEL_H
#define MYQLABEL_H#include <QObject>
#include <QLabel>class myQlabel : public QLabel
{Q_OBJECT
public:myQlabel();explicit myQlabel(QWidget *parent);protected:void resizeEvent(QResizeEvent* event);
};#endif // MYQLABEL_H
6.mytablelist.h
#ifndef MYTABLELIST_H
#define MYTABLELIST_H#include <QObject>
#include <QAbstractTableModel>class myTablelist : public QAbstractTableModel
{Q_OBJECT
public:myTablelist();
};#endif // MYTABLELIST_H
7.mythread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H#include <QObject>
#include <QThread>
#include <QDebug>
#include <QApplication>
#include <QScreen>
#include <QPixmap>class myThread : public QThread
{Q_OBJECT
public:myThread(QObject* parent = nullptr);void stopping();void reset();protected:virtual void run() override;signals:void sndDesk(QPixmap pic);void threadstopped();private:QScreen* screen;volatile bool stopped; //判断是否停止线程
};#endif // MYTHREAD_H
8.pack.h
#ifndef PACK_H
#define PACK_H#include <cstring>
#include <string>
#include <QTcpSocket>enum Type{TYPE_REGIST,TYPE_LOGIN,TYPE_FILE_UPLOAD_REQUEST,TYPE_FILE_UPLOADING,TYPE_FILE_UPLOAD_END,TYPE_SENDING_CAMERA,TYPE_SEND_CAMERA_END,TYPE_SENDING_DESKTOP,TYPE_SEND_DESKTOP_END,TYPE_MEDICAL_RECORD_ADD, // 添加病历TYPE_MEDICAL_RECORD_QUERY, // 查询病历TYPE_MEDICAL_RECORD_UPDATE, // 更新病历TYPE_TEXT,TYPE_MEDICAL_RECORD_DELETE, // 删除病历TYPE_SENDING_AUDIO, // 发送音频数据TYPE_SEND_AUDIO_END // 音频数据发送结束};// 重新添加这个常量定义,以兼容服务器端
#define TYPE_QUERY_MEDICAL_RECORD 12// 协议包发给服务器之后,服务器要处理,如果处理成功,服务器发给客户端的协议包里面的Back,就是SUCCESS
enum Back{BACK_SUCCESS,BACK_ERR
};class Pack
{
private:int pack_size = 12;// 记录整个协议包大小,方便服务器知道当前发过去的协议包有多大Type type;Back back;char buf[4096] = "";int used = 0;
public:Pack();void append(const QString& val);// 将外部的字符串数据,写入协议包的buf里面void append(const char* p,int size);QStringList readAll();// 读取协议包的buf所有字符串QByteArray readAll(int size);Pack& operator<<(const QString& val);Pack& operator<<(const std::string& val); // 添加std::string重载声明void operator>>(QTcpSocket* client);void setType(Type type);void setBack(Back back);void setSize(int size);int size(); // pack_size的get接口Type getType();Back getBack();// 添加std::string兼容接口void append(const std::string& val) { append(val.c_str(), val.size()); }
};#endif // PACK_H
9.petinfoinput.h
#ifndef PETINFOINPUT_H
#define PETINFOINPUT_H#include <QWidget>
#include <QTcpSocket>namespace Ui {
class petinfoinput; // 修改为全小写,与UI文件一致
}class petinfoinput : public QWidget
{Q_OBJECTpublic:explicit petinfoinput(QWidget *parent = nullptr);~petinfoinput();void setClient(QTcpSocket *client);signals:void returnToMedicalRecordQuery(); // 添加返回到病历查询窗口的信号private slots:void on_pushButton_submit_clicked();void on_pushButton_back_clicked(); // 添加返回按钮槽函数声明void on_pushButton_clicked();private:Ui::petinfoinput *ui;QTcpSocket *client = nullptr;
};#endif // PETINFOINPUT_H
10.schedule.h
#ifndef SCHEDULE_H
#define SCHEDULE_H#include <QDialog>namespace Ui {
class schedule;
}class schedule : public QDialog
{Q_OBJECTpublic:explicit schedule(QWidget *parent = 0);~schedule();private slots:void on_pushButton_save_clicked();void on_pushButton_update_clicked();void on_pushButton_find_clicked();void on_pushButton_del_clicked();private:Ui::schedule *ui;
};#endif // SCHEDULE_H
11.userinterface.h
#ifndef USERINTERFACE_H
#define USERINTERFACE_H#include <QWidget>
#include <QDragEnterEvent>
#include <QDropEvent>
#include <QMimeData>
#include <QFile>
#include <QFileDialog>
#include <QBuffer>
#include <QImageReader>#include "pack.h"
#include "abs.h"
#include "mythread.h"
#include "audioinput.h"
#include "audiooutput.h"#include "medicalrecordquery.h" // 添加这行代码namespace Ui {
class Userinterface;
}class Userinterface : public QWidget
{Q_OBJECTpublic:explicit Userinterface(QWidget *parent = nullptr);~Userinterface();void setLoginInterface(QWidget* w); // 用来设置接受登录界面指针的一个函数void setClient(QTcpSocket* client);void setcurrentUsername(QString name){currentUsername=name;}
private slots:void on_pushButton_clicked();void on_pushButton_2_clicked();void on_pushButton_3_clicked();void on_pushButton_4_clicked();void on_pushButton_5_clicked();void on_pushButton_6_clicked();void on_pushButton_7_clicked(); // 确保这行存在void on_pushButton_8_clicked();void on_pushButton_9_clicked();protected:virtual void dragEnterEvent(QDragEnterEvent *event) override;virtual void dropEvent(QDropEvent *event) override;public slots:void recvImage(QImage& image);// 用来接受Abs 通过信号发来的图片的槽函数void recvDesk(QPixmap pic);void textshow(QString currentUsername,QString text);void handleAudioData(const QByteArray& audioData);// 处理音频数据的槽函数void recvCameraData(const QByteArray& data, bool isEnd); // 接收摄像头视频数据void recvDesktopData(const QByteArray& data, bool isEnd);MedicalRecordQuery* getMedicalrecordquery();
private:Ui::Userinterface *ui;QWidget* loginInterface;QTcpSocket* client;QFile file;Abs* abs;myThread* thread;QString currentUsername;AudioInput* audioInput;AudioOutput* audioOutput;QByteArray cameraBuffer; // 摄像头视频数据缓存QByteArray desktopBuffer;MedicalRecordQuery *medicalRecordQuery;};#endif // USERINTERFACE_H
12.widget.h
#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>
#include <QTcpSocket>
#include <QHostAddress>
#include <QMessageBox>
#include <QFile>
#include <QDebug>#include "pack.h"
#include "userinterface.h"
#include "schedule.h"QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACEclass Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();private slots:void on_pushButton_clicked();void on_pushButton_2_clicked();void on_pushButton_3_clicked();void on_pushButton_4_clicked();public slots:void onReadyRead();void checkInput();protected:void showEvent(QShowEvent *event) override;private:Ui::Widget *ui;QTcpSocket* client; // 私有的客户端套接字QHostAddress ip;int port;QString currentUsername;Userinterface* userinterface;schedule* userschedule;Pack readed_pack;int readed_pack_size = 0;int unreaded_pack_size = 0;int readed_size = 0;int readed_size_size = 0;int unreaded_size_size = 0;int flag=0;//看是否登录宠物端;
};
#endif // WIDGET_H
Sources
1.abs.cpp
#include "abs.h"Abs::Abs(QObject *parent):QAbstractVideoSurface(parent)
{QCameraInfo info = QCameraInfo::defaultCamera();// 用来获取当前设备当中,默认摄像头的信息//QList<QCameraInfo> info_list = QCameraInfo::availableCameras();// 获取当前设备中,所有可用的摄像头信息camera = new QCamera(info,this);// 根据获取到的摄像头信息,构建一个摄像头管理对象 camera,该对象未来用来打开或关闭摄像头camera->setViewfinder(this);// 将camera拍摄到的画面,交给当前类(Abs类)处理,只要调用了这条代码,摄像头拍摄到的画面,就会自动进入 Abs::present函数中}// 摄像头拍摄到的画面,会自动进入present函数,所以我们要在present函数里面写一些简单的处理逻辑
bool Abs::present(const QVideoFrame &frame)
{/*如何处理摄像头拍摄到的画面1:我们要将摄像头拍摄到的画面,发送给userinterface可以在 userinterface里面写 函数,用来获取 Abs 里面的图片也可以直接通过信号和槽,Abs类通过信号,将图片发送给 userinterface2:我们的最终目的,是将图片显示在label上面,label里面,用来设置图片的函数,叫做 setPixmap(QPixmap pic)所以我们要做的第2件事情:将QVideoFrame类型的图片,转换成 QPixmapQVideoFrame 和 QPixmap 之间,不存在直接转换的路径必须借助一个类 QImage,QImage非常好用,他可以将各种各样不同的类型的图片,都转换成QImage因为QImage 的构造函数,只需要提供:图片首地址,图片宽度,图片高度,图片每行字节数,图片像素格式而这些数据,是一个图片的基本核心数据,不管什么类,只要是图片,都会提供这些数据而QImage 可以直接转换成 QPixmap*/QVideoFrame fm = frame;// 拷贝构造一下fm.map(QAbstractVideoBuffer::ReadOnly);// 上面2条代码唯一的目的,就是为了100%保证,视频流里面的像素数据,允许被访问// 将QVideoFrame 图片转换成 QImageQImage image(fm.bits(),fm.width(),fm.height(),fm.bytesPerLine(),QImage::Format_RGB32);image = image.mirrored(1,1);// 参数2个1表示:横向、纵向都需要镜像一下emit sndImage(image); // 将图片通过信号发送给 userinterface
}// Abs构造函数调用的时候会自动调用这个函数
// 功能是设置Abs类允许处理的像素格式
// 一般来说,通用摄像头拍摄到的画面,他的像素格式 是 rgb32的
// 如果我们不设置的话,Abs 在处理摄像头画面的时候会报错,并告诉用户需要设置 支持 XXX 格式的像素
QList<QVideoFrame::PixelFormat> Abs::supportedPixelFormats(QAbstractVideoBuffer::HandleType type) const
{QList<QVideoFrame::PixelFormat> list; // 创建一个允许处理的像素格式的列表list << QVideoFrame::Format_RGB32;// 向列表中添加 rgb32 像素格式return list;// 向外返回列表,外部或自动获取列表的数据,并设置Abs类允许处理这些像素格式的图片
}void Abs::camera_start()
{camera->start();
}void Abs::camera_stop()
{camera->stop();
}
2.audioinput.cpp
#include "audioinput.h"
#include <QAudioDeviceInfo>AudioInput::AudioInput(QObject *parent): QObject(parent)
{// 获取默认音频输入设备QAudioDeviceInfo info = QAudioDeviceInfo::defaultInputDevice();// 创建音频格式对象并设置参数QAudioFormat format;format.setSampleRate(44100); // 设置采样率format.setChannelCount(1); // 设置通道数format.setSampleSize(16); // 设置样本大小(位)format.setCodec("audio/pcm"); // 设置编码格式format.setByteOrder(QAudioFormat::LittleEndian); // 设置字节顺序format.setSampleType(QAudioFormat::SignedInt); // 设置样本类型// 检查设备是否支持当前格式if (!info.isFormatSupported(format)) {qDebug() << "当前音频格式不被设备支持,尝试使用设备的最近格式";format = info.nearestFormat(format);qDebug() << "使用格式: 采样率=" << format.sampleRate() << ", 声道数=" << format.channelCount()<< ", 位深度=" << format.sampleSize();}input = new QAudioInput(info, format);// 设置缓冲区大小(可以减少延迟和噪音)input->setBufferSize(4096);
}void AudioInput::start()
{io = input->start();QObject ::connect(io,&QIODevice::readyRead,this,&AudioInput::onReadyRead);
}void AudioInput::onReadyRead()
{QByteArray arr = io->readAll();emit sndVoice(arr);
}
3.audiooutput.cpp
#include "audiooutput.h"
#include <QAudioDeviceInfo>AudioOutput::AudioOutput(QObject *parent) : QObject(parent)
{// 获取默认音频输出设备QAudioDeviceInfo info = QAudioDeviceInfo::defaultOutputDevice();// 创建音频格式对象并设置参数QAudioFormat format;format.setSampleRate(44100); // 设置采样率format.setChannelCount(1); // 设置通道数format.setSampleSize(16); // 设置样本大小(位)format.setCodec("audio/pcm"); // 设置编码格式format.setByteOrder(QAudioFormat::LittleEndian); // 设置字节顺序format.setSampleType(QAudioFormat::SignedInt); // 设置样本类型// 检查设备是否支持当前格式if (!info.isFormatSupported(format)) {qDebug() << "当前音频格式不被输出设备支持,尝试使用设备的最近格式";format = info.nearestFormat(format);qDebug() << "输出使用格式: 采样率=" << format.sampleRate() << ", 声道数=" << format.channelCount()<< ", 位深度=" << format.sampleSize();}output = new QAudioOutput(info, format);// 设置缓冲区大小output->setBufferSize(4096);io = output->start();
}void AudioOutput::outPutVoice(const QByteArray &arr)
{io->write(arr);
}
4.main.cpp
#include "widget.h"#include <QApplication>int main(int argc, char *argv[])
{QApplication a(argc, argv);// 应用级统一字体:SimHei 10pt BoldQFont appFont("SimHei");appFont.setPointSize(10);appFont.setWeight(QFont::Bold);a.setFont(appFont);Widget w;w.show();return a.exec();
}
5.medicalrecordquery.cpp
#include <string> // 为std::string提供支持#include "medicalrecordquery.h"
#include "ui_medicalrecordquery.h"
#include <QDebug>
#include <QTimer>
#include "pack.h"
#include "petinfoinput.h" // 新增头文件,用于跳转到宠物信息输入页面
#include <QTcpSocket>
#include <QFile>
#include <QTimer>
#include <QFileInfo>
#include <QResizeEvent> // 确保包含此头文件
#include <QShowEvent> // 添加 QShowEvent 支持
#include <QMessageBox> // 新增头文件,用于显示消息框
#include <QApplication> // 新增:用于QApplication::activeWindow()
#include <QHeaderView> // 新增:用于QHeaderView::Stretch
// 在文件开头的头文件引用部分添加以下内容
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlError>
#include <QDebug> // 用于调试输出
#include <QJsonDocument> // 添加 JSON 文档支持
#include <QJsonArray> // 添加 JSON 数组支持
#include <QHostAddress> // 添加 QHostAddress 支持MedicalRecordQuery::MedicalRecordQuery(QWidget *parent) :QWidget(parent),ui(new Ui::MedicalRecordQuery),client(nullptr),receivedData(QByteArray()) // 初始化 receivedData
{ui->setupUi(this);// 设置窗口图标 - 简化处理,只使用资源文件this->setWindowIcon(QIcon(":/pet_hospital.png"));// 加载样式表 - 简化处理,不强制要求样式文件QFile styleFile(":/medicalrecordquery.qss");if (styleFile.open(QIODevice::ReadOnly)) {QString styleSheet = QLatin1String(styleFile.readAll());this->setStyleSheet(styleSheet);styleFile.close();}// 连接按钮信号和槽函数 - 添加Qt::UniqueConnection防止重复连接connect(ui->pushButtonQuery, &QPushButton::clicked, this, &MedicalRecordQuery::handleQueryButtonClicked, Qt::UniqueConnection);connect(ui->pushButtonReturn, &QPushButton::clicked, this, &MedicalRecordQuery::on_pushButtonReturn_clicked, Qt::UniqueConnection);//bool connected = connect(ui->pushButton_2, &QPushButton::clicked, this, &MedicalRecordQuery::on_pushButton_2_clicked, Qt::UniqueConnection);//qDebug() << "填写病历按钮连接状态: " << connected;// // 优化:增加基础连接框架,借鉴widget.cpp的连接方式
// // 即使client为nullptr也先建立连接框架
// if (client) {
// connect(client, &QTcpSocket::readyRead, this, &MedicalRecordQuery::onReadyRead, Qt::UniqueConnection);
// }}MedicalRecordQuery::~MedicalRecordQuery()
{delete ui;
}void MedicalRecordQuery::setClient(QTcpSocket *client)
{this->client = client;
// if (client) {
// // 强制重新连接以确保连接有效性
// disconnect(client, &QTcpSocket::readyRead, this, &MedicalRecordQuery::onReadyRead);
// connect(client, &QTcpSocket::readyRead, this, &MedicalRecordQuery::onReadyRead, Qt::UniqueConnection);
// // 添加连接状态监控
// connect(client, &QTcpSocket::disconnected, this, [this]() {
// qDebug() << "客户端连接断开";
// // 处理连接断开逻辑
// });// }
}void MedicalRecordQuery::drawTable(Pack &pack)
{qDebug() << "开始绘制表格";QStringList list = pack.readAll();int row = 0;for(int i=0;i<list.size();i+=6){row = ui->tableWidgetMedicalRecords->rowCount();ui->tableWidgetMedicalRecords->insertRow(row);QTableWidgetItem* item0 = new QTableWidgetItem(list[i]);QTableWidgetItem* item1 = new QTableWidgetItem(list[i+1]);QTableWidgetItem* item2 = new QTableWidgetItem(list[i+2]);QTableWidgetItem* item3 = new QTableWidgetItem(list[i+3]);QTableWidgetItem* item4 = new QTableWidgetItem(list[i+4]);QTableWidgetItem* item5 = new QTableWidgetItem(list[i+5]);ui->tableWidgetMedicalRecords->setItem(row,0,item0);ui->tableWidgetMedicalRecords->setItem(row,1,item1);ui->tableWidgetMedicalRecords->setItem(row,2,item2);ui->tableWidgetMedicalRecords->setItem(row,3,item3);ui->tableWidgetMedicalRecords->setItem(row,4,item4);ui->tableWidgetMedicalRecords->setItem(row,5,item5);}ui->labelStatus->setText(QString("搜索到%1条病历").arg(row+1));}void MedicalRecordQuery::handleQueryButtonClicked()
{// 获取宠物名称或IDQString input = ui->lineEditPetName->text();if (input.isEmpty()) {ui->labelStatus->setText("请输入宠物名称或ID");return;}// 增强的TCP连接状态检查if (!client) {ui->labelStatus->setText("未连接到服务器");qDebug() << "MedicalRecordQuery: 客户端套接字未初始化";return;}// 检查连接状态if (client->state() != QAbstractSocket::ConnectedState) {ui->labelStatus->setText("服务器连接已断开,请重新连接");qDebug() << "MedicalRecordQuery: 连接状态异常 - " << client->state();return;}// 检查套接字可写性if (!client->isWritable()) {ui->labelStatus->setText("网络连接不可写,请检查连接");qDebug() << "MedicalRecordQuery: 套接字不可写";return;}// 输出连接状态信息qDebug() << "MedicalRecordQuery: 连接状态正常 - 对端地址:" << client->peerAddress().toString()<< "端口:" << client->peerPort() << "本地端口:" << client->localPort();// 清空表格ui->tableWidgetMedicalRecords->clearContents();ui->tableWidgetMedicalRecords->setRowCount(0);// 设置表格列数和表头ui->tableWidgetMedicalRecords->setColumnCount(6);QStringList headers = {"ID", "宠物名称", "就诊日期", "症状", "就诊结果", "治疗方案"};ui->tableWidgetMedicalRecords->setHorizontalHeaderLabels(headers);// // 创建超时定时器
// if (queryTimeoutTimer) {
// delete queryTimeoutTimer;
// }
// queryTimeoutTimer = new QTimer(this);
// queryTimeoutTimer->setSingleShot(true);
// connect(queryTimeoutTimer, &QTimer::timeout, this, [this]() {
// ui->labelStatus->setText("查询超时,请重试");
// qDebug() << "查询超时,未在指定时间内收到服务器响应";
// delete queryTimeoutTimer;
// queryTimeoutTimer = nullptr;
// });// 发送查询请求到服务器Pack pack;pack.setType(TYPE_MEDICAL_RECORD_QUERY);// 使用QByteArray确保正确编码//QByteArray data = input.toUtf8();//pack.append(data.data(), data.size());pack << input;// 调试输出数据包信息qDebug() << "发送数据包:类型=" << pack.getType()<< " 大小=" << pack.size()<< " 内容=" << input;// 发送数据包// client->write((char*)&pack, pack.size());pack >> client;// // 确保数据立即发送
// client->flush();// // 发送后检查写入的字节数
// if (client->bytesToWrite() == 0) {
// qDebug() << "数据发送成功,无待写入数据";
// } else {
// qDebug() << "数据发送中,待写入字节数: " << client->bytesToWrite();
// }// // 启动超时定时器(5秒)
// queryTimeoutTimer->start(5000);// ui->labelStatus->setText("正在查询...");// // 添加额外的超时保护机制
// QTimer::singleShot(5000, this, [this]() {
// if (ui->labelStatus->text() == "正在查询...") {
// ui->labelStatus->setText("查询超时,请重试");
// qDebug() << "MedicalRecordQuery: 查询请求超时";
// }
// });// qDebug() << "MedicalRecordQuery: 查询请求已发送 - 查询内容:" << input;// this->setClient(client);
}// 返回按钮的槽函数
void MedicalRecordQuery::on_pushButtonReturn_clicked()
{
// // 在返回前断开readyRead信号连接,防止继续处理数据
// if (client) {
// disconnect(client, &QTcpSocket::readyRead, this, &MedicalRecordQuery::onReadyRead);
// qDebug() << "MedicalRecordQuery: 断开readyRead信号连接";
// }// 创建信号通知用户界面显示emit returnToUserInterface();this->close();// 延迟释放资源,确保信号能被接收QTimer::singleShot(100, this, &MedicalRecordQuery::deleteLater);
}void MedicalRecordQuery::on_pushButton_2_clicked()
{qDebug() << "=== 填写病历按钮被点击 ===";// // 在切换窗口前断开当前窗口与客户端的信号连接,避免数据处理冲突
// if (client) {
// disconnect(client, &QTcpSocket::readyRead, this, &MedicalRecordQuery::onReadyRead);
// qDebug() << "MedicalRecordQuery: 断开readyRead信号连接";
// }// 创建宠物信息录入窗口 - 设置为独立窗口(不设置父窗口)qDebug() << "正在创建petinfoinput窗口...";petinfoinput *petInputWindow = new petinfoinput(nullptr);qDebug() << "petinfoinput窗口创建完成,地址:" << petInputWindow;// 设置窗口属性,确保它作为独立窗口显示petInputWindow->setAttribute(Qt::WA_DeleteOnClose);petInputWindow->setWindowFlags(Qt::Window | Qt::WindowStaysOnTopHint);qDebug() << "窗口属性设置完成";// 连接返回信号,当petinfoinput窗口发射返回信号时,重新显示当前窗口connect(petInputWindow, &petinfoinput::returnToMedicalRecordQuery, this, [this]() {qDebug() << "接收到返回信号,重新显示病历查询窗口";this->show();this->raise();this->activateWindow();// 重新连接客户端的readyRead信号
// if (client) {
// connect(client, &QTcpSocket::readyRead, this, &MedicalRecordQuery::onReadyRead, Qt::UniqueConnection);
// qDebug() << "重新连接readyRead信号";
// }});// 如果有客户端连接,传递给新窗口if (client) {petInputWindow->setClient(client);qDebug() << "客户端连接已传递给petinfoinput窗口";} else {qDebug() << "警告:没有客户端连接可传递";}// 显示宠物信息录入窗口,并确保它在最前面qDebug() << "正在显示petinfoinput窗口...";petInputWindow->show();petInputWindow->raise();petInputWindow->activateWindow();qDebug() << "petinfoinput窗口显示命令已执行,窗口可见性:" << petInputWindow->isVisible();qDebug() << "petinfoinput窗口大小:" << petInputWindow->size();qDebug() << "petinfoinput窗口位置:" << petInputWindow->pos();// 隐藏当前窗口qDebug() << "正在隐藏当前窗口...";this->hide();qDebug() << "=== 窗口切换完成 ===";
}void MedicalRecordQuery::resizeEvent(QResizeEvent *event)
{QWidget::resizeEvent(event);// 可选:添加自定义的 resize 逻辑// 例如:根据窗口大小调整字体大小或列宽int columnCount = ui->tableWidgetMedicalRecords->columnCount();if (columnCount > 0) {int columnWidth = (ui->tableWidgetMedicalRecords->width() - 20) / columnCount;for (int i = 0; i < columnCount; ++i) {ui->tableWidgetMedicalRecords->setColumnWidth(i, columnWidth);}}
}void MedicalRecordQuery::processMedicalRecords(const QJsonArray &records)
{// 处理医疗记录的实现代码qDebug() << "Processing medical records count:" << records.size();// 添加具体的处理逻辑
}//void MedicalRecordQuery::handleReadyRead() {
// // 此函数不再使用,已合并到onReadyRead中
// onReadyRead();
//}// 在窗口显示时重新连接readyRead信号
void MedicalRecordQuery::showEvent(QShowEvent *event)
{// 调用父类的showEventQWidget::showEvent(event);// 检查client是否有效且未连接readyRead信号
// if (client) {
// // 断开可能存在的连接,然后重新连接
// disconnect(client, &QTcpSocket::readyRead, this, &MedicalRecordQuery::onReadyRead);
// connect(client, &QTcpSocket::readyRead, this, &MedicalRecordQuery::onReadyRead, Qt::UniqueConnection);
// qDebug() << "MedicalRecordQuery: 重新连接readyRead信号";
// }
}// 确保信号槽连接保持有效
//void MedicalRecordQuery::initConnection() {
// if (client) {
// // 连接readyRead信号到onReadyRead槽函数
// connect(client, &QTcpSocket::readyRead, this, &MedicalRecordQuery::onReadyRead, Qt::UniqueConnection);
// // 连接disconnected信号
// connect(client, &QTcpSocket::disconnected, this, [this]() {
// qDebug() << "客户端连接断开";
// ui->labelStatus->setText("服务器连接已断开");
// });
// }
//}void MedicalRecordQuery::on_pushButtonQuery_clicked()
{
// Pack pack;
// pack << ui->lineEditPetName->placeholderText();
// pack.setType(TYPE_MEDICAL_RECORD_QUERY);
// pack >> client;
}
6.myqlabel.cpp
#include "myqlabel.h"
#include <QFontMetrics>myQlabel::myQlabel()
{}myQlabel::myQlabel(QWidget *parent=nullptr):QLabel(parent)
{setWordWrap(true); //允许换行this->setAlignment(Qt::AlignCenter);
}void myQlabel::resizeEvent(QResizeEvent *event){Q_UNUSED(event);if(text().isEmpty()) return;int fontSize=100;QFont font=this->font();while(fontSize>1){font.setPointSize(fontSize);QFontMetrics fm(font);QRect textRect = fm.boundingRect(rect(), Qt::TextWordWrap, text());// 检查文字矩形是否小于等于标签矩形if (textRect.width() <= width() && textRect.height() <= height()) {break;}fontSize--;}setFont(font); // 应用计算后的字体大小}
7.mytablelist.cpp
#include "mytablelist.h"myTablelist::myTablelist()
{}
8.mythread.cpp
#include "mythread.h"myThread::myThread(QObject *parent): QThread(parent)
{screen = QApplication::primaryScreen();// 获取当前设备中,默认的屏幕设备,并用screen管理stopped=false;
}void myThread::stopping()
{stopped=true;
}void myThread::reset()
{stopped=false;
}void myThread::run()
{this->reset();while(1){QPixmap pic = screen->grabWindow(0);emit sndDesk(pic);this->usleep(50);//this->sleep(1);if(this->stopped==true){break;}}emit threadstopped();
}
9.pack.cpp
#include <string>
#include "pack.h"
#include <unistd.h> // 包含 usleep 函数的声明
#include <QDataStream>
#include <QDebug>using namespace std;Pack::Pack()
{}void Pack::setType(Type type)
{this->type = type;
}void Pack::setBack(Back back)
{this->back = back;
}void Pack::setSize(int size)
{pack_size = size;
}int Pack::size()
{return pack_size;
}Type Pack::getType()
{return type;
}Back Pack::getBack()
{return back;
}void Pack::append(const QString &val)
{
// QByteArray utf8Data = val.toUtf8();
// int len = utf8Data.size();// if (used + 2 + len > sizeof(buf)) {
// qDebug() << "Buffer overflow prevented!";
// return;
// }// *(short*)(buf+used) = (short)len;
// used += 2;// memcpy(buf+used, utf8Data.data(), len);
// used += len;// pack_size = 12 + used;const char* p = val.toStdString().data();short len = strlen(p);*(short*)(buf+used) = len;used += 2;memcpy(buf+used,p,len);used += len;pack_size = 12 + used;
}void Pack::append(const char *p, int size)
{memset(buf,0,4096);memcpy(buf, p, size);pack_size = 12 + size;
}QStringList Pack::readAll()
{
// QStringList list;
// int readed_size = 0;
// int total_size = used; // 使用实际使用的大小// while(readed_size < total_size){
// if (readed_size + 2 > total_size) break; // 确保有足够的空间读取size// short size = *(short*)(buf+readed_size);
// if(size <= 0 || readed_size + 2 + size > total_size){break;}
// readed_size += 2;// char temp[size + 1];
// memset(temp,0,size+1);
// memcpy(temp,buf+readed_size,size);
// readed_size += size;// QString str = QString::fromUtf8(temp);
// list << str;
// }
// return list;QStringList list;int readed_size = 0;while (1) {short size = *(short*)(buf + readed_size);qDebug() << "size = " << size ;if(size == 0){break;}readed_size += 2;char temp[size + 1];memset(temp,0,size+1);memcpy(temp,buf+readed_size,size);qDebug() << "temp = " << temp;readed_size += size;QString str = QString::fromStdString(temp);list << str;}return list;
}QByteArray Pack::readAll(int size)
{//QByteArray arr(buf); // arr {char* p} 构造函数拷贝的数据,只会从头拷贝到第一个结束符,非常容易出现数据不全的情况//return arr;return QByteArray::fromRawData(buf,size);
}Pack &Pack::operator<<(const QString &val)
{append(val);return *this;
}Pack &Pack::operator<<(const std::string &val)
{// 计算字符串长度int len = val.length();// 写入长度字段*(int*)(buf + used) = len;used += 4;// 写入字符串内容memcpy(buf + used, val.data(), len);used += len;// 更新包大小pack_size = 12 + used;return *this;
}void Pack::operator>>(QTcpSocket *client)
{
// // 使用QDataStream封装数据包,确保数据格式与服务器端匹配
// QByteArray buffer;
// QDataStream stream(&buffer, QIODevice::WriteOnly);// // 写入包头信息
// stream.writeRawData((char*)&pack_size, 4);
// stream.writeRawData((char*)&type, 4);
// stream.writeRawData((char*)&back, 4);// // 写入实际数据
// if (used > 0) {
// stream.writeRawData(buf, used);
// }// // 一次性发送完整数据包
// qint64 bytesWritten = client->write(buffer);// // 调试信息
// qDebug() << "Pack::operator>> - 发送数据包:"
// << "大小=" << pack_size
// << "类型=" << type
// << "实际写入字节=" << bytesWritten
// << "缓冲区大小=" << buffer.size();client->write((char*)this,pack_size);
}
10.petinfoinput.cpp
#include "petinfoinput.h"
#include "ui_petinfoinput.h"
#include <QFile>
#include <QPixmap>
#include <QDebug>
#include <QMessageBox>
#include <QTcpSocket>
#include "pack.h"petinfoinput::petinfoinput(QWidget *parent) :QWidget(parent),ui(new Ui::petinfoinput)
{ui->setupUi(this);qDebug() << "petinfoinput构造函数开始,parent:" << parent;// 设置窗口标题this->setWindowTitle("宠物信息录入");// 设置窗口大小和位置this->resize(600, 450);this->move(300, 200); // 设置窗口位置,避免被其他窗口遮挡// 设置窗口图标 - 使用资源路径QPixmap icon(":/pet_hospital.png");if (!icon.isNull()) {this->setWindowIcon(icon);qDebug() << "窗口图标加载成功(资源)";} else {qDebug() << "窗口图标加载失败(资源)";}// 加载样式表 - 使用资源路径QFile styleFile(":/petinfoinput.qss");if (styleFile.open(QIODevice::ReadOnly)) {this->setStyleSheet(QString::fromUtf8(styleFile.readAll()));styleFile.close();qDebug() << "样式表加载成功(资源)";} else {qDebug() << "样式表加载失败(资源): " << styleFile.errorString();}// 强制设置默认字体,确保字体族/大小/粗细按需求生效QFont defaultFont("SimHei");defaultFont.setPointSize(10);defaultFont.setWeight(QFont::Bold);this->setFont(defaultFont);// 覆盖UI文件里给按钮单独设置的字体,确保与全局一致if (ui->pushButton_back) ui->pushButton_back->setFont(defaultFont);if (ui->pushButton_submit) ui->pushButton_submit->setFont(defaultFont);// 连接返回按钮的信号和槽函数// 使用findChild动态查找返回按钮,避免编译错误QPushButton *backButton = findChild<QPushButton*>("pushButton_back");QHBoxLayout *horizontalLayout = findChild<QHBoxLayout*>("horizontalLayout_6");if (!backButton) {qDebug() << "UI中未找到返回按钮,将手动创建";if (horizontalLayout) {// 创建新的返回按钮backButton = new QPushButton("返回", this);backButton->setObjectName("pushButton_back");// 设置与提交按钮相同的样式backButton->setSizePolicy(ui->pushButton_submit->sizePolicy());backButton->setMinimumSize(ui->pushButton_submit->minimumSize());backButton->setFont(ui->pushButton_submit->font());// 添加到布局horizontalLayout->insertWidget(0, backButton);qDebug() << "手动创建返回按钮成功";} else {qDebug() << "未找到水平布局,无法创建返回按钮";}}// 如果找到了按钮或成功创建了按钮,连接信号和槽if (backButton) {connect(backButton, &QPushButton::clicked, this, &petinfoinput::on_pushButton_back_clicked, Qt::UniqueConnection);qDebug() << "返回按钮连接成功";}qDebug() << "petinfoinput窗口初始化完成,窗口大小:" << this->size();
}petinfoinput::~petinfoinput()
{delete ui;
}void petinfoinput::setClient(QTcpSocket *client)
{this->client = client;qDebug() << "petinfoinput: 客户端连接已设置";
}void petinfoinput::on_pushButton_submit_clicked()
{// 获取表单数据QString petId = ui->lineEditPetId->text().trimmed();QString petName = ui->lineEditPetName->text().trimmed();QString visitDate = ui->lineEditVisitDate->text().trimmed();QString symptoms = ui->lineEditSymptoms->text().trimmed();QString diagnosisResult = ui->lineEditDiagnosisResult->text().trimmed();QString treatmentPlan = ui->textEditTreatmentPlan->toPlainText().trimmed();// 验证必填字段if (petId.isEmpty() || petName.isEmpty() || visitDate.isEmpty()) {QMessageBox::warning(this, "输入错误", "宠物ID、病宠name和就诊日期为必填项!");return;}// 检查客户端连接if (!client || client->state() != QAbstractSocket::ConnectedState) {QMessageBox::warning(this, "连接错误", "未连接到服务器,请先登录!");return;}// 构建Pack数据包Pack pack;pack.setType(TYPE_MEDICAL_RECORD_ADD);// 按照服务器期望的顺序添加数据:name, data, sympton, result, treatpack << petName; // 宠物名称pack << visitDate; // 就诊日期pack << symptoms; // 症状pack << diagnosisResult; // 诊断结果pack << treatmentPlan; // 治疗方案// 发送数据包到服务器pack >> client;qWarning()<<"seclient:"<<client;qDebug() << "宠物病历数据已发送到服务器";qDebug() << "宠物名称:" << petName;qDebug() << "就诊日期:" << visitDate;qDebug() << "症状:" << symptoms;qDebug() << "诊断结果:" << diagnosisResult;qDebug() << "治疗方案:" << treatmentPlan;// 显示成功消息QMessageBox::information(this, "提交成功", "宠物病历信息已成功提交到服务器!");// 清空表单ui->lineEditPetId->clear();ui->lineEditPetName->clear();ui->lineEditVisitDate->clear();ui->lineEditSymptoms->clear();ui->lineEditDiagnosisResult->clear();ui->textEditTreatmentPlan->clear();
}// 添加返回按钮的槽函数实现
void petinfoinput::on_pushButton_back_clicked()
{qDebug() << "返回按钮被点击";// 发射信号通知返回到病历查询窗口emit returnToMedicalRecordQuery();// 关闭当前窗口this->close();
}void petinfoinput::on_pushButton_clicked()
{}
11.schedule.cpp
#include "schedule.h"
#include "ui_schedule.h"schedule::schedule(QWidget *parent) :QDialog(parent),ui(new Ui::schedule)
{ui->setupUi(this);
}schedule::~schedule()
{delete ui;
}void schedule::on_pushButton_save_clicked()
{}void schedule::on_pushButton_update_clicked()
{}void schedule::on_pushButton_find_clicked()
{}void schedule::on_pushButton_del_clicked()
{}
12.userinterface.cpp
#include "userinterface.h"
#include "ui_userinterface.h"// 用户界面的代码Userinterface::Userinterface(QWidget *parent) :QWidget(parent),ui(new Ui::Userinterface)
{ui->setupUi(this);this->setAcceptDrops(1);abs = new Abs(this);QObject::connect(abs,&Abs::sndImage,this,&Userinterface::recvImage);thread = new myThread(this);QObject::connect(thread,&myThread::sndDesk,this,&Userinterface::recvDesk);QObject::connect(thread,&myThread::threadstopped,ui->label_2,&QLabel::clear);// 初始化音频输入输出对象audioInput = new AudioInput(this);audioOutput = new AudioOutput(this);// 检查音频格式(修改为适配现有AudioInput类)QAudioDeviceInfo info(QAudioDeviceInfo::defaultInputDevice());QAudioFormat format = audioInput->input->format();if (!info.isFormatSupported(format)) {qWarning() << "当前音频格式不支持,使用最接近的格式";// 这里我们无法更改AudioInput的格式,因为它没有提供setFormat方法// 可以考虑在AudioInput类中添加setFormat方法来解决这个问题}QObject::connect(audioInput, &AudioInput::sndVoice, this, &Userinterface::handleAudioData);// 添加设置窗口图标的代码this->setWindowIcon(QIcon(":/pet_hospital.png"));// 添加加载UserInterface.qss样式文件的代码QFile file(":/UserInterface.qss"); // 修正路径,移除qrc:if(file.open(QFile::ReadOnly)){this->setStyleSheet(file.readAll());} else {qDebug() << "无法打开UserInterface.qss文件: " << file.errorString();}// 启用label_2的背景自动填充,使透明背景生效ui->label_2->setAutoFillBackground(true);medicalRecordQuery = new MedicalRecordQuery(nullptr);medicalRecordQuery->hide();
}Userinterface::~Userinterface()
{// 释放音频资源if (audioInput) {// 停止音频采集if (audioInput->input && audioInput->input->state() == QAudio::ActiveState) {audioInput->input->stop();}delete audioInput;audioInput = nullptr;}if (audioOutput) {delete audioOutput;audioOutput = nullptr;}delete ui;
}void Userinterface::setLoginInterface(QWidget *w)
{loginInterface = w;
}void Userinterface::setClient(QTcpSocket *client)
{this->client = client;
}// 返回登录界面
void Userinterface::on_pushButton_clicked()
{this->hide();this->client->disconnectFromHost();loginInterface->show();
}void Userinterface::dragEnterEvent(QDragEnterEvent *event)
{event->acceptProposedAction();
}void Userinterface::dropEvent(QDropEvent *event)
{QString filepath = event->mimeData()->urls()[0].toLocalFile();QString filename = filepath.split("/").last();qDebug() << filename;Pack p1,p2,p3;p1.setType(TYPE_FILE_UPLOAD_REQUEST);p2.setType(TYPE_FILE_UPLOADING);p3.setType(TYPE_FILE_UPLOAD_END);file.setFileName(filepath);file.open(QFile::ReadOnly);// ① 将文件名发给服务器p1 << filename;p1 >> client;// ② 将文件内容发给服务器while(1){QByteArray arr = file.read(4096);// 读取文件中4096字节的数据,存入arr里面if(arr.isEmpty()){break;}// 将 arr 放到协议包中发给服务器p2.append(arr.data(),arr.size());// 将arr中的数据,存入协议包p2p2 >> client;// p2发送给服务器}// ③ 发送一个结束通知给服务器,通知服务器文件传输完成p3 >> client;file.close();
}void Userinterface::on_pushButton_2_clicked()
{QString filepath = QFileDialog::getOpenFileName(this,"上传文件","D:/");QString filename = filepath.split("/").last();qDebug() << filename;Pack p1,p2,p3;p1.setType(TYPE_FILE_UPLOAD_REQUEST);p2.setType(TYPE_FILE_UPLOADING);p3.setType(TYPE_FILE_UPLOAD_END);file.setFileName(filepath);file.open(QFile::ReadOnly);p1 << filename;p1 >> client;while(1){QByteArray arr = file.read(4096);// 读取文件中4096字节的数据,存入arr里面if(arr.isEmpty()){break;}// 将 arr 放到协议包中发给服务器p2.append(arr.toStdString().data(),arr.size());// 将arr中的数据,存入协议包p2p2 >> client;// p2发送给服务器}p3 >> client;file.close();
}void Userinterface::recvImage(QImage &image)
{// 现在,参数image就是摄像头拍摄到的画面QImage lb_image = image.scaled(ui->label->size());// 将摄像头拍摄到的画面,大小适应成label的大小QPixmap pic = QPixmap::fromImage(lb_image);// 将lb_image 转换成 QPixmap 类型,因为label里面,只能显示QPximap类型的图片ui->label->setPixmap(pic);// 将 pic 设置到 label上去// 画面就在这发送了,画面发送的逻辑:将图片转换成字节流,写入协议包,通过协议包进行发送
// 结论就是:将QImage 转换成 QByteArrayQByteArray arr;// 创建一个字节流对象QBuffer buf(&arr); // 以 arr指向的地址为缓存区,创建buf,如果向buf中写入内容,相当于向arr中写入内容lb_image.save(&buf,"jpg",30/*最后一个参数为图片的转换质量,根据实际情况自己调节*/); // 将 图片 lb_image 保存到buf里面去。// 到此为止,图片的所有内容就转换成字节流保存到 arr里面了Pack pack;int image_size = arr.size(); // 图片总大小//qDebug() << image_size;int writed_size = 0;// 记录一帧已发送多少字节,方便计算什么时候发送了最后一包图片while(1){if(writed_size + 4096 >= image_size){// 如果在当前已发送图片字节数的基础上,再发送4096字节,超出图片总大小,说明本次发送是一帧图片的最后一包// 将图片 arr 存入协议包pack.setType(TYPE_SEND_CAMERA_END);pack.append(arr.data()+writed_size,image_size - writed_size);pack >> client;break;}else{// 一帧图片的前面几包pack.setType(TYPE_SENDING_CAMERA);pack.append(arr.data()+writed_size,4096);pack >> client;}writed_size += 4096;}
}void Userinterface::recvDesk(QPixmap pic)
{pic = pic.scaled(ui->label_2->size());ui->label_2->setPixmap(pic);/*在这里,套用发送摄像头画面的逻辑,将桌面画面发送给服务器*/QByteArray arr;QBuffer buf(&arr);pic.save(&buf,"jpg",30);Pack pack;int image_size=arr.size();int writed_size=0;while(1){if(writed_size+4096>=image_size){pack.setType(TYPE_SEND_DESKTOP_END);pack.append(arr.data()+writed_size,image_size-writed_size);pack>>client;break;}else{pack.setType(TYPE_SENDING_DESKTOP);pack.append(arr.data()+writed_size,4096);pack >> client;}writed_size+=4096;}
}void Userinterface::on_pushButton_3_clicked()
{abs->camera_start();
}void Userinterface::on_pushButton_4_clicked()
{abs->camera_stop();
}// 添加病历查询按钮的槽函数实现
void Userinterface::on_pushButton_7_clicked()
{// 创建MedicalRecordQuery实例,不设置父窗口if(medicalRecordQuery == nullptr)return;// 设置客户端连接medicalRecordQuery->setClient(this->client);// 连接返回信号connect(medicalRecordQuery, &MedicalRecordQuery::returnToUserInterface, this, &Userinterface::show);// 显示病历查询页面medicalRecordQuery->show();// 强制将窗口置于最前方medicalRecordQuery->raise();medicalRecordQuery->activateWindow();// 隐藏当前用户界面(视频界面)this->hide();
}
void Userinterface::on_pushButton_5_clicked()
{thread->start();
}void Userinterface::on_pushButton_6_clicked()
{thread->stopping();thread->wait();ui->label_2->clear();}void Userinterface::on_pushButton_8_clicked()
{QString mytext = ui->lineEdit->text();Pack pack;pack.setType(TYPE_TEXT);// 在自己的聊天窗口显示发送的消息QString targetUser = "all";pack << currentUsername << targetUser << mytext ;// 发送消息pack >> client;QString text=QString("%1: %2").arg(currentUsername).arg(mytext);ui->textEdit->append(text);// 清空输入框ui->lineEdit->clear();
}
void Userinterface::textshow(QString currentUsername,QString text)
{QString mytext=QString("%1: %2").arg(currentUsername).arg(text);ui->textEdit->append(mytext);
}//
void Userinterface::on_pushButton_9_clicked()
{// 启动/停止音频采集if (audioInput->input->state() != QAudio::ActiveState) {audioInput->start();qDebug() << "音频采集已启动";// 更换按钮图标为stop.png,设置固定尺寸40x40pxui->pushButton_9->setStyleSheet("border-image: url(:/stop.png); width: 40px; height: 40px;");} else {// 停止音频采集audioInput->input->stop();qDebug() << "音频采集已停止";// 恢复按钮图标为voice.png,设置固定尺寸40x40pxui->pushButton_9->setStyleSheet("border-image: url(:/voice.png); width: 40px; height: 40px;");}
}void Userinterface::handleAudioData(const QByteArray& audioData)
{// 处理音频数据 - 可以选择发送到服务器或本地播放// 方式1: 通过网络发送音频数据到服务器(采用分片发送机制)if(client && client->state() == QTcpSocket::ConnectedState) {Pack pack;int audio_size = audioData.size(); // 音频数据总大小int writed_size = 0; // 记录已发送多少字节while(1) {if(writed_size + 4096 >= audio_size) {// 如果在当前已发送音频字节数的基础上,再发送4096字节,超出音频总大小,说明本次发送是音频的最后一包pack.setType(TYPE_SEND_AUDIO_END);pack.append(audioData.data() + writed_size, audio_size - writed_size);pack >> client;break;} else {// 音频的前面几包pack.setType(TYPE_SENDING_AUDIO);pack.append(audioData.data() + writed_size, 4096);pack >> client;}writed_size += 4096;}qDebug() << "音频数据已分片发送到服务器,总大小:" << audioData.size() << "字节";}// 注释掉本地播放避免回音问题// 如果需要本地测试,可以取消注释下面的代码audioOutput->outPutVoice(audioData); qDebug() << "处理音频数据,大小:" << audioData.size() << "字节";
}void Userinterface::recvCameraData(const QByteArray& data, bool isEnd)
{// 追加数据到缓存cameraBuffer.append(data);// 如果是结束包,解析并显示图像if (isEnd) {QImage image;image.loadFromData(cameraBuffer, "jpg"); // 假设传输格式为JPGif (!image.isNull()) {QImage scaledImg = image.scaled(ui->label->size(), Qt::KeepAspectRatio);ui->label->setPixmap(QPixmap::fromImage(scaledImg));}// 清空缓存cameraBuffer.clear();}
}// 接收桌面视频数据并处理
void Userinterface::recvDesktopData(const QByteArray& data, bool isEnd)
{// 追加数据到缓存desktopBuffer.append(data);// 如果是结束包,解析并显示图像if (isEnd) {QBuffer buf(&desktopBuffer);QImageReader reader(&buf,"jpg");QImage image = reader.read();QPixmap pic = QPixmap::fromImage(image);pic = pic.scaled(ui->label_2->size());ui->label_2->setPixmap(pic);desktopBuffer.clear();}
}MedicalRecordQuery *Userinterface::getMedicalrecordquery()
{return medicalRecordQuery;
}
13.widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QPixmap>
#include <QShowEvent>
#include <QDebug>Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);client = new QTcpSocket(this);QObject::connect(client,&QTcpSocket::readyRead,this,&Widget::onReadyRead);ui->pushButton->setEnabled(false);ui->pushButton_2->setEnabled(false);QObject::connect(ui->lineEdit,&QLineEdit::textChanged,this,&Widget::checkInput);QObject::connect(ui->lineEdit_2,&QLineEdit::textChanged,this,&Widget::checkInput);// 关键:强制启用工具提示,即使按钮处于禁用状态ui->pushButton->setAttribute(Qt::WA_AlwaysShowToolTips);ui->pushButton_2->setAttribute(Qt::WA_AlwaysShowToolTips);checkInput();userinterface = new Userinterface;userschedule = new schedule;userinterface->setLoginInterface(this);userinterface->setClient(client);QFile file(":/LoginWidget.qss");if(file.open(QFile::OpenModeFlag::ReadOnly)){this->setStyleSheet(file.readAll());}// 修复图片路径,移除多余空格QPixmap pixmap(":/SPM.png");if(!pixmap.isNull()){//调整图片大小QPixmap scaledPixmap=pixmap.scaled(ui->label_2->size(),Qt::KeepAspectRatio,Qt::SmoothTransformation);ui->label_2->setPixmap(scaledPixmap);ui->label_2->setAlignment(Qt::AlignCenter);}//userschedule->show(); //schedule表格显示//ui->lineEdit_3->setText("192.168.108.88");//ui->lineEdit_4->setText("8888");ui->lineEdit->setText("1");ui->lineEdit_2->setText("1");
}Widget::~Widget()
{delete ui;
}// 注册
void Widget::on_pushButton_clicked()
{ip.setAddress(ui->lineEdit_3->text());port = ui->lineEdit_4->text().toInt();client->connectToHost(ip,port);if(client->waitForConnected(2000)){}else{QMessageBox::warning(this,"连接","连接失败,请检查网络");return;}QString name = ui->lineEdit->text();QString pswd = ui->lineEdit_2->text();Pack pack;pack.setType(TYPE_REGIST);pack << name << pswd;pack >> client;
}// 登录
void Widget::on_pushButton_2_clicked()
{ip.setAddress(ui->lineEdit_3->text());port = ui->lineEdit_4->text().toInt();qDebug()<< port << ip;client->connectToHost(ip,port);if(client->waitForConnected(2000)){}else{QMessageBox::warning(this,"连接","连接失败,请检查网络");return;}QString name = ui->lineEdit->text();QString pswd = ui->lineEdit_2->text();flag++;// 新增:判断用户名是否为"2",是则修改label文本if (flag != 0 && name != "1") {ui->label->setText("慧宠医疗(宠物端)");}if (name == "1") {ui->label->setText("慧宠医疗(医生端)");}currentUsername = name; // 保存当前用户名(新增)userinterface->setcurrentUsername(name);Pack pack;pack.setType(TYPE_LOGIN);pack << name << pswd;pack >> client;qWarning()<<"client: "<<client;
}// 连接服务器
void Widget::on_pushButton_3_clicked()
{ip.setAddress(ui->lineEdit_3->text());port = ui->lineEdit_4->text().toInt();client->connectToHost(ip,port);if(client->waitForConnected(2000)){//如果2秒内连接上服务器QMessageBox::information(this,"连接","连接服务器成功");}else{QMessageBox::warning(this,"连接","连接失败,请检查网络");}
}// 服务器向客户端发送数据的时候,会自动触发的一个函数
void Widget::onReadyRead()
{while (1) {int res = 0;int size = 0;Pack pack;if(unreaded_pack_size != 0){res = client->read((char*)&readed_pack + readed_pack_size,unreaded_pack_size);if(res != unreaded_pack_size){// 这里只要更新下已读大小和未读大小,保证下一次进入当前分支的时候,能够根据已读大小和未读大小,读取对应的数据以及存放到对应的地方readed_pack_size += res;unreaded_pack_size -= res;break;}pack = readed_pack;// 处理完分包记得 unreaded_pack_size = 0;unreaded_pack_size = 0;}else{if(unreaded_size_size != 0){client->read((char*)&readed_size + readed_size_size,unreaded_size_size);size = readed_size;unreaded_size_size = 0;}else{// else 部分为正常读取数据:先读4字节,再读剩余字节数,可能处理分包res = client->read((char*)&size,4);if(res == 0){break;}if(res != 4){readed_size_size = res;unreaded_size_size = 4 - res;memcpy(&readed_size,&size,readed_size_size);break;}}res = client->read((char*)&pack+4,size-4);if(res == 0){break;}pack.setSize(size);if(res != size-4){// 如果实际读取到的字节数 != 想要读取的字节数// 说明发生了分包readed_pack_size = res + 4;unreaded_pack_size = pack.size() - readed_pack_size;memcpy(&readed_pack,&pack,readed_pack_size);break;}}// 只处理非医疗记录查询的数据包,避免与MedicalRecordQuery重复处理// if (pack.getType() != TYPE_MEDICAL_RECORD_QUERY) {// 处理数据包switch (pack.getType()) {case TYPE_REGIST:{if(pack.getBack() == BACK_SUCCESS){QMessageBox::information(this,"注册","注册成功");}else{QMessageBox::critical(this,"注册","该账号已存在");}break;}case TYPE_LOGIN:{if(pack.getBack() == BACK_SUCCESS){QMessageBox::information(this,"登录","登录成功");// 断开readyRead信号连接,防止继续处理数据 zhanghangyu//disconnect(client, &QTcpSocket::readyRead, this, &Widget::onReadyRead);// 界面跳转userinterface->show();this->hide();}else{QMessageBox::critical(this,"登录","账号或密码错误");}break;}case TYPE_TEXT:{// 断开可能存在的连接,然后重新连接//disconnect(client, &QTcpSocket::readyRead, this, &Widget::onReadyRead);//connect(client, &QTcpSocket::readyRead, this, &Widget::onReadyRead);qDebug() << "Widget: 重新连接readyRead信号";QList<QString> list = pack.readAll();QString sender = list[0];QString receiver = list[1];QString text = list[2];qDebug() << "收到消息:" << sender << "->" << receiver << ":" << text;userinterface->textshow(sender,text);}case TYPE_SENDING_CAMERA:{// 发送非结束包数据userinterface->recvCameraData(QByteArray(pack.readAll(pack.size() - 4).data(), pack.size() - 4), // 排除包头false);break;}case TYPE_SEND_CAMERA_END:{// 发送结束包数据userinterface->recvCameraData(QByteArray(pack.readAll(pack.size() - 4).data(), pack.size() - 4),true);break;}// 处理桌面共享视频数据case TYPE_SENDING_DESKTOP:{// 发送非结束包数据qDebug() << "接受部分桌面";userinterface->recvDesktopData(pack.readAll(pack.size()-12),false);break;}case TYPE_SEND_DESKTOP_END:{// 发送结束包数据qDebug() << "接受完整桌面";qDebug() << "pack.size - 12 = " << pack.size() - 12;userinterface->recvDesktopData(pack.readAll(pack.size()-12),true);break;}case TYPE_MEDICAL_RECORD_QUERY: {qDebug() << "接收到查询回包";qDebug() << userinterface->getMedicalrecordquery();userinterface->getMedicalrecordquery()->drawTable(pack);break;}default:qDebug() << "Widget: 收到未处理的数据包类型: " << pack.getType();break;}// } else {// qDebug() << "Widget: 跳过医疗记录查询数据包,由MedicalRecordQuery处理";// }}
}void Widget::checkInput()
{bool hasUsername=!ui->lineEdit->text().isEmpty();bool hasPassword=!ui->lineEdit_2->text().isEmpty();ui->pushButton->setEnabled(hasPassword&&hasUsername);ui->pushButton_2->setEnabled(hasPassword&&hasUsername);// 更新提示文本if (!hasUsername) {ui->pushButton->setToolTip("请输入用户名");ui->pushButton_2->setToolTip("请输入用户名");} else if (!hasPassword) {ui->pushButton->setToolTip("请输入密码");ui->pushButton_2->setToolTip("请输入密码");} else {ui->pushButton->setToolTip("");ui->pushButton_2->setToolTip("");}}// 在窗口显示时重新连接readyRead信号
void Widget::showEvent(QShowEvent *event)
{// 调用父类的showEventQWidget::showEvent(event);// 检查client是否有效且未连接readyRead信号if (client) {// 断开可能存在的连接,然后重新连接 zhanghangyu//disconnect(client, &QTcpSocket::readyRead, this, &Widget::onReadyRead);//connect(client, &QTcpSocket::readyRead, this, &Widget::onReadyRead);qDebug() << "Widget: 重新连接readyRead信号";}
}//右上角的链接数据库按钮
void Widget::on_pushButton_4_clicked()
{ip.setAddress(ui->lineEdit_3->text());port = ui->lineEdit_4->text().toInt();client->connectToHost(ip,port);if(client->waitForConnected(2000)){//如果2秒内连接上服务器QMessageBox::information(this,"连接","连接服务器成功");}else{QMessageBox::warning(this,"连接","连接失败,请检查网络");}
}

