两种方式分别查询 begintime 和 endtime 时间段内的前 count 条记录,和后 count 条记录 具体实现 //chatrecordmanager.h 9 / 33 #ifndef CHATRECORDMANAGER_H #define CHATRECORDMANAGER_H #include "mysql/mysql.h" #include #include #include #include #include #include "define.h" using namespace std; //聊天记录结构,用于保存和传送 struct chat_record { char username[20]; char content[MAX_MSG_SIZE]; time_t time; char ip[15]; }; typedef queue Records; //用队列来传递返回值 class ChatRecordManager { public: ChatRecordManager(); bool ConnectDB(char* username,char* password,char* db); void Disconnect(); bool InsertRecord(const char *username,const char *content,time_t time,const char *ip); Records QueryRecordsByBeginTime(time_t begintime,time_t endtime,int count); Records QueryRecordsByEndTime(time_t begintime,time_t endtime,int count); private: void TraceError(); Records QueryRecords(char* sql); MYSQL* m_conn_ptr; }; #endif // CHATRECORDMANAGER_H //chatrecordmanager.cpp #include "chatrecordmanager.h" ChatRecordManager::ChatRecordManager() { m_conn_ptr=0; 10 / 33 } //连接数据库 bool ChatRecordManager::ConnectDB(char *username, char *password, char *db) { m_conn_ptr=mysql_init(0); if(!m_conn_ptr) { cout<<"mysql_init failed!"<from_unixtime(%lu) and time<=from_unixtime(%lu) order by time limit 0,%d",begintime,endtime,count); return QueryRecords(sql); } //根据 sql 语句查询,返回查询结果 Records ChatRecordManager::QueryRecords(char *sql) { Records r; int res=mysql_query(m_conn_ptr,sql); if(res) { cerr<<"exec sql failed:"<from_unixtime(%lu) and time<=from_unixtime(%lu) order by time desc limit 0,%d) as t_temp order by time",begintime,endtime,count); return QueryRecords(sql); } 4.测试与调试 测试截图 客户端主界面, 客户端启动后,设置昵称和正确的服务器地址,点击上线后连接服务器。 成功连接服务后用户列表中显示当前在线用户 下方按钮显示未读的群聊信息数量,当收到新的私聊消息时用户列表中会出现提示 13 / 33 双击列表项, 弹出与该用户 对话的私聊窗 口;单击最下方按钮弹出群聊窗口 点击群聊窗口的历史记录,弹出群聊记录查询窗 口,设置查询条件,可以查询对应的群聊记录 服务端界面 显示当前在线客户端的详细信息和群聊消息 14 / 33 经过调试,基本解决了现有功能中的 bug 5.总结与感想 通过这次的课程设计的实现, 对于 linux 下的开发有了很大的改变, 不再单纯的做 window 下的开发。 附录:程序源码 限于篇幅,仅列出服务端非界面部分的代码,chatrecordmanager.h 和 chatreocordmanager.cpp 代码在之前已完整列出 //define.h 定义数据包的数据结构和一些常量 #ifndef DEFINE_H_INCLUDED #define DEFINE_H_INCLUDED #include #include #include using namespace std; #define SRV_PORT #define CLT_PORT 8118 8119 #define MAX_UDP_SIZE 1000 #define MAX_MSG_SIZE 150 struct udp_packet{ int type; int senderId; long size; char content[MAX_UDP_SIZE-8]; 15 / 33 }; //客户端上线请求 #define CLIENT_ONLINE_REQUST 1 struct client_info{ char username[20]; char hostname[30]; short client_port; }; #define CLIENT_ONLINE_ACK 2 struct server_ack { int client_id; }; //客户端发送群聊信息 #define GROUP_MSG_SEND 4 struct group_msg_send { char content[MAX_MSG_SIZE]; }; //服务端转发群聊信息 #define GROUP_MSG_BRODCAST 5 struct group_msg_broadcast { int userId; time_t time; char content[MAX_MSG_SIZE]; }; //客户端请求查询在线用户 #define QUERY_USER_INFO 6 struct query_user { int userId; }; //服务端发送用户信息 #define SEND_USER_INFO 7 struct user_info { int user_id; sockaddr_in useraddr; char username[20]; char hostname[30]; short port; }; //服务端用户信息变动通知,用户的上线和下线问题 16 / 33 #define USER_CHANGE 8 #define ACTION_USER_LOGIN struct user_change_info { char action; user_info user; }; #define PRIVATE_CHAT_MSG 9 struct private_chat_msg { int sender_id; time_t time; char content[MAX_MSG_SIZE]; }; //客户端查询聊天记录请求 #define QUERY_CHAT_RECORD 10 struct query_chat_record { int by_begin_time; 结束时间点最近的 count 条记录 time_t begintime; time_t endtime; int count; }; //服务器发送聊天记录 #define SEND_CHAT_RECORD 11 //聊天记录结构,用于保存和传送 struct chat_record { char username[20]; char content[MAX_MSG_SIZE]; time_t time; char ip[15]; }; typedef queue Records; #define CLIENT_OFF_LINE 12 #endif // DEFINE_H_INCLUDED //开始时间点 //结束时间点 //查询记录的条数 //查询方式,1 表示离开始时间点最近的 count 条记录,0 表示离 1 #define ACTION_USER_LOGOUT 2 //server.h #ifndef SERVER_H #define SERVER_H #include #include 17 / 33 #include #include #include #include "define.h" #include "client.h" #include #include #include #include #include #include "serverpacketprocessor.h" #include "chatrecordmanager.h" using namespace std; typedef map ClientCollection; class Server { friend class ServerPacketProcessor; public: virtual ~Server(); static Server* Instance(); bool Init(QObject *mainWindow); bool Start(); bool Stop(); bool Restart(); void WaitForServerStop(); void SendMsgTo(Client* c,const udp_packet* packet); void SendMsgTo(sockaddr_in clientaddr,const udp_packet* packet); void GetClients_lock(); map *GetClients(); void GetClients_unlock(); void GetMsgs_lock(); queue* GetMsgs(); void GetMsgs_unlock(); static const QEvent::Type MessageArrival=(QEvent::Type)3000; static const QEvent::Type ClientListChange=(QEvent::Type)3001; protected: Server(); void* ThreadStartRoutine(void* arg); ServerPacketProcessor* GetPacketProcessor(int packet_type); private: static void* ThreadRoutineTransponder(void* arg); void CleanUp(); void postEvent(QEvent::Type eventType); static Server* m_server; 18 / 33 sockaddr_in m_localaddr; int m_sockfd; pthread_t m_thread; bool bCancel; ClientCollection m_clients; queue m_msgQueue; pthread_mutex_t m_mutex_clients; pthread_mutex_t m_mutex_msgs; QObject* m_ui; ChatRecordManager m_chatRecordMgr; }; #endif // SERVER_H //server.cpp #include "server.h" Server* Server::m_server=0; Server::Server() { //ctor } Server::~Server() { //dtor CleanUp(); } Server* Server::Instance() { if(m_server==0) m_server=new Server(); return m_server; } bool Server::Restart() { if(!Stop()) return false; if(!Start()) return false; return true; } bool Server::Start() { m_sockfd=socket(AF_INET,SOCK_DGRAM,0); if(m_sockfd==-1) { 19 / 33 cerr<<"create udp socket failed!"<ProcessPacket(&packet,clientaddr); delete processor; } return 0; } void* Server::ThreadRoutineTransponder(void* arg) { return Server::Instance()->ThreadStartRoutine(arg); } void Server::WaitForServerStop() { pthread_join(m_thread,0); } ServerPacketProcessor* Server::GetPacketProcessor(int packet_type) { switch(packet_type) { case CLIENT_ONLINE_REQUST: { return new CltOnlinePacketProcessor(this); } case GROUP_MSG_SEND: { return new GroupMsgPacketProcessor(this); } case QUERY_USER_INFO: { return new QueryUserInfoPacketProcessor(this); } 21 / 33 case QUERY_CHAT_RECORD: { return new QueryRecordPacketProcessor(this); } case CLIENT_OFF_LINE: { return new CltOfflinePacketProcessor(this); } default: return 0; } } void Server::SendMsgTo(Client* c,const udp_packet* packet) { sockaddr_in clientaddr=c->ClientAddr(); clientaddr.sin_port=htons(c->Port()); SendMsgTo(clientaddr,packet); } void Server::SendMsgTo(sockaddr_in clientaddr,const udp_packet* packet) { cout<<"send data to:"<size,0,(struct sockaddr*)&clientaddr,sizeof(clientaddr)); } void Server::GetClients_lock() { pthread_mutex_lock(&m_mutex_clients); } void Server::GetClients_unlock() { pthread_mutex_unlock(&m_mutex_clients); } map* Server::GetClients() { return &m_clients; } void Server::GetMsgs_lock() { pthread_mutex_lock(&m_mutex_msgs); } void Server::GetMsgs_unlock() { pthread_mutex_unlock(&m_mutex_msgs); 22 / 33 } queue* Server::GetMsgs() { return &m_msgQueue; } void Server::CleanUp() { pthread_mutex_destroy(&m_mutex_clients); pthread_mutex_destroy(&m_mutex_msgs); } void Server::postEvent(QEvent::Type eventType) { QCoreApplication::postEvent(m_ui,new QEvent(eventType)); } //msg.h #ifndef MSG_H #define MSG_H #include "define.h" #include #include "client.h" class Msg { public: Msg(Client *client, char *content,time_t time); Msg(Client* client,string content,time_t time); Client* GetClient(); string Content(); time_t Time(); private: Client* m_client; string m_content; time_t m_time; }; #endif // MSG_H //msg.cpp #include "msg.h" #include "client.h" Msg::Msg(Client* client,char* content, time_t time) { m_client=client; m_content=string(content); 23 / 33 m_time=time; } Msg::Msg(Client *client, string content,time_t time) { m_client=client; m_content=content; m_time=time; } Client* Msg::GetClient() { return m_client; } string Msg::Content() { return m_content; } time_t Msg::Time() { return m_time; } //client.h #ifndef CLIENT_H #define CLIENT_H #include #include #include #include #include using namespace std; class Client { public: Client(char* username,char* hostname,sockaddr_in clientaddr,short port, int id); void UpdataClientInfo(char* username,char* hostname); virtual ~Client(); string HostName(); string ClientIP(); string UserName(); sockaddr_in ClientAddr(); short Port(); int ClientID(); private: string m_hostname; 24 / 33 sockaddr_in m_clientaddr; string m_username; short m_port; int m_id; }; #endif // CLIENT_H //client.cpp #include "client.h" Client::Client(char* username,char* hostname,sockaddr_in clientaddr, short port,int id) { m_hostname=string(hostname); m_username=string(username); m_clientaddr=clientaddr; m_port=port; m_id=id; } int Client::ClientID() { return m_id; } short Client::Port() { return m_port; } void Client::UpdataClientInfo(char *username, char *hostname) { m_hostname=string(hostname); m_username=string(username); } Client::~Client() { //dtor } string Client::HostName() { return m_hostname; } string Client::ClientIP() { return string(inet_ntoa(m_clientaddr.sin_addr)); } string Client::UserName() { 25 / 33 return m_username; } sockaddr_in Client::ClientAddr() { return m_clientaddr; } //serverpacketprocessor.h 定义 ServerPacketProcessor 及其子类 #ifndef SERVERPACKETPROCESSOR_H #define SERVERPACKETPROCESSOR_H #include #include "define.h" #include using namespace std; class Server; class Client; typedef map ClientCollection; class ChatRecordManager; class ServerPacketProcessor { public: ServerPacketProcessor(Server* server); void ProcessPacket(const udp_packet* packet,sockaddr_in addr); private: virtual void ProcessPacket_Core(const udp_packet *packet, sockaddr_in addr)=0; protected: Server* m_server; ClientCollection* m_clients; ChatRecordManager* m_chatRecordMgr; void PostEvent(QEvent::Type eventType); }; class CltOnlinePacketProcessor:public ServerPacketProcessor { public: CltOnlinePacketProcessor(Server* server):ServerPacketProcessor(server){}; private: void ProcessPacket_Core(const udp_packet *packet,sockaddr_in addr); }; class GroupMsgPacketProcessor:public ServerPacketProcessor { public: GroupMsgPacketProcessor(Server* server):ServerPacketProcessor(server){}; private: void ProcessPacket_Core(const udp_packet *packet,sockaddr_in addr); 26 / 33 }; class QueryUserInfoPacketProcessor:public ServerPacketProcessor { public: QueryUserInfoPacketProcessor(Server* server):ServerPacketProcessor(server){} private: void ProcessPacket_Core(const udp_packet *packet, sockaddr_in addr); }; class QueryRecordPacketProcessor:public ServerPacketProcessor { public: QueryRecordPacketProcessor(Server* server):ServerPacketProcessor(server){} private: void ProcessPacket_Core(const udp_packet *packet, sockaddr_in addr); }; class CltOfflinePacketProcessor:public ServerPacketProcessor { public: CltOfflinePacketProcessor(Server* server):ServerPacketProcessor(server){} private: void ProcessPacket_Core(const udp_packet *packet, sockaddr_in addr); }; #endif // SERVERPACKETPROCESSOR_H //serverpacketprocessor.cpp //ServerPacketProcessor 及其子类的实现 #include "serverpacketprocessor.h" #include "server.h" #include "client.h" #include "msg.h" #include "chatrecordmanager.h" ServerPacketProcessor::ServerPacketProcessor(Server *server) { this->m_server=server; this->m_chatRecordMgr=&m_server->m_chatRecordMgr; } void ServerPacketProcessor::PostEvent(QEvent::Type eventType) { m_server->postEvent(eventType); } void ServerPacketProcessor::ProcessPacket(const udp_packet *packet, sockaddr_in addr) { 27 / 33 m_server->GetClients_lock(); m_clients=m_server->GetClients(); ProcessPacket_Core(packet,addr); m_server->GetClients_unlock(); } void CltOfflinePacketProcessor::ProcessPacket_Core(const udp_packet *packet, sockaddr_in addr) { if(m_clients->count(packet->senderId)==0) return; Client* c=(*m_clients)[packet->senderId]; m_clients->erase(packet->senderId); delete c; user_change_info uc_info; uc_info.action=ACTION_USER_LOGOUT; uc_info.user.user_id=packet->senderId; udp_packet p; p.type=USER_CHANGE; p.senderId=1; memcpy(p.content,&uc_info,sizeof(uc_info)); p.size=12+sizeof(uc_info); for(ClientCollection::iterator it=m_clients->begin();it!=m_clients->end();it++) { Client* c=it->second; m_server->SendMsgTo(c,&p); } PostEvent(Server::ClientListChange); } void CltOnlinePacketProcessor::ProcessPacket_Core(const udp_packet *packet,sockaddr_in addr) { client_info* info=(client_info*)packet->content; //用户上线, cout<<"new client login:"<username<<"|"<hostname<<"|"<count(id)!=0) { id=rand()%9999+1; } (*m_clients)[id]=new Client(info->username,info->hostname,addr,info->client_port,id); PostEvent(Server::ClientListChange); udp_packet p; 28 / 33 p.type=CLIENT_ONLINE_ACK; p.senderId=1; server_ack d; d.client_id=id; memcpy(p.content,&d,sizeof(d)); p.size=12+sizeof(d); m_server->SendMsgTo(addr,&p); //向其它用户发送改用户的信息 user_change_info uc_info; uc_info.action=ACTION_USER_LOGIN; memcpy(uc_info.user.hostname,info->hostname,30); memcpy(uc_info.user.username,info->username,20); uc_info.user.user_id=id; uc_info.user.useraddr=addr; uc_info.user.port=info->client_port; udp_packet p1; p1.type=USER_CHANGE; p1.senderId=1; memcpy(p1.content,&uc_info,sizeof(uc_info)); p1.size=12+sizeof(uc_info); for(ClientCollection::iterator it=m_clients->begin();it!=m_clients->end();it++) { Client* c=it->second; m_server->SendMsgTo(c,&p1); } } void GroupMsgPacketProcessor::ProcessPacket_Core(const udp_packet *packet, sockaddr_in addr) { time_t t=time(0); //保存系统当前时间 group_msg_send* msg=(group_msg_send*)packet->content; cout<<"recive group msg:"<content<<" from:"<count(packet->senderId)==0) { //不存在改客户端,或改客户端未登录 return; } Client* c=(*m_clients)[packet->senderId]; //保存消息到队列 m_server->GetMsgs_lock(); m_server->GetMsgs()->push(Msg(c,msg->content,t)); m_server->GetMsgs_unlock(); PostEvent(Server::MessageArrival); 29 / 33 //保存聊天记录到数据库 m_chatRecordMgr->InsertRecord(c->UserName().c_str(),msg->content,t,c->ClientIP().c_ str()); //转发消息 group_msg_broadcast msg_broadcast; strncpy(msg_broadcast.content,msg->content,MAX_MSG_SIZE); msg_broadcast.userId=packet->senderId; msg_broadcast.time=t; udp_packet p; p.type=GROUP_MSG_BRODCAST; p.size=12+sizeof(msg_broadcast); memcpy(p.content,&msg_broadcast,sizeof(msg_broadcast)); for(map::iterator it=m_clients->begin();it!=m_clients->end();it++) { if(packet->senderId==it->second->ClientID()) continue; m_server->SendMsgTo(it->second,&p); } } void QueryUserInfoPacketProcessor::ProcessPacket_Core(const udp_packet *packet, sockaddr_in addr) { cout<<"client request user.."<content; if(qu->userId!=0) { Client* c=(*m_clients)[qu->userId]; if(c==0) return; user_info info; strncpy(info.hostname,c->HostName().c_str(),30); strncpy(info.username,c->UserName().c_str(),20); info.useraddr=c->ClientAddr(); info.user_id=c->ClientID(); info.port=c->Port(); udp_packet p; p.type=SEND_USER_INFO; p.senderId=1; memcpy(p.content,&info,sizeof(info)); p.size=12+sizeof(info); m_server->SendMsgTo(addr,&p); } else 30 / 33 for(ClientCollection::iterator it=m_clients->begin();it!=m_clients->end();it++) { Client* c=it->second; user_info info; info.useraddr=c->ClientAddr(); strncpy(info.hostname,c->HostName().c_str(),30); strncpy(info.username,c->UserName().c_str(),20); info.user_id=c->ClientID(); info.port=c->Port(); udp_packet p; p.type=SEND_USER_INFO; memcpy(p.content,&info,sizeof(info)); p.size=12+sizeof(info); m_server->SendMsgTo(addr,&p); } } void QueryRecordPacketProcessor::ProcessPacket_Core(const udp_packet *packet, sockaddr_in addr) { query_chat_record* msg=(query_chat_record*)packet->content; Records r; if(msg->by_begin_time==1) r=m_chatRecordMgr->QueryRecordsByBeginTime(msg->begintime,msg->endtime,msg->count); else if(msg->by_begin_time==0) r=m_chatRecordMgr->QueryRecordsByEndTime(msg->begintime,msg->endtime,msg->count); while(!r.empty()) { chat_record record=r.front(); r.pop(); udp_packet p; p.type=SEND_CHAT_RECORD; p.senderId=1; memcpy(p.content,&record,sizeof(record)); p.size=12+sizeof(record); m_server->SendMsgTo(addr,&p); } //最后一条表示聊天记录发送结束 chat_record endflag; endflag.time=0; udp_packet p; p.type=SEND_CHAT_RECORD; 31 / 33 p.senderId=1; memcpy(p.content,&endflag,sizeof(endflag)); p.size=12+sizeof(endflag); m_server->SendMsgTo(addr,&p); } 32 / 33