基于SQLite数据库的IO多路复用(epoll)实现TCP并发-员工管理系统{一个帐号只能一个人登录}

目录

  • 要求
    • 代码
      • 服务器
        • server.c
        • server.h
      • 客户端
        • client.c
        • client.h
    • 执行结果
  • 该客户端Shell的图形界面(很简单只是一个 ` 选择 `)
    • menu.sh
    • 开发板作为服务器
  • 本项目面试必问
    • epoll使用方法
    • epoll原理
      • 还有更简单的回答[可以用内核使用异步事件通知解释](https://mp.weixin.qq.com/s/naGheZq_z5d8pyB_i9hY7g)
    • epoll工作模式
      • 一、水平触发模式——LT
      • 二、边沿触发模式——ET
      • 三、边沿非阻塞模式
  • 问select/poll/epoll有什么区别
    • select:表
    • poll:结构体数组
    • epoll:(红黑树)
    • epoll优点:
    • epoll缺点:
    • atoi编写

要求

  • 服务端支持客户端远程登录TCP
  • 支持多用户同时访问( 服务器并发 IO多路复用 )
  • 服务端管理所有员工数据信息(用户分级管理<管理员、普通用户>)
  • 管理员可以对所有员工的数据信息进行增、删、改、查
  • 普通用户只可以查询自身信息数据 且 可以修改除薪资和职务以外的数据
  • 数据管理 sqlite3
  • 一个帐号在同一时间只能一个人登录( 如QQ另一设备登录前一设备退出 )

代码

服务器

server.c

#include "./server.h"

char xbuf[128];
LOG_SUCC login_success[256]={0};
int main(int argc, char const *argv[])
{
	//1.数据库初始化
	sqlite3 *sql_db=proc_init();
	//检验命令行参数个数
    if (3 != argc)
    {
        printf("Usage : %s  \n", argv[0]);
        exit(-1);
    }
	//3. 创建套接字-填充服务器网络信息结构体-绑定-监听
    int sockfd = socket_bind_listen(argv);

	//4. 用来保存 客户端 信息的结构体
    struct sockaddr_in client_addr;
    memset(&client_addr, 0, sizeof(client_addr));
    socklen_t client_addr_len = sizeof(client_addr);

	//epoll函数实现io多路复用
	//创建一个epoll
	int epfd=epoll_create(10);//内部数字没意义,填大于零数字即可
	if (-1==epfd)	ERRLOG("epoll_create error");

	struct epoll_event event;		//事件结构体
	event.events=EPOLLIN;//监控读;EPOLLIN读;EPOLLOUT写
	event.data.fd=sockfd;//监控的套接字;将sockfd套接字加入监控集合
	//                    -添加
	if (0!=epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&event))
		ERRLOG("epoll_ctl ADD error");
	
	struct epoll_event revents[256]={0};//监控的最大个数,创建监控表,用于epoll_wait函数使用
	int ret_epoll=0;//承接epoll_wait函数返回值,返回准备好的文件描述符个数
	int i=0;//用于循环筛选准备好的文件描述符
	int accept_fd;
	int ret;
	
	
	while (1)
	{
		ret_epoll= epoll_wait(epfd,revents,sizeof(revents),-1);// -1:不关心超时
		if(-1==ret_epoll)
			ERRLOG("epoll wait error");
		else
		{
			//for循环遍历监控表
			for ( i = 0; i <ret_epoll; i++)
			{
				if (revents[i].data.fd==sockfd)
				{
					//阻塞等待客户端连接--一旦有客户端连接就会解除阻塞
					accept_fd=accept(sockfd,(struct sockaddr *)&client_addr, &client_addr_len);
					if (-1==accept_fd)	ERRLOG("accept error");
					/* inet_ntoa 32位网络字节序二进制地址转换成点分十进制的字符串; ntohs将无符号2字节整型  网络-->主机 */
					printf("客户端[%s : %d]连接\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));
					event.events=EPOLLIN;//监控读;EPOLLIN读;EPOLLOUT写
					event.data.fd=accept_fd;//监控的套接字
					//                    -添加
					if (0!=epoll_ctl(epfd,EPOLL_CTL_ADD,accept_fd,&event))
						ERRLOG("epoll_ctl ADD error");
				}
				else
				{
					//说明有客户端发来消息了
                    accept_fd = revents[i].data.fd;
                    //接收客户端发来的数据
					ret=child_do(accept_fd,sql_db);
					if (88==ret)
					{
						//                    - 删除
						if (0!=epoll_ctl(epfd,EPOLL_CTL_DEL,accept_fd,&event))
							ERRLOG("epoll_ctl ADD error");
						close(accept_fd);
					}
				}
			}
		}
	}
	//关闭监听套接字  一般不关闭
    close(sockfd);
	return 0;
}

// 1. 数据库初始化 ---打开数据文件、建表
sqlite3 *proc_init(void)
{
	//1. 打开数据库文件
	sqlite3 *sql_db=NULL;
	int ret=sqlite3_open(FILEname,&sql_db);
	if (ret!=SQLITE_OK)
	{
		printf("打开数据库文件 失败");
        printf("errcode[%d]  errmsg[%s]\n", ret, sqlite3_errmsg(sql_db));
        exit(-1);
	}
	//2. 建表
    /* IF NOT EXISTS  表不存在则创建  表存在则直接使用,而不是报错;引号里面sql语句后面不用加分号*/
	//管理员-密码
	char sql_rbuf[256]="CREATE TABLE IF NOT EXISTS root(id INT PRIMARY KEY, pass TEXT)";
	ret=sqlite3_exec(sql_db,sql_rbuf,NULL,NULL,NULL);
	if (ret!=SQLITE_OK)
	{
		perror("建root表 失败");
        printf("errcode[%d]  errmsg[%s]\n", ret, sqlite3_errmsg(sql_db));
        exit(-1);
	}
	
	memset(sql_rbuf,0,sizeof(sql_rbuf));
	//使用查询函数
	sprintf(sql_rbuf,"SELECT * FROM root WHERE id=%d",123456);
	char **result={0};	// 结果集
	int row = 0;	// 行数
	int column = 0; // 列数
					/*
					会将 查询的到的结果的 行数写到row的地址里  列数写到column的地址里
					让 result 指向 sqlite3_get_table 申请的结果集的内存空间
					*/
	//查询结果的函数                             /结果集   /行数  /列数   /错误信息
	ret=sqlite3_get_table(sql_db,sql_rbuf,&result,&row,&column,NULL);
	if (ret!=SQLITE_OK)
	{
		perror("查询 失败");
		printf("errcode[%d]  errmsg[%s]\n", ret, sqlite3_errmsg(sql_db));
	}
	if (0==row)
	{
		memset(sql_rbuf,0,sizeof(sql_rbuf));
		sprintf(sql_rbuf,"INSERT INTO root(id,pass) VALUES(%d,'%s')",123456,"123456");
		ret=sqlite3_exec(sql_db,sql_rbuf,NULL,NULL,NULL);
		if (ret!=SQLITE_OK)
		{
			perror("注册 失败");
			printf("errcode[%d]  errmsg[%s]\n", ret, sqlite3_errmsg(sql_db));
		}

	}
	
	//用户-密码-性别-年龄-住址-薪资-职务
	char sql_ubuf[256]="CREATE TABLE IF NOT EXISTS user(id INT PRIMARY KEY,pass TEXT,name TEXT,sex TEXT,age INT,pay INT,job TEXT)";
	ret=sqlite3_exec(sql_db,sql_ubuf,NULL,NULL,NULL);
	if (ret!=SQLITE_OK)
	{
		perror("建user表 失败");
        printf("errcode[%d]  errmsg[%s]\n", ret, sqlite3_errmsg(sql_db));
        exit(-1);
	}
	
	return sql_db;
}

//3. 创建套接字-填充服务器网络信息结构体-绑定-监听
int socket_bind_listen(const char *argv[])
{
	// 1.创建套接字      /IPV4    /TCP
	int sockfd=socket(AF_INET,SOCK_STREAM,0);
	if (-1==sockfd)	ERRLOG("创建套接字 失败"); 

	// 2.填充服务器网络信息结构体
	struct sockaddr_in server_addr;
	memset(&server_addr,0,sizeof(server_addr));//清空
	server_addr.sin_family=AF_INET;			   // IPV4 
    /*端口号   atoi字符串转换成整型数; htons将无符号2字节整型  主机-->网络*/
	server_addr.sin_port=htons(atoi(argv[2]));
    /*ip地址;  inet_addr字符串转换成32位的网络字节序二进制值*/
	server_addr.sin_addr.s_addr=inet_addr(argv[1]);

	//结构体长度
	socklen_t server_addr_len=sizeof(server_addr);

	//3. 将套接字和网络信息结构体绑定
	if (-1==bind(sockfd,(struct sockaddr *)&server_addr,server_addr_len))
		ERRLOG("结构体绑定 失败"); 
	
	//4. 将套接字设置成被动监听状态
    if (-1 == listen(sockfd, 10))
        ERRLOG("listen error");
	
	return sockfd;
}

// 执行
int child_do(int accept_fd, sqlite3 *sql_db)
{
	MSG msg;
	memset(&msg,0,sizeof(MSG));
	if (0==recv(accept_fd, &msg, sizeof(MSG), 0))
	{
		printf("客户端 退出\n");
		delete_history(accept_fd);
		return 88;// 返回值用于退出判断
	}
	printf("type = %d\n",msg.type); //选择
	switch (msg.type)
	{
	case R: // 1. 注册
		do_register_s(accept_fd, &msg, sql_db);
		break;
	case L: // 2. 登录
		do_login_s(accept_fd, &msg, sql_db);
		break;
	case Q: // 3. 查询
		do_query_s(accept_fd, &msg, sql_db);
		break;
	case M: // 4.修改
		do_Modify_s(accept_fd, &msg, sql_db);
		break;
	case ROOT: //ROOT 登录
		root_login_s(accept_fd, &msg, sql_db);
		break;
	case D: //删除
		root_delete_s(accept_fd, &msg, sql_db);
		break;
	case Qall: // 查询所有
		all_query_s(accept_fd,sql_db);
		break;
	case Only: 
		all_only_s(accept_fd, &msg, sql_db);
		break;
	}
	return 0;
}

// 信号处理函数  用于回收子进程资源
void deal_signal(int s)
{
	//回收子进程资源
    wait(NULL); //阻塞回收
}

// 1. 注册
void do_register_s(int accept_fd, MSG *msg, sqlite3 *sql_db)
{
	char sql_buf[512];
	memset(sql_buf,0,sizeof(sql_buf));
    //使用sqlite3_exec函数调用插入函数判断是否能够插入成功
    //由于用户名设置为主键,所以如果用户名已经存在就会报错
    //大写,小写容易报错
	sprintf(sql_buf,"INSERT INTO user(id,pass) VALUES(%d,'%s')", msg->id, msg->pass);
	int ret=sqlite3_exec(sql_db,sql_buf,NULL,NULL,NULL);
	if (ret!=SQLITE_OK)
	{
		perror("注册 失败");
		printf("errcode[%d]  errmsg[%s]\n", ret, sqlite3_errmsg(sql_db));
		sprintf(msg->data, "ID %d 已经存在!!!", msg->id);
	}
	else
	{
		strcpy(msg->data, "注册成功");
	}
	 //发送数据
    if (0 >= send(accept_fd,msg, sizeof(MSG), 0))
        ERRLOG("send error");
}


int flag=0;	//是否已经登录 标志位
// 登录成功后唯一查询
void all_only_s(int accept_fd, MSG *msg, sqlite3 *sql_db)
{
	int i,id;
	id=msg->id;
	MSG tmsg;
	if (1==flag)
	{
		for (i = 0; i < 256; i++)
		{
			if (login_success[i].id==id&&(0!=login_success[i].ord_accept_fd))
			{
				strcpy(tmsg.data, "在另一台设备登录");
				//发送数据
				if (0 >= send(login_success[i].ord_accept_fd,&tmsg, sizeof(MSG), 0))
					ERRLOG("send error");
				//printf("--------------247---------------\n");
				break;
			}
		}
		flag=0;
	}
	if (0 >= send(accept_fd,&tmsg, sizeof(MSG), 0))
					ERRLOG("send error");
}

// 删除登录成功记录
void delete_history(int accept_fd)
{
	int i;
	for ( i = 0; i < 256; i++)
	{
		if (login_success[i].ord_accept_fd==accept_fd)
		{
			flag=0;
			login_success[i].ord_accept_fd=0;
			//printf("-----删除ord记录成功---------\n");
			break;
		}
		if (login_success[i].accept_fd==accept_fd)
		{
			flag=0;
			login_success[i].id=0;
			login_success[i].ord_accept_fd=0;
			login_success[i].accept_fd=0;
			//printf("-----删除new记录成功---------\n");
			break;
		}
	}
}
// 记录登录成功
void history(int accept_fd,int id)
{
	int i;
	for ( i = 0; i < 256; i++)
	{
		if (login_success[i].id==id)
		{
			// 已经登录
			flag=1;
			login_success[i].ord_accept_fd=login_success[i].accept_fd;
			login_success[i].accept_fd=accept_fd;
			//printf("[%d]已经登录\n",id);
			break;
		}
		if (0==login_success[i].id)
		{
			login_success[i].id=id;
			login_success[i].accept_fd=accept_fd;
			//printf("[%d]插入\n",id);
			break;
		}		
	}
}
// 2. 登录
void do_login_s(int accept_fd, MSG *msg, sqlite3 *sql_db)
{
	char sql_buf[256]={0};
	//使用查询函数判断是否存在
	sprintf(sql_buf,"SELECT * FROM user WHERE id=%d AND pass='%s'", msg->id, msg->pass);
	
	char **result;	// 结果集
	int row = 0;	// 行数
	int column = 0; // 列数
					/*
					会将 查询的到的结果的 行数写到row的地址里  列数写到column的地址里
					让 result 指向 sqlite3_get_table 申请的结果集的内存空间
					*/
	//查询结果的函数                             /结果集   /行数  /列数   /错误信息
	int ret=sqlite3_get_table(sql_db,sql_buf,&result,&row,&column,NULL);
	if (ret!=SQLITE_OK)
	{
		perror("查询 失败");
		printf("errcode[%d]  errmsg[%s]\n", ret, sqlite3_errmsg(sql_db));
	}
	if (0==row)
	{
		sprintf(msg->data,"ID[%d]与密码[%s]不C一致", msg->id,msg->pass);
	}
	else
    {
		history(accept_fd, msg->id);
		strcpy(msg->data, "登录成功");
    }
	//发送数据
    if (0 >= send(accept_fd, msg, sizeof(MSG), 0))
        ERRLOG("send error");
}

// 3. 查询
void do_query_s(int accept_fd, MSG *msg, sqlite3 *sql_db)
{
	int id=msg->id;
	memset(msg,0,sizeof(MSG));
	msg->id=id;
	//printf("查询ID:%d\n",msg->id);
	char sql_buf[256]={0};
	
	//使用查询函数
	sprintf(sql_buf,"SELECT * FROM user WHERE id=%d",msg->id);
	char **result={0};	// 结果集
	int row = 0;	// 行数
	int column = 0; // 列数
					/*
					会将 查询的到的结果的 行数写到row的地址里  列数写到column的地址里
					让 result 指向 sqlite3_get_table 申请的结果集的内存空间
					*/
	//查询结果的函数                             /结果集   /行数  /列数   /错误信息
	int ret=sqlite3_get_table(sql_db,sql_buf,&result,&row,&column,NULL);
	if (ret!=SQLITE_OK)
	{
		perror("查询 失败");
		printf("errcode[%d]  errmsg[%s]\n", ret, sqlite3_errmsg(sql_db));
	}
	if (0==row)
	{
		sprintf(msg->data,"ID[%d]为空", msg->id);

	}
	else
    {
		if (result[column])
		{
			msg->id=atoi(result[column]);
		}
		//上输入的字符个数大于s的大小的时候,最多格式化sizeof(s)-1个字符,在s最后一个字符的
		//位置补上'\0'
		snprintf(msg->pass,sizeof(msg->pass),"%s",result[column+1]);
		snprintf(msg->name,sizeof(msg->name),"%s",result[column+2]);
		snprintf(msg->sex,sizeof(msg->sex),"%s",result[column+3]);
		if (result[column+4])
		{
			msg->age=atoi(result[column+4]);
		}
		if (result[column+5])
		{
			msg->pay=atoi(result[column+5]);
		}
		snprintf(msg->job,sizeof(msg->job),"%s",result[column+6]);
	}
	//发送数据
    if (0 >= send(accept_fd,msg, sizeof(MSG), 0))
        ERRLOG("send error");
	//释放 结果集 防止内存泄漏
	sqlite3_free_table(result);
}

// 4. 修改
void do_Modify_s(int accept_fd, MSG *msg, sqlite3 *sql_db)
{
	char sql_buff[512] = {0};
	// pass ,name ,sex ,age,pay,job
	sprintf(sql_buff,"UPDATE user SET pass='%s',name='%s',sex='%s',age=%d,pay=%d,job='%s' WHERE id=%d",
					msg->pass,msg->name,msg->sex,msg->age,msg->pay,msg->job,msg->id);
	//执行sql语句
	int ret = sqlite3_exec(sql_db, sql_buff, NULL, NULL, NULL);
	if (ret != SQLITE_OK)
	{
		perror("修改 失败");
		printf("返回值[%d]  错误信息[%s]\n", ret, sqlite3_errmsg(sql_db));
		exit(-1);
	}
	strcpy(msg->data, "修改成功");
	//发送数据
    if (0 >= send(accept_fd,msg, sizeof(MSG), 0))
        ERRLOG("send error");
	memset(msg,0,sizeof(MSG));
}


// 5. ROOT登录
void root_login_s(int accept_fd, MSG *msg, sqlite3 *sql_db)
{
	char sql_buf[256]={0};
	//使用查询函数判断是否存在
	sprintf(sql_buf,"SELECT * FROM root WHERE id=%d AND pass='%s'",123456, msg->pass);
	
	char **result;	// 结果集
	int row = 0;	// 行数
	int column = 0; // 列数
					/*
					会将 查询的到的结果的 行数写到row的地址里  列数写到column的地址里
					让 result 指向 sqlite3_get_table 申请的结果集的内存空间
					*/
	//查询结果的函数                             /结果集   /行数  /列数   /错误信息
	int ret=sqlite3_get_table(sql_db,sql_buf,&result,&row,&column,NULL);
	if (ret!=SQLITE_OK)
	{
		perror("查询 失败");
		printf("errcode[%d]  errmsg[%s]\n", ret, sqlite3_errmsg(sql_db));
	}
	if (0==row)
	{
		sprintf(msg->data,"ID[%d]与密码[%s]不一致", msg->id,msg->pass);
	}
	else
    {
        strcpy(msg->data, "登录成功");
    }
	//发送数据
    if (0 >= send(accept_fd, msg, sizeof(MSG), 0))
        ERRLOG("send error");
	memset(msg,0,sizeof(MSG));
}

//6. 删除
void root_delete_s(int accept_fd, MSG *msg, sqlite3 *sql_db)
{
	char sql_buf[256]={0};
	//填写sql语句
	sprintf(sql_buf,"DELETE FROM user WHERE id=%d",msg->id);
	//执行sql语句
	int ret = sqlite3_exec(sql_db, sql_buf, NULL, NULL, NULL);
	if (ret!=SQLITE_OK)
	{
		perror("查询 失败");
		printf("errcode[%d]  errmsg[%s]\n", ret, sqlite3_errmsg(sql_db));
	}
	else
    {
        strcpy(msg->data, "删除成功");
    }
	//发送数据
    if (0 >= send(accept_fd, msg, sizeof(MSG), 0))
        ERRLOG("send error");
	memset(msg,0,sizeof(MSG));
}

// 7. 查询所有
void all_query_s(int accept_fd,sqlite3 *sql_db)
{
	MSG msg;
	int i,x;
	char sql_buf[256]={0};
	memset(&msg,0,sizeof(msg));
	//使用查询函数
	strcpy(sql_buf,"SELECT * FROM user");
	char **result;	// 结果集
	int row = 0;	// 行数
	int column = 0; // 列数
					/*
					会将 查询的到的结果的 行数写到row的地址里  列数写到column的地址里
					让 result 指向 sqlite3_get_table 申请的结果集的内存空间
					*/
	//查询结果的函数                             /结果集   /行数  /列数   /错误信息
	int ret=sqlite3_get_table(sql_db,sql_buf,&result,&row,&column,NULL);
	if (ret!=SQLITE_OK)
	{
		perror("查询 失败");
		printf("errcode[%d]  errmsg[%s]\n", ret, sqlite3_errmsg(sql_db));
	}
	if (0==row)
	{
		strcpy(msg.data,"**over**");
		//发送数据
		if (0 >= send(accept_fd,&msg, sizeof(MSG), 0))
			ERRLOG("send error");
	}
	else
    {
		x=column;
		for (i= 0; i<row; i++)// 行数
		{
			if (result[x])
			{
				msg.id=atoi(result[x]);
			}
			//上输入的字符个数大于s的大小的时候,最多格式化sizeof(s)-1个字符,在s最后一个字符的
			//位置补上'\0'
			x++;
			snprintf(msg.pass,sizeof(msg.pass),"%s",result[x++]);
			snprintf(msg.name,sizeof(msg.name),"%s",result[x++]);
			snprintf(msg.sex,sizeof(msg.sex),"%s",result[x++]);
			if (result[x])
			{
				msg.age=atoi(result[x]);
			}
			x++;
			if (result[x])
			{
				msg.pay=atoi(result[x]);
			}
			x++;
			snprintf(msg.job,sizeof(msg.job),"%s",result[x++]);
			//发送数据
			if (0 >= send(accept_fd,&msg, sizeof(MSG), 0))
				ERRLOG("send error");
			memset(&msg,0,sizeof(msg));
			if (row==(i+1))
			{
				strcpy(msg.data,"**over**");
				//发送数据
				if (0 >= send(accept_fd,&msg, sizeof(MSG), 0))
					ERRLOG("send error");
			}				
		}	
	}
	//释放 结果集 防止内存泄漏
	sqlite3_free_table(result);
}

server.h

#ifndef __SERVER_H__
#define __SERVER_H__
#include 
#include 
#include 
#include      // memset
#include   
#include 
#include  // 信息结构体struct sockaddr_in
#include 
#include 	//inet_aton
#include 
#include 	// fork
#include 
#include 


/*数据库文件名字*/
#define FILEname "ser.db"

#define ERRLOG(errmsg)                                       \
	do                                                       \
	{                                                        \
		printf("%s--%s(%d):", __FILE__, __func__, __LINE__); \
		perror(errmsg);                                      \
	} while (0)

#define R 1 //  user register注册
#define L 2 //  user login登录
#define Q 3 //  query word查询
#define M 4 //  修改
#define ROOT 5 // root
#define D 6 // 删除
#define Qall 7 // 查询所有
#define Only 8 

#define N 32 
//信息结构体
typedef struct
{
	int type;		//选择
	int id;
	char name[N];	//用户名
	char pass[64];// password密码
	char data[256]; //emark返回数据
	char sex[32];
	int age;
	int pay;
	char job[64];
}MSG;

//登录成功信息结构体
typedef struct
{
	int accept_fd;		//选择
	int id;
	int ord_accept_fd;
}LOG_SUCC;

// 1. 数据库初始化 ---打开数据文件、建表
sqlite3 *proc_init(void);

//创建套接字-填充服务器网络信息结构体-绑定-监听
int socket_bind_listen(const char *argv[]);

// 子进程执行
int child_do(int accept_fd, sqlite3 *sql_db);

// 信号处理函数  用于回收子进程资源
void deal_signal(int s);

// 1. 注册
void do_register_s(int accept_fd, MSG *msg, sqlite3 *sql_db);

// 2. 登录
void do_login_s(int accept_fd, MSG *msg, sqlite3 *sql_db);

// 3. 查询
void do_query_s(int accept_fd, MSG *msg, sqlite3 *sql_db);

// 4. 修改
void do_Modify_s(int accept_fd, MSG *msg, sqlite3 *sql_db);

// 5. ROOT登录
void root_login_s(int accept_fd, MSG *msg, sqlite3 *sql_db);

//6. 删除
void root_delete_s(int accept_fd, MSG *msg, sqlite3 *sql_db);

// 7. 查询所有
void all_query_s(int accept_fd,sqlite3 *sql_db);
// 登录成功后唯一查询
void all_only_s(int accept_fd, MSG *msg, sqlite3 *sql_db);
// 删除登录成功记录
void delete_history(int id);

#endif

客户端

client.c

#include "./client.h"
int errnum=0;
int root = 0,root2= 0;
char xbuf[128];
int main(int argc, char const *argv[])
{
	int ret;
	//检验命令行参数个数
	if (3 != argc)
	{
		printf("Usage : %s  \n", argv[0]);
		exit(-1);
	}
	// 1. 创建套接字-填充服务器网络信息结构体-connect
	int sockfd = socket_connect(argv);

	MSG msg;
	memset(&msg, 0, sizeof(MSG));
	int n;
	char nn;

	fd_set readfds;    //母本
	FD_ZERO(&readfds); //清空
	fd_set readfds_temp;    //用来备份原集合的
	FD_ZERO(&readfds_temp); //清空
	int max_fd = 0;//记录表中最大的文件描述符
	while (1)
	{
		printf("************************************\n");
		printf("* 1:  登录     2: 退出             *\n");
		printf("************************************\n");
		printf("please choose : ");

		if (scanf("%d", &n) <= 0)
		{
			perror("scanf");
			exit(-1);
		}

		switch (n)
		{
		case 1: 
			//执行登录函数,执行完毕后通过返回值决定是否要跳转到下一个菜单
			if (do_login(sockfd, &msg) == 1)
				goto next;
			break;
		case 2:
			close(sockfd);
			exit(0);
		}
	}
next:
	while (1)
	{
		if (0 == root)
		{
			do_only(sockfd,&msg);
			printf("************************************\n");
			printf("* 1: 查询   2: 修改   3: 退出 *\n");
			printf("************************************\n");
			printf("please choose : ");
			fflush(stdout);
			
			nn=fgetc(stdin);
			//将要监视的文件描述符放到表中
			FD_SET(0, &readfds);
			FD_SET(sockfd, &readfds);
			max_fd = (max_fd > sockfd ? max_fd : sockfd); //记录最大文件描述符
			int ret;
			MSG xxmsg;

			readfds_temp=readfds;
			ret = select(max_fd + 1, &readfds_temp, NULL, NULL,NULL);
			if (ret == -1)
			{
				printf("select error");
			}
			else
			{
				if (FD_ISSET(0, &readfds_temp))
				{
					nn=fgetc(stdin);
				}
				if (FD_ISSET(sockfd, &readfds_temp))
				{
					//接收数据并输出
					if (0 >= recv(sockfd, &xxmsg, sizeof(MSG), 0))
						ERRLOG("send error");
					if (strcmp(xxmsg.data, "在另一台设备登录") == 0)
					{
						printf("%s\n", xxmsg.data);
						exit(0);
					}
				}
			}

			switch (nn)
			{
			case '1': //查询
				do_query(sockfd, &msg);
				break;
			case '2': //修改
				do_Modify(sockfd, &msg);
				break;
			case '3':
				close(sockfd);
				exit(0);
			}
		}
		if (1 == root)
		{	//增、删、改、查
			printf("********************************************************\n");
			printf("* 1: 注册(增)    2: 删除   3: 修改   4: 查询    5: 退出 *\n");
			printf("********************************************************\n");
			printf("please choose : ");

			if (scanf("%d", &n) <= 0)
			{
				perror("scanf");
				exit(-1);
			}

			switch (n)
			{
			case 1: //注册
				do_register(sockfd, &msg);
				break;
			case 2: //删除
				do_delete(sockfd, &msg);
				break;
			case 3://修改
				do_Modify(sockfd, &msg);
				break;
			case 4:
				//查询
				do_query(sockfd, &msg);
				break;
			case 5:
				root=0;
				close(sockfd);
				exit(0);
			}
		}
	}
	//关闭套接字
	close(sockfd);
	return 0;
}

// 1. 创建套接字-填充服务器网络信息结构体-connect
int socket_connect(const char *argv[])
{
	// 1.创建套接字      /IPV4    /TCP
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (-1 == sockfd)
		ERRLOG("创建套接字 失败");

	// 2.填充服务器网络信息结构体
	struct sockaddr_in server_addr;
	memset(&server_addr, 0, sizeof(server_addr)); //清空
	server_addr.sin_family = AF_INET;			  // IPV4
												  /*端口号   atoi字符串转换成整型数; htons将无符号2字节整型  主机-->网络*/
	server_addr.sin_port = htons(atoi(argv[2]));
	/*ip地址;  inet_addr字符串转换成32位的网络字节序二进制值*/
	server_addr.sin_addr.s_addr = inet_addr(argv[1]);

	//结构体长度
	socklen_t server_addr_len = sizeof(server_addr);

	//与服务器建立连接
	if (-1 == connect(sockfd, (struct sockaddr *)&server_addr, server_addr_len))
	{
		ERRLOG("listen error");
		exit(-1);
	}

	printf("---连接服务器成功---\n");
	return sockfd;
}
// 唯一
void do_only(int sockfd, MSG *msg)
{
	MSG tmsg;
	tmsg.id=msg->id;
	//指定操作码
	tmsg.type =Only;
	//发送数据
	if (0 >= send(sockfd, &tmsg, sizeof(MSG), 0))
		ERRLOG("send error");
	//接收数据
	if (0 >= recv(sockfd, &tmsg, sizeof(MSG), 0))
		ERRLOG("send error");
}

// 1.注册
void do_register(int sockfd, MSG *msg)
{
	memset(msg, 0, sizeof(MSG));
	//指定操作码
	msg->type = R;
	printf("输入ID: ");
	scanf("%d", &msg->id);
	printf("输入[%d] 密码: ", msg->id);
	scanf("%s", msg->pass);
	//发送数据
	if (0 >= send(sockfd, msg, sizeof(MSG), 0))
		ERRLOG("send error");
	//接收数据并输出
	if (0 >= recv(sockfd, msg, sizeof(MSG), 0))
		ERRLOG("send error");

	printf("%s\n", msg->data);
}

// 2.登录
int do_login(int sockfd, MSG *msg)
{
	char NO[32] = "n";
	char unlogin[128]={0};
	printf("是否管理员(y/n): ");
	scanf("%s", NO);
	if (strcmp(NO, "y") == 0 || strcmp(NO, "Y") == 0)
	{
		//指定操作码
		msg->type = ROOT;
		root = 1;
		printf("输入 [%d] 密码: ", msg->id);
		scanf("%s", msg->pass);
		goto root;
	}
	//指定操作码
	msg->type = L;
	root = 0;
	printf("输入ID: ");
	scanf("%d", &msg->id);
	printf("输入 [%d] 密码: ", msg->id);
	scanf("%s", msg->pass);

root:
	//发送数据
	if (0 >= send(sockfd, msg, sizeof(MSG), 0))
		ERRLOG("send error ?");
	//接收数据并输出
	if (0 >= recv(sockfd, msg, sizeof(MSG), 0))
		ERRLOG("recv error");
	printf("%s\n", msg->data);
	sprintf(unlogin,"ID[%d]与密码[%s]不一致", msg->id,msg->pass);
	if (strcmp(msg->data,unlogin)== 0)
	{
		errnum++;
		if (3==errnum)
		{
			errnum=0;
			printf("休息一下,您已经输错3次\n");
			exit(0);
		}
	}
	
	if (strcmp(msg->data, "登录成功") == 0)
	{
		if (1==root)
		{
			memset(msg,0,sizeof(MSG));
		}
		return 1;
	}
	return 0;
}

// 3. 查询
int do_query(int sockfd, MSG *msg)
{
	int id=msg->id;
	memset(msg,0,sizeof(MSG));
	msg->id=id;
	char zoor[64]={0};
	//指定操作码
	msg->type = Q;
	if (1==root&&0==root2)
	{
		printf("输入查询ID{0 all}: ");
		scanf("%d", &msg->id);
		errnum=0;
		if (0==msg->id)
		{
			goto ALLQ;
		}
	}
	//发送数据
	if (0 >= send(sockfd, msg, sizeof(MSG), 0))
		ERRLOG("send error");
	//接收数据并输出
	if (0 >= recv(sockfd, msg, sizeof(MSG), 0))
		ERRLOG("recv error");
	sprintf(zoor,"ID[%d]为空", msg->id);
	if (strcmp(msg->data, zoor) == 0)
	{
		printf("%s\n", msg->data);
	}
	else{
		printf("id     |pass   |name   |sex    |age    |pay    |job    |\n");
		printf("%d\t%s\t%s\t%s\t%d\t%d\t%s\n", msg->id, msg->pass, msg->name, msg->sex, msg->age, msg->pay, msg->job);
		if (0==root2)
		{
			errnum++;
		}
		if (3==errnum)
		{
			errnum=0;
			printf("休息一下,您已经查询3次\n");
			exit(0);
		}
	}
	return 0;
ALLQ:
	All_query(sockfd, msg);
}

// 4. 修改
void do_Modify(int sockfd, MSG *msg)
{
	root2=1;
	if (1==root)
	{
		printf("输入修改ID: ");
		scanf("%d", &msg->id);
	}
	do_query(sockfd,msg);
	char NO[32] = "n";
	int flag=0;
	//指定操作码
	msg->type = M;
	
	printf("开始修改ID: [%d] 信息\n", msg->id);
	// msg->pass,msg->name,msg->sex,msg->age
	// pass='%s',name='%s',sex='%s',age=%d
	printf("修改密码(y/n): ");
	scanf("%s", NO);
	if (strcmp(NO, "y") == 0 || strcmp(NO, "Y") == 0)
	{
		memset(msg->pass, 0, sizeof(msg->pass));
		printf("新密码: ");
		scanf("%s", msg->pass);
		flag=1;
	}
	printf("修改名字(y/n): ");
	scanf("%s", NO);
	if (strcmp(NO, "y") == 0 || strcmp(NO, "Y") == 0)
	{
		memset(msg->name, 0, sizeof(msg->name));
		printf("新名字: ");
		scanf("%s", msg->name);
		flag=1;
	}
	printf("修改性别(y/n): ");
	scanf("%s", NO);
	if (strcmp(NO, "y") == 0 || strcmp(NO, "Y") == 0)
	{
		memset(msg->sex, 0, sizeof(msg->sex));
		printf("新性别: ");
		scanf("%s", msg->sex);
		flag=1;
	}
	printf("修改年龄(y/n): ");
	scanf("%s", NO);
	if (strcmp(NO, "y") == 0 || strcmp(NO, "Y") == 0)
	{
		printf("新年龄: ");
		scanf("%d", &msg->age);
		flag=1;
	}
	if (1==root)
	{
		printf("修改工资(y/n): ");
		scanf("%s", NO);
		if (strcmp(NO, "y") == 0 || strcmp(NO, "Y") == 0)
		{
			printf("新工资: ");
			scanf("%d", &msg->pay);
			flag=1;
		}
		printf("修改职位(y/n): ");
		scanf("%s", NO);
		if (strcmp(NO, "y") == 0 || strcmp(NO, "Y") == 0)
		{
			printf("新职位: ");
			scanf("%s", msg->job);
			flag=1;
		}
	}
	
	if (0==flag)
	{
		printf("无更新\n");
	}
	else
	{
		//发送数据
		if (0 >= send(sockfd, msg, sizeof(MSG), 0))
			ERRLOG("send error");
		//接收数据并输出
		if (0 >= recv(sockfd, msg, sizeof(MSG), 0))
			ERRLOG("recv error");

		if (strcmp(msg->data, "修改成功") == 0)
		{
			printf("%s\n", msg->data);
		}
	}
	root2=0;
}

//删除
void do_delete(int sockfd, MSG *msg)
{
	
	printf("输入删除ID: ");
	scanf("%d", &msg->id);
	//指定操作码
	msg->type = D;
	//发送数据
	if (0 >= send(sockfd, msg, sizeof(MSG), 0))
		ERRLOG("send error");
	//接收数据并输出
	if (0 >= recv(sockfd, msg, sizeof(MSG), 0))
		ERRLOG("recv error");
	printf("%s\n", msg->data);
}

// 7. 查询所有
void All_query(int sockfd, MSG *msg)
{
	memset(msg, 0, sizeof(MSG));
	int flag=0;
	char zoor[64]={0};
	sprintf(zoor,"ID[%d]为空", 0);
	//指定操作码
	msg->type = Qall;
	//发送数据
	if (0 >= send(sockfd, msg, sizeof(MSG), 0))
		ERRLOG("send error");
	while (1)
	{
		//接收数据并输出
		if (0 >= recv(sockfd, msg, sizeof(MSG), 0))
			ERRLOG("recv error");
		if (strcmp(msg->data, zoor) == 0)
		{
			printf("%s\n", msg->data);
		}
		if (strcmp(msg->data,"**over**") == 0)
		{
			printf("输出成功\n");
			break;
		}
		else
		{
			if (0==flag)
			{
				printf("id     |pass   |name   |sex    |age    |pay    |job    |\n");
				flag=1;
			}
			printf("%d\t%s\t%s\t%s\t%d\t%d\t%s\n", msg->id, msg->pass, msg->name, msg->sex, msg->age, msg->pay, msg->job);
			memset(msg, 0, sizeof(MSG));
		}
	}
	flag=0;
}

client.h

#ifndef __SERVER_H__
#define __SERVER_H__
#include 
#include 
#include 
#include      // memset
#include   
#include 
#include  // 信息结构体struct sockaddr_in
#include 
#include 	//inet_aton
#include 
#include 	
#include 

#define ERRLOG(errmsg)                                       \
	do                                                       \
	{                                                        \
		printf("%s--%s(%d):", __FILE__, __func__, __LINE__); \
		perror(errmsg);                                      \
	} while (0)

#define R 1 //  user register注册
#define L 2 //  user login登录
#define Q 3 //  query word查询
#define M 4 //  修改
#define ROOT 5 // root
#define D 6 // 删除
#define Qall 7 // 查询所有
#define Only 8 

#define N 32 
//信息结构体
typedef struct
{
	int type;		//选择
	int id;
	char name[N];	//用户名
	char pass[64];// password密码
	char data[256]; //emark返回数据
	char sex[32];
	int age;
	int pay;
	char job[64];
}MSG;

//1. 创建套接字-填充服务器网络信息结构体-connect
int socket_connect(const char *argv[]);

//1.注册
void do_register(int sockfd,MSG *msg);

//2.登录
int do_login(int sockfd, MSG *msg);

// 3. 查询
int do_query(int sockfd, MSG *msg);

//4. 修改
void do_Modify(int sockfd, MSG *msg);

//删除
void do_delete(int sockfd, MSG *msg);

// 7. 查询所有
void All_query(int sockfd, MSG *msg);

// 登录成功后唯一查询
void do_only(int sockfd, MSG *msg);

#endif

执行结果

  • 一个帐号在同一时间只能一个人登录( 如QQ另一设备登录前一设备退出 )
    基于SQLite数据库的IO多路复用(epoll)实现TCP并发-员工管理系统{一个帐号只能一个人登录}_第1张图片
    基于SQLite数据库的IO多路复用(epoll)实现TCP并发-员工管理系统{一个帐号只能一个人登录}_第2张图片

基于SQLite数据库的IO多路复用(epoll)实现TCP并发-员工管理系统{一个帐号只能一个人登录}_第3张图片

该客户端Shell的图形界面(很简单只是一个 选择

  • sudo apt-get install dialog

menu.sh

#!/bin/bash

# simple script menu

function Performing {
    clear
    
    cd /home/linux/xxxxxxxxx目录(pwd)查看xxxxxxxxx/03epoll员工/客户端
    
    dialog --title "Questionnaire" --inputbox "请输入端口号 " 9 30 2>_1.txt
    port=$(cat _1.txt)
    ./cli 127.0.0.1 $port
}

function menu {
    clear
	# 重定向标准错误输出流(2)到“_1.txt”。
    dialog --menu "    员工管理系统  " 15 30 2 1 "进入" 0 "退出"  2>_1.txt
    if [ $? -ne 0 ]; then  	# yes的返回码为0,“$?”为上一个命令的退出状态。
        dialog --clear  	# “-clear”作用为清屏。
        clear
        exit 0
    fi
    option=$(cat _1.txt)

}

while [ 1 ]; do

    menu

    case $option in

    0)

        break
        ;;

    1)

        Performing
        ;;

    *)
        clear
        echo "对不起,错误的选择"
        ;;

    esac

