基于C/S架构的在线阅读器

news2024/10/24 9:19:57

项目简介

        本项目实现了用户的基本阅读功能。项目内容涉及到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,&reg::login2register);
    QObject::connect(&r,&reg::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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1497963.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

关于制作一个Python小游戏(三)

目录 前言: 在前面我们已经了解过了关于制作pygame的使用和在里面游戏中的简单操作的内容了,今天我们主要讲的就是关于敌机的出现和如何去操控游戏中英雄飞机和敌机的出现 1.敌机的设计: 1.1敌机出场的实现: 1.1.1游戏启动后,每个一秒钟出现一架敌方飞机 1.1.2每架敌机向屏…

ETL与抖音数据同步,让数据流动无阻

在当今数字化时代&#xff0c;数据的价值日益凸显&#xff0c;企业需要从各种渠道获取有关用户行为、市场趋势和竞争对手活动的数据。作为一家专注于数据集成和转换的领先平台&#xff0c;ETLCloud为企业提供了强大的数据同步和转换功能。而与此同时&#xff0c;抖音作为一款热…

vcomp140.dll丢失如何修复,5种修复方法轻松搞定vcomp140.dll问题

vcomp140.dll文件的丢失可能会引发一系列系统运行与软件功能上的问题。具体来说&#xff0c;这个动态链接库文件是Visual C Redistributable的一部分&#xff0c;对于许多基于此环境开发的应用程序至关重要。一旦缺失&#xff0c;可能会导致部分应用程序无法正常启动或运行&…

深入浅出(二)MVVM

MVVM 1. 简介2. 示例 1. 简介 2. 示例 示例下载地址&#xff1a;https://download.csdn.net/download/qq_43572400/88925141 创建C# WPF应用(.NET Framework)工程&#xff0c;WpfApp1 添加程序集 GalaSoft.MvvmLight 创建ViewModel文件夹&#xff0c;并创建MainWindowV…

SAP Parallel Accounting(平行分类账业务)配置及操作手册(超详细的说明和测试)

SAP Parallel Accounting(平行分类账业务)配置及操作手册 1、Overview 为了适应不同的会计准则&#xff0c;SAP在新总账中启用了多分类账&#xff0c;&#xff08;其作用简单来说就是&#xff0c;同时一笔记账&#xff0c;会产生多个账套的凭证。&#xff09;分类账可以对应一…

一文掌握:电力管理系统该的功能和界面设计

一、什么电力管理系统 电力管理系统是一个用于监控、控制和优化电力系统运行的软件系统。它集成了实时数据采集、数据分析、决策支持和远程控制等功能&#xff0c;旨在提高电力系统的运行效率、可靠性和安全性。 电力管理系统是一个集成了数据采集、监控、分析和控制等功能的软…

应用层协议--HTTP

目录 一.HTTP是什么&#xff1f; 二.HTTP的请求和响应 a.请求&#xff1a; b.响应&#xff1a; 三.URL 四.Header 1.Host 2. Content-Length 3. Content-Type a. 请求 b. 响应 4. Referer 5. User-Agent 6. Cookie 一.HTTP是什么&#xff1f; HTTP是一种应用层协议&#xff0c…

Java并发编程-实现多线程的四种方式

创建线程的四种方式 创建线程的四种方式包括使用继承 Thread 类、实现 Runnable 接口、使用 Callable 和 Future 接口以及利用线程池。每种方式都有其特定的优势和适用场景。通过继承 Thread 类或实现 Runnable 接口&#xff0c;可以定义线程要执行的任务&#xff0c;并通过调用…

图形系统开发实战课程:进阶篇(上)——10.应用实例:交通路网

图形开发学院&#xff5c;GraphAnyWhere 课程名称&#xff1a;图形系统开发实战课程&#xff1a;进阶篇(上)课程章节&#xff1a;“图形样式”原文地址&#xff1a;https://www.graphanywhere.com/graph/advanced/2-10.html 第十章 应用实例&#xff1a;交通路网 \quad 在前面几…

Spring学习 基础(一)

Spring基础 IoC容器&#xff08;Inversion of Control&#xff09;: Spring 的核心是其控制反转&#xff08;IoC&#xff09;容器&#xff0c;它负责管理对象的生命周期和相互之间的依赖关系。通过依赖注入&#xff08;Dependency Injection&#xff09;&#xff0c;Spring能够…

python 基础知识点(蓝桥杯python科目个人复习计划59)

今日复习内容&#xff1a;做题 例题1&#xff1a;建造房屋 问题描述&#xff1a; 小蓝和小桥是两位年轻的建筑师&#xff0c;他们正在设计一座新的城市。 在这个城市中&#xff0c;有N条街道&#xff0c;每条街道上有M个位置可以建造房屋&#xff08;一个位置只能建造一个房…

【java】后序遍历二叉树

采用递归方式实现 节点类 public class Node {private int value;//父节点private Node fNode;//左节点private Node left;//右节点private Node right;//是否已经打印过private boolean sign false;public Node() {}public boolean isSign() {return sign;}public void setS…

融资项目——OpenFeign的降级与熔断

当一个微服务调用其他微服务时&#xff0c;如果被调用的微服务因各种原因无法在规定时间内提供服务&#xff0c;则可以直接使用本地的服务作为备选&#xff0c;即进行降级熔断。 如之前所提到的微服务为例&#xff1a; 如果希望实现降级熔断&#xff0c;可以在本地创建一个实现…

【Spring云原生系列】Spring Cloud Stream:消息驱动架构(MDA)解析,实现异步处理与解耦合!

&#x1f389;&#x1f389;欢迎光临&#xff0c;终于等到你啦&#x1f389;&#x1f389; &#x1f3c5;我是苏泽&#xff0c;一位对技术充满热情的探索者和分享者。&#x1f680;&#x1f680; &#x1f31f;持续更新的专栏《Spring 狂野之旅&#xff1a;从入门到入魔》 &a…

HYBBS 表白墙网站PHP程序源码

安装教程 上传程序安装&#xff0c;然后设置账号密码&#xff0c;登陆后台切换模板手机PC都要换开启插件访问前台。 安装完成后如果不能正常访问就删除install安装文件夹 安装完成后右上角登录后点击头图进入后台 找到插件 安装表白墙配置插件 找到模板 将表白墙模板同时设…

18个惊艳的可视化大屏(第19辑):工业制造、智能工厂

实时监控和数据展示 可视化大屏可以集成和展示各种传感器、设备和系统的实时数据。通过将数据可视化展示在大屏上&#xff0c;工厂管理人员可以实时监控生产线的状态、设备的运行情况、生产效率等重要指标。这有助于及时发现问题、做出决策&#xff0c;并提高生产效率和质量。…

多模态入门

VIT处理图像 CNN VS Transformer 多模态BLIP模型 网络结构 视觉编码器: 就是 ViT 的架构。将输入图像分割成一个个的 Patch 并将它们编码为一系列 Image Embedding,并使用额外的 [CLS] token 来表示全局的图像特征。视觉编码器不采用之前的基于目标检测器的形式,因为 ViLT 和…

【Linux】权限管理(文件的访问者、类型和访问权限,chmod、chown、chgrp、umask,粘滞位)

目录 00.前言 01.文件访问者的分类 02.文件类型和访问权限 文件类型&#xff1a; 文件基本权限&#xff1a; 03.文件权限值的表示方法 04.访问权限的设置 &#xff08;1&#xff09;chmod &#xff08;2&#xff09;chown &#xff08;3&#xff09;chgrp &#xff0…

Redis安全加固策略:绑定Redis监听的IP地址 修改默认端口 禁用或者重命名高危命令

Redis安全加固策略&#xff1a;绑定Redis监听的IP地址 & 修改默认端口 & 禁用或者重命名高危命令 1.1 绑定Redis监听的IP地址1.2 修改默认端口1.3 禁用或者重命名高危命令1.4 附&#xff1a;redis配置文件详解&#xff08;来源于网络&#xff09; &#x1f496;The Beg…

启动Docker镜像时候,ENTRYPOINT 和CMD这两者指令的写法有什么不同和区别?

ENTRYPOINT和CMD在Dockerfile中都用于指定容器启动时执行的命令&#xff0c;但它们之间存在一些关键的区别和不同的用途&#xff1a; 1. 基本用途和行为差异 ENTRYPOINT 定义了容器启动时执行的基础命令&#xff0c;使得容器像一个可执行程序。ENTRYPOINT让你能够指定容器启动…