项目简介
本项目实现了用户的基本阅读功能。项目内容涉及到IO,网络编程,C++,QT等知识点。本次项目服务器搭建在ubuntu上,客户端ui在QT中实现,客户端和服务器使用套接字通信。
一、基本功能展示
(1)界面展示
本次项目主要有九个界面,具体结构如下,其中不乏界面的相互跳转,和跨结构跳转
(2)登录注册功能
本项目的登录注册功能除了最基本的保存账号密码,还实现了对登录人的身份判断和登录状态判断。
(3)主界面和个人界面
在主界面中,可以浏览到当前库存的书籍,并且可以通过直接点击书籍,将书籍的名字自动填充到行编辑器中。
在个人界面中,可以进入个人的收藏以及个人的浏览记录
两个界面之间可以相互跳转,在当前界面时无法点击切换到自己界面的按钮
(4)预览界面
在预览界面中,主要展示书籍的信息,并且可以将当前书籍添加到喜欢,也可以直接开始阅读
(5)阅读界面
在阅读界面中,用户可以自己设置字体,可以通过上一页,下一页浏览整本书,可以将文本朗读出来
(6)喜欢和记录界面
在喜欢和记录界面,存放着用户的收藏和历史阅读记录,用户可以通过类似主界面的操作进入阅读界面,阅读自己选中的书籍
(7)管理员界面界面
在管理员啊界面,展示着当前所有的书籍,管理员可以修改书籍的书名以及描述。当管理员选中表中内容时,会自动将表中选中的当前行的内容展示到行编辑器中。底下四个按钮分别可以实现对书籍的增删改查
二、相关原理介绍
(1)数据库结构
本次项目的数据库结构一共分为三个数据库, 第一个数据库存放着user表和book表,分别存放着用户和书籍的信息,在user表中除了最基本的账号(主键)、密码,还存在这flag和state选项。 flag用于判断登录账号的身份,如果是1则身份为管理员,登录后跳转到管理员界面,如果是0,则为普通用户,跳转到主界面。 state用于判断当前用户是否在线,在登录时会将state置为1,表示已经在线,此时便不可登录,在退出时会置为0,表示可以登录 还有两个数据库分别是“收藏”和“喜欢”数据库,在普通用户登录之后会自动以他们的用户名为名创建表单,分别存放用户收藏的书籍和用户的历史记录
更新数据库信息代码
int updatebook(sqlite3 *ub,const char *oldbn,const char *newbn,const char *des)
{char sql[111]="";sprintf(sql,"update book set decribe = \"%s\" where name = \"%s\"",des,oldbn);char *errmsg = NULL;if(sqlite3_exec(ub,sql,NULL,NULL,&errmsg)!=SQLITE_OK){printf("delbook error\n");sqlite3_free(errmsg);return -1;}bzero(sql,sizeof(sql));sprintf(sql,"update book set name = \"%s\" where name = \"%s\"",newbn,oldbn);errmsg = NULL;if(sqlite3_exec(ub,sql,NULL,NULL,&errmsg)!=SQLITE_OK){printf("delbook error\n");sqlite3_free(errmsg);return -1;}return 0;
}
(2)阅读原理
本次项目的阅读界面使用了IO的知识,并没有将书籍的内容直接放入数据库,而是以普通文本文件储存,在用户阅读时使用系统IO将数据从文本文件读取出来,传输至客户端。在客户端为了实现翻页的效果,所以需要对传输过来的数据分段。这里使用的是标准模板库中的list容器。在传输过来时,先将内容以两百个字节的大小存入list容器。在阅读界面翻页时只需要清空textbrowser中的数据读取容器中数据显示到组件上就实现了翻页的效果.
阅读的界面相关代码
void read::preview2read(QString username,QString bookname)
{articial.clear();ui->textBrowser->clear();this->bookname = bookname;this->username = username;cli.connectToHost(ip,port.toUInt());this->show();struct msg m;strcpy(m.regmsg.flag,"1006");QByteArray qbusername = username.toLatin1();QByteArray qbbookname = bookname.toLatin1();strcpy(m.regmsg.username,qbusername.data());strcpy(m.regmsg.password,qbbookname.data());cli.write((char *)&m,sizeof(m));
}
void read::slot_readyread()
{struct msg m;cli.read((char*)&m,sizeof(m));QString buf(m.regmsg.password);articial.push_back(buf);ui->textBrowser->setText(articial.at(0));
}
(3)数据通信原理
本项目使用结构体作为信息载体,实现了服务器和客户端的数据交换。使用消息标志位判断消息是谁发的,发给谁。
(4)组件通信原理
本次组件的通信通过信号与槽的连接进行通信。在涉及到界面和界面跳转时需要进行的通信,则将信息存放在信号和槽函数的参数中进行传输。例如,当进入我的收藏界面时我需要我的用户名才能找到我的收藏,所以在登录的时候就将用户名通过界面跳转的信号与槽函数一级一级传输过来
界面跳转之间的连接代码
QObject::connect(&w,&Widget::login2register,&r,®::login2register);QObject::connect(&r,®::sig_register2login,&w,&Widget::register2login);QObject::connect(&w,&Widget::login2home,&h,&home::login2home);QObject::connect(&h,&home::home2preview,&p,&Preview::home2preview);QObject::connect(&p,&Preview::preview2read,&re,&read::preview2read);QObject::connect(&h,&home::home2mine,&m,&mine::home2mine);QObject::connect(&m,&mine::mine2home,&h,&home::mine2home);QObject::connect(&m,&mine::mine2favor,&f,&favor::mine2favor);QObject::connect(&f,&favor::favor2read,&re,&read::preview2read);QObject::connect(&rec,&record::record2read,&re,&read::preview2read);QObject::connect(&m,&mine::mine2record,&rec,&record::mine2record);QObject::connect(&w,&Widget::login2admin,&ad,&admin::login2admin);
(5)内容展示原理
本项目使用qtablewidget组件对服务器传来的书本信息进行展示。在服务器端,使用sqlite3_get_table得到列表。由于书本的信息只有书名和描述两种属性,所以在传输过程中,使用结构体存放每一行数据,在客户端接收到对应消息后,增加一行存放接收到的消息数据。
获取数据库表单内容代码
//获取表单内容
int get_booktable(int sfd,sqlite3 *ub,const char *table_name)
{char **res = NULL;int rows = 0;int cols = 0;char *errmsg = NULL;char sql[111]="";sprintf(sql,"select * from \"%s\"",table_name);if(sqlite3_get_table(ub,sql,&res,&rows,&cols,&errmsg)!=SQLITE_OK){printf("get table error\n");sqlite3_free(errmsg);return -1;}char buf[111]="";int i,j;for(i=0;i<rows;i++){struct msg m;strcpy(m.regmsg.flag,"9001");strcpy(m.regmsg.username,*(res+(i+1)*cols));strcpy(m.regmsg.password,*(res+(i+1)*cols+1));send(sfd,&m,sizeof(m),0);usleep(50000);}return 0;
}
接收并展示到qtablewidget中代码
void home::slot_readyread()
{struct msg m;cli.read((char*)&m,sizeof(m));if(strcmp(m.regmsg.flag,"9001")==0){int row = ui->tableWidget->rowCount();ui->tableWidget->setRowCount(row + 1);QString name(m.regmsg.username);QString des(m.regmsg.password);QTableWidgetItem *item1 = new QTableWidgetItem(name);QTableWidgetItem *item2 = new QTableWidgetItem(des);ui->tableWidget->setItem(row,0,item1);ui->tableWidget->setItem(row,1,item2);}if(strcmp(m.regmsg.flag,"9006")==0){cli.disconnectFromHost();this->close();}
}
三、服务器端主函数代码
#include "header.h"
int main(int argc, const char *argv[])
{sqlite3 *ub = NULL;sqlite3 *favor = NULL;sqlite3 *record = NULL;if(sqlite3_open("./userandbook.db",&ub)!=SQLITE_OK){printf("%s\n",sqlite3_errmsg(ub));return -1;}if(sqlite3_open("./favor.db",&favor)!=SQLITE_OK){printf("%s\n",sqlite3_errmsg(ub));return -1;}if(sqlite3_open("./record.db",&record)!=SQLITE_OK){printf("%s\n",sqlite3_errmsg(ub));return -1;}sqlite_init(ub,favor,record);//初始化数据库//创建套接字int sfd = socket(AF_INET,SOCK_STREAM,0);if(sfd == -1){perror("socket:");return -1;}//端口快速重用int reuse = 1;if(setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse))==-1){perror("setsockaddr error:");return -1;}//绑定信息结构体struct sockaddr_in sin;sin.sin_family=AF_INET;sin.sin_port=htons(SER_PORT);sin.sin_addr.s_addr=inet_addr(SER_IP);if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin))==-1){perror("bind error:");return -1;}//设置监听if(listen(sfd,128)==-1){perror("listen error:");return -1;}//定义客户端信息结构体struct sockaddr_in cin;socklen_t socklen=sizeof(cin);//IO多路复用struct pollfd pfd[1024];int n = 1;pfd[0].fd=sfd;pfd[0].events=POLLIN;while(1){int res=poll(pfd,n,-1);if(res==0){printf("manba out\n");return -1;}else if(res==-1&&errno!=4){perror("poll error:");return -1;}if(pfd[0].revents==POLLIN){int newfd=accept(sfd,(struct sockaddr*)&cin,&socklen);pfd[n].fd=newfd;pfd[n].events=POLLIN;n++;printf("[%s:%d:%d]已连接\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),newfd);}for(int i = 1;i<n;i++){if(pfd[i].revents == POLLIN){struct msg m;recvfrom(pfd[i].fd,&m,sizeof(m),0,(struct sockaddr*)&cin,&socklen);if(strcmp(m.regmsg.flag,"1001")==0)//注册信息{int res = reg(ub,favor,record,m.regmsg.username,m.regmsg.password);if(res==0){char buf[111]="";strcpy(buf,"success");send(pfd[i].fd,buf,sizeof(buf),0);}else{char buf[111]="";strcpy(buf,"fail");send(pfd[i].fd,buf,sizeof(buf),0);}}if(strcmp(m.regmsg.flag,"1002")==0)//登录信息{int res = login(ub,favor,record,m.regmsg.username,m.regmsg.password);char buf[111]="";if(res==-2){strcpy(buf,"fail1");//用户名不存在}else if(res==-3){strcpy(buf,"fail");//密码错误}else if(res == -8){strcpy(buf,"fail2");//重复登录}else if(res == 1){strcpy(buf,"success1");//管理员登录}else if(res == 0){strcpy(buf,"success");//成功登陆}send(pfd[i].fd,buf,sizeof(buf),0);}if(strcmp(m.regmsg.flag,"1003")==0){get_booktable(pfd[i].fd,ub,"book");}if(strcmp(m.regmsg.flag,"1004")==0){get_book(pfd[i].fd,ub,m.regmsg.username);}if(strcmp(m.regmsg.flag,"1005")==0){int res = addfavor(ub,favor,m.regmsg.username,m.regmsg.password);struct msg m;strcpy(m.regmsg.flag,"9003");if(res == 0){strcpy(m.regmsg.username,"success");}else if(res == -1){strcpy(m.regmsg.username,"fail");}send(pfd[i].fd,&m,sizeof(m),0);}if(strcmp(m.regmsg.flag,"1006")==0){get_bookcontent(pfd[i].fd,record,m.regmsg.username,m.regmsg.password);}if(strcmp(m.regmsg.flag,"1007")==0){get_favortable(pfd[i].fd,favor,m.regmsg.username);}if(strcmp(m.regmsg.flag,"1008")==0){get_recordtable(pfd[i].fd,record,m.regmsg.username);}if(strcmp(m.regmsg.flag,"1010")==0){int res = userexit(ub,m.regmsg.username);struct msg m;strcpy(m.regmsg.flag,"9006");if(res == 0){strcpy(m.regmsg.username,"success");}else{strcpy(m.regmsg.username,"fail");}send(pfd[i].fd,(char *)&m,sizeof(m),0);}if(strcmp(m.regmsg.flag,"1011")==0){get_booklist(pfd[i].fd,ub,"book");}if(strcmp(m.regmsg.flag,"1012")==0){int res = addbook(ub,m.regmsg.username,m.regmsg.password);struct msg m;strcpy(m.regmsg.flag,"9008");if(res == 0){strcpy(m.regmsg.username,"success");}else{strcpy(m.regmsg.username,"fail");}send(pfd[i].fd,(char*)&m,sizeof(m),0);}if(strcmp(m.regmsg.flag,"1013")==0){int res = delbook(ub,m.regmsg.username);struct msg m;strcpy(m.regmsg.flag,"9009");if(res == 0){strcpy(m.regmsg.username,"success");}else{strcpy(m.regmsg.username,"fail");}send(pfd[i].fd,(char*)&m,sizeof(m),0);}if(strcmp(m.regmsg.flag,"1014")==0){int res = updatebook(ub,m.regmsg.str,m.regmsg.username,m.regmsg.password);struct msg m;strcpy(m.regmsg.flag,"9010");if(res == 0){strcpy(m.regmsg.username,"success");}else{strcpy(m.regmsg.username,"fail");}send(pfd[i].fd,(char*)&m,sizeof(m),0);}}}}return 0;
}