done

clear
  1. bash menu.sh
    在这里插入图片描述
  2. 输入端口号基于SQLite数据库的IO多路复用(epoll)实现TCP并发-员工管理系统{一个帐号只能一个人登录}_第4张图片
  3. 本质就是 ./cli 127.0.0.1 8888在这里插入图片描述

开发板作为服务器

1.先移植好数据库再搞
在这里插入图片描述
2.
基于SQLite数据库的IO多路复用(epoll)实现TCP并发-员工管理系统{一个帐号只能一个人登录}_第5张图片

本项目面试必问

epoll使用方法

epoll_create	:创建一个 epoll 对象
epoll_ctl		:向 epoll 对象中添加要管理的连接
epoll_wait		:等待其管理的连接上的 IO 事件

参考:https://www.zhangshilong.cn/work/221044.html

epoll原理

方法1:

还有更简单的回答可以用内核使用异步事件通知解释

方法2:中断回调函数

  • 首先调用epoll_create时内核在epoll文件系统里建了个file结点;除此之外在内核cache(高速缓存)里建立红黑树用于存储以后epoll_ctl传来的socket,
  • 当有新的socket连接来时,先遍历红黑树中有没有这个socket存在,如果有就立即返回,
  • 没有就插入红黑树,然后给内核中断处理程序注册一个回调函数,每当有事件发生时就通过回调函数。
  • 把这些文件描述符放到 事先准备好的 用来存储就绪事件的链表中,
  • 调用epoll_wait时,会把准备就绪的socket拷贝到 用户态内存,然后清空准备就绪list链表,最后检查这些socket,

epoll工作模式

  • LT模式下,如果这些socket上确实有未处理的事件时,该句柄会再次被放回到刚刚清空的准备就绪链表,保证所有的事件都得到正确的处理,
  • ET模式不会。如果到timeout时间后链表中没有数据也立刻返回。

一、水平触发模式——LT

  • epoll的默认工作模式
    如果想要验证的话可以把【读缓冲区】read_buf改小一些,这样就可以发现epoll_wait()被调用了很多次——
    基于SQLite数据库的IO多路复用(epoll)实现TCP并发-员工管理系统{一个帐号只能一个人登录}_第6张图片

可以根据来解释
可以观察epoll_wait()的返回次数,重点是【返回次数】和【发送数据的次数】没有任何关系,比如发送了100个byte的数据,但是接收一次只能接收5个byte,那么返回次数会是20次,但是发送数据的次数只有1次。

二、边沿触发模式——ET

本质上是【边沿阻塞触发】,因为fd默认是【阻塞】的,就算为了可以读取完数据,加一个while(recv()),但是当数据读完的时候,是会阻塞的——等待新的数据,所以为了解决阻塞问题,我们可以【设置非阻塞FD】。

  • 提高epoll_wait()的效率
  • 【套接字】默认是阻塞的

client给server发数据,发送一次epoll_wait就返回一次,不在乎这一次能不能把数据读完

三、边沿非阻塞模式

  • 效率最高

如何设置非阻塞?

  1. open(),因为open中有flags——将其设置成O_WDRW | O_NONBLOCK
  2. fcntl(),修改FD的flag属性
int flag = fcntl(fd, F_GETFL);
flag |= O_NONBLOCK
fcntl(fd, F_SETFL, flag);

将缓冲区的全部数据都读出

while(recv() > 0) { printf(); }

epoll的三种工作模式:https://www.jianshu.com/p/c682804219e3

问select/poll/epoll有什么区别

select:表

1.select监听的文件描述符最大是1024个
2.select用户空间的表会被内核空间返回覆盖,在用户空间需要反复构造表 需要反复从用户空间向内核空间拷贝,效率比较低
3.select被唤醒之后,需要重新遍历文件描述符的表,找到准备好的文件描述符,效率比较低。

poll:结构体数组

1.poll监听的文件描述符没有个数限制
2.poll表没有被覆盖的过程,不需要反复拷贝,效率比较高
3.poll被唤醒之后,需要重新遍历文件描述符的表,找到准备好的文件描述符,效率比较低。

epoll:(红黑树)

1.epoll监听的文件描述符没有个数限制
2.epoll表没有被覆盖的过程,不需要反复拷贝,效率比较高
3.epoll被唤醒之后,它能够直接拿到准备好的文件描述符,不需要遍历,效率高。

epoll优点:

(1)epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。
(2)本身没有最大并发连接的限制,仅受系统中进程能打开的最大文件数目限制;
(3)效率提升:只有活跃的socket才会主动的去调用callback函数;
(4)省去不必要的内存拷贝:epoll通过内核与用户空间mmap同一块内存实现。

epoll缺点:

(1)如果在并发量低,socket都比较活跃的情况下,select就不见得比epoll慢了。
(2)epoll的跨平台性不够用 只能工作在linux下。

atoi编写

int atois(char *s){
    int len = strlen(s);
    int sum=0;
    for(int i=0;i<len;i++){
        sum = sum*10 + (int)(s[i] - '0');//'0'-'0'=0
    }
    return sum;
}

你可能感兴趣的:(SQLite,Linux&C,数据库,sqlite,tcp/ip)