员工管理系统功能介绍:
1)服务器负责管理所有员工表单(以数据库形式),其他客户端可通过网络连接服务器来查询员工表单。
2)需要账号密码登陆,其中需要区分管理员账号还是普通用户账号。
3)管理员账号可以查看、修改、添加、删除员工信息,同时具有查询历史记录功能,管理员要负责管理所有的普通用户。
4)普通用户只能查询修改与本人有关的相关信息,其他员工信息不得查看修改。
5)服务器能同时相应多台客户端的请求功能。并发
1 流程图
服务器:
客户端:
2 通信结构体
typedef struct staff_info{
int no; //员工编号
int usertype; //ADMIN 0 USER 1
char name[NAMELEN]; //姓名
char passwd[8]; //密码
int age; // 年龄
char phone[NAMELEN];//电话
char addr[DATALEN]; // 地址
char work[DATALEN]; //职位
char date[DATALEN]; //入职年月
int level; // 等级
double salary ; // 工资
}staff_info_t;
/定义双方通信的结构体信息/
typedef struct {
int msgtype; //请求的消息类型
int usertype; //ADMIN 1 USER 2
char username[NAMELEN]; //姓名
char passwd[8]; //登陆密码
char recvmsg[DATALEN]; //通信的消息
int flags; //标志位
staff_info_t info; //员工信息
}MSG;
3 所需知识点
自动探测
自动换行
【1】TCP通信的编程步骤
1.服务器:
1)创建套接字
2)绑定ip和端口号
3)监听
4)等待客户端连接
int main()
{
//1.创建套接字
int sockfd = socket();
//2.初始化通信结构
struct sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_port = port;
addr.sin_addr=addr;
bind(sockfd, &addr);
//3.监听
listen();
//4.连接
while(1)
{
int connfd = accept();
//5.循环数据收发
while(1)
{
recv();
send();
}
}
close(sockfd);
close(connfd);
}
2.客户端:
1)创建套接字
2)连接服务器
int main()
{
//1.创建套接字
int sockfd = socket();
//2.初始化通信结构
struct sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_port = port;
addr.sin_addr=addr;
//3.连接
connect();
//5.循环数据收发
while(1)
{
send();
recv();
}
}
【2】服务器模型
1.循环服务器
2.并发服务器
1)多线程
2)多进程
3)IO多路复用:
a. select:
基本思想:
1. 先构造一张有关文件描述符的表(集合、数组); fd_set fd;
2. 将你关心的文件描述符加入到这个表中;FD_SET();
3. 然后调用一个函数。 select / poll
4. 当这些文件描述符中的一个或多个已准备好进行I/O操作的时候
该函数才返回(阻塞)。
5. 判断是哪一个或哪些文件描述符产生了事件(IO操作);
6. 做对应的逻辑处理;
****select函数返回之后,会自动将除了产生事件的文件描述符以外的位全部清空;
程序步骤:
1.把关心的文件描述符放入集合--FD_SET
2.监听集合中的文件描述符--select
3.依次判断哪个文件描述符有数据--FD_ISSET
4.依次处理有数据的文件描述符的数据
伪代码:
fd_set fd;
FD_SET(sockfd);
while(1) {
设置监听读写文件描述符集合(FD_*);
调用select;
select();
如果是监听套接字就绪,说明有新的连接请求
if(sockfd)
{
建立连接();
int connfd = accept();
加入到监听文件描述符集合;
FD_SET(connfd);
}否则说明是一个已经连接过的描述符
else
{
进行操作(send或者recv);
recv();
send();
}
}
select弊端:
1. 一个进程最多只能监听1024个文件描述符 (千级别)
2. select是一种轮询的机制;
3. 涉及到用户态和内核态的数据拷贝;
b. poll
1. 优化文件描述符个数的限制;
2. poll是一种轮询的机制;
3. 涉及到用户态和内核态的数据拷贝;
函数接口:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
参数:
struct pollfd *fds
关心的文件描述符数组struct pollfd fds[N];
nfds:个数
timeout: 超市检测
毫秒级的:如果填1000,1秒
如果-1,阻塞
问题:
我想检测是键盘事件(标准输入 文件描述如为0 ),
还是鼠标事件(文件描述符是/dev/input/mouse1);
1. 创建一个结构体数组
struct pollfd fds[2];
2. 将你关心的文件描述符加入到结构体成员中
struct pollfd {
int fd; // 关心的文件描述符;
short events; // 关心的事件,读
short revents; // 如果产生事件,则会自动填充该成员的值
};
// 键盘
fds[0].fd = 0;
fds[0].events = POLLIN;
//鼠标
fds[1].fd = mouse1_fd;
fds[1].events = POLLIN;
3. 调用poll函数
如果返回表示有事件产生;
poll(fds,2,1000)
4. 判断具体是哪个文件描述符产生了事件
if(fds[0].revents == POLLIN)
{
....
}
c. epoll
1. 没有文件描述符的限制
2. 异步IO,当有事件产生,文件描述符主动调用callback
3. 不用数据拷贝;
3个功能函数:
#include <sys/epoll.h>
int epoll_create(int size);//创建红黑树根节点
//成功时返回epoll文件描述符,失败时返回-1
//控制epoll属性
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epfd:epoll_create函数的返回句柄。
op:表示动作类型。有三个宏 来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中
EPOLL_CTL_MOD:修改已注册fd的监听事件
EPOLL_CTL_DEL:从epfd中删除一个fd
FD:需要监听的fd。
event:告诉内核需要监听什么事件
EPOLLIN:表示对应文件描述符可读
EPOLLOUT:可写
EPOLLPRI:有紧急数据可读;
EPOLLERR:错误;
EPOLLHUP:被挂断;
EPOLLET:触发方式,电平触发;
ET模式:表示状态的变化;
//成功时返回0,失败时返回-1
//等待事件到来
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
功能:等待事件的产生,类似于select嗲用
epfd:句柄;
events:用来从内核得到事件的集合;
maxevents:表示每次能处理事件最大个数;
timeout:超时时间,毫秒,0立即返回,-1阻塞
//成功时返回发生事件的文件描述数,失败时返回-1
伪代码:
1.定义epoll事件,创建epoll的fd
int epfd,epct,i;
struct epoll_event event; //定义epoll 事件
struct epoll_event events[20]; //定义epoll 事件集合
epfd = epoll_create(1); // 创建epoll 的fd
2.填充事件
event.data.fd = serverFd; //填充事件的fd
event.events = EPOLLIN | EPOLLET; //填充 事件类型
epoll_ctl(epfd,EPOLL_CTL_ADD,serverFd,&event); //把serverFd(监听FD)注册到epfd中
3.监听事件
while(1){
epct = epoll_wait(epfd,events,20,-1); // 等待事件到来,阻塞模式
for(i=0;i<epct;i++){ //根据epoll返回的值来查询事件
if(events[i].data.fd == serverFd){ // 如果事件的fd是监听fd,调用accept处理
clientFd = accept();
//添加clientfd描述符
epoll_ctl(epfd,EPOLL_CTL_ADD,clientFd,&event);
}else {
//如果不是serverFd,应是client数据事件,调用读数据
read();
}
}
}
【3】数据库函数接口
1.int sqlite3_open(char *path, sqlite3 **db);
功能:打开sqlite数据库
参数:
path: 数据库文件路径
db: 指向sqlite句柄的指针
返回值:成功返回0,失败返回错误码(非零值)
2.int sqlite3_close(sqlite3 *db);
功能:关闭sqlite数据库
返回值:成功返回0,失败返回错误码
3.int sqlite3_exec(sqlite3 *db, const char *sql,
sqlite3_callback callback, void *, char **errmsg);
功能:执行SQL语句
参数:
db:数据库句柄
sql:SQL语句 ("create table stu .....;")
callback:回调函数
void * arg:
当使用查询命令的时候,callback和arg才有意义;
select .....
errmsg:错误信息指针的地址
char *errmsg;
&errmsg;
返回值:成功返回0,失败返回错误码
int callback(void *para, int f_num, char **f_value, char **f_name);
功能:每找到一条记录自动执行一次回调函数
参数:
para: 传递给回调函数的参数
f_num: 记录中包含的字段数目(id name score)
相当于有多少列;
f_value:包含每个字段值的指针数组
f_name:包含每个字段名称的指针数组
返回值:成功返回0,失败返回-1
4.int sqlite3_get_table(sqlite3 *db, const char *sql,
char ***resultp, int *nrow, int *ncolumn, char **errmsg);
功能:执行SQL操作
参数:
db:数据库句柄
sql:SQL语句
resultp:用来指向sql执行结果的指针;实际上就是“指针数组指针”;
nrow:满足条件的记录的数目,实际上就是有多少行数据;
ncolumn:每条记录包含的字段数目,实际上就是有多少个字段(多少列);
errmsg:错误信息指针的地址
返回值:成功返回0,失败返回错误码
练习:创建数据库stu.db,包含name、id、score字段,实现对数据库的增删改查。
【4】员工管理系统
1.通信结构体的定义(5min)
2.服务器负责处理操作
客户端不能直接操作数据库
common.h
#ifndef _COMMON_H_
#define _COMMON_H_
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sqlite3.h>
#include <sys/wait.h>
#include <signal.h>
#include <time.h>
#include <pthread.h>
#include <sys/stat.h>
#include <sqlite3.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <pthread.h>
#define STAFF_DATABASE "staff_manage_system.db"
#define USER_LOGIN 0x00000000 // login 登陆 0x00000001
#define USER_MODIFY 0x00000001 // user-modification 修改
#define USER_QUERY 0x00000002 // user-query 查询
#define ADMIN_LOGIN 0x10000000 // login 登陆 0x00000001
#define ADMIN_MODIFY 0x10000001 // admin_modification
#define ADMIN_ADDUSER 0x10000002 // admin_adduser
#define ADMIN_DELUSER 0x10000004 // admin_deluser
#define ADMIN_QUERY 0x10000008 //hitory_query
#define ADMIN_HISTORY 0x10000010 //hitory_query
#define QUIT 0x11111111
#define ADMIN 0 //管理员
#define USER 1 //用户
#define NAMELEN 16
#define DATALEN 128
/*员工基本信息*/
typedef struct staff_info{
int no; //员工编号
int usertype; //ADMIN 1 USER 2
char name[NAMELEN]; //姓名
char passwd[8]; //密码
int age; // 年龄
char phone[NAMELEN];//电话
char addr[DATALEN]; // 地址
char work[DATALEN]; //职位
char date[DATALEN]; //入职年月
int level; // 等级
double salary ; // 工资
}staff_info_t;
/*定义双方通信的结构体信息*/
typedef struct {
int msgtype; //请求的消息类型
int usertype; //ADMIN 1 USER 2
char username[NAMELEN]; //姓名
char passwd[8]; //登陆密码
char recvmsg[DATALEN]; //通信的消息
int flags; //标志位
staff_info_t info; //员工信息
}MSG;
#endif
Makefile
all:
gcc server.c -o server -lpthread -lsqlite3
gcc client.c -o client
clean:
rm server client
client.c
#include "common.h"
/**************************************
*函数名:do_query
*参 数:消息结构体
*功 能:登陆
****************************************/
void do_admin_query(int sockfd,MSG *msg)
{
printf("------------%s-----------%d.\n",__func__,__LINE__);
}
/**************************************
*函数名:admin_modification
*参 数:消息结构体
*功 能:管理员修改
****************************************/
void do_admin_modification(int sockfd,MSG *msg)//管理员修改
{
printf("------------%s-----------%d.\n",__func__,__LINE__);
}
/**************************************
*函数名:admin_adduser
*参 数:消息结构体
*功 能:管理员创建用户
****************************************/
void do_admin_adduser(int sockfd,MSG *msg)//管理员添加用户
{
printf("------------%s-----------%d.\n",__func__,__LINE__);
}
/**************************************
*函数名:admin_deluser
*参 数:消息结构体
*功 能:管理员删除用户
****************************************/
void do_admin_deluser(int sockfd,MSG *msg)//管理员删除用户
{
printf("------------%s-----------%d.\n",__func__,__LINE__);
}
/**************************************
*函数名:do_history
*参 数:消息结构体
*功 能:查看历史记录
****************************************/
void do_admin_history (int sockfd,MSG *msg)
{
printf("------------%s-----------%d.\n",__func__,__LINE__);
}
/**************************************
*函数名:admin_menu
*参 数:套接字、消息结构体
*功 能:管理员菜单
****************************************/
void admin_menu(int sockfd,MSG *msg)
{
printf("------------%s-----------%d.\n",__func__,__LINE__);
}
/**************************************
*函数名:do_query
*参 数:消息结构体
*功 能:登陆
****************************************/
void do_user_query(int sockfd,MSG *msg)
{
printf("------------%s-----------%d.\n",__func__,__LINE__);
}
/**************************************
*函数名:do_modification
*参 数:消息结构体
*功 能:修改
****************************************/
void do_user_modification(int sockfd,MSG *msg)
{
printf("------------%s-----------%d.\n",__func__,__LINE__);
}
/**************************************
*函数名:user_menu
*参 数:消息结构体
*功 能:管理员菜单
****************************************/
void user_menu(int sockfd,MSG *msg)
{
printf("------------%s-----------%d.\n",__func__,__LINE__);
}
int admin_or_user_login(int sockfd,MSG *msg)
{
printf("------------%s-----------%d.\n",__func__,__LINE__);
//输入用户名和密码
memset(msg->username, 0, NAMELEN);
printf("请输入用户名:");
scanf("%s",msg->username);
getchar();
memset(msg->passwd, 0, DATALEN);
printf("请输入密码(6位)");
scanf("%s",msg->passwd);
getchar();
//发送登陆请求
send(sockfd, msg, sizeof(MSG), 0);
//接受服务器响应
recv(sockfd, msg, sizeof(MSG), 0);
printf("msg->recvmsg :%s\n",msg->recvmsg);
//判断是否登陆成功
if(strncmp(msg->recvmsg, "OK", 2) == 0)
{
if(msg->usertype == ADMIN)
{
printf("亲爱的管理员,欢迎您登陆员工管理系统!\n");
admin_menu(sockfd,msg);
}
else if(msg->usertype == USER)
{
printf("亲爱的用户,欢迎您登陆员工管理系统!\n");
user_menu(sockfd,msg);
}
}
else
{
printf("登陆失败!%s\n", msg->recvmsg);
return -1;
}
return 0;
}
/************************************************
*函数名:do_login
*参 数:套接字、消息结构体
*返回值:是否登陆成功
*功 能:登陆
*************************************************/
int do_login(int sockfd)
{
int n;
MSG msg;
while(1){
printf("*************************************************************\n");
printf("******** 1:管理员模式 2:普通用户模式 3:退出********\n");
printf("*************************************************************\n");
printf("请输入您的选择(数字)>>");
scanf("%d",&n);
getchar();
switch(n)
{
case 1:
msg.msgtype = ADMIN_LOGIN;
msg.usertype = ADMIN;
break;
case 2:
msg.msgtype = USER_LOGIN;
msg.usertype = USER;
break;
case 3:
msg.msgtype = QUIT;
if(send(sockfd, &msg, sizeof(MSG), 0)<0)
{
perror("do_login send");
return -1;
}
close(sockfd);
exit(0);
default:
printf("您的输入有误,请重新输入\n");
}
admin_or_user_login(sockfd,&msg);
}
}
int main(int argc, const char *argv[])
{
//socket->填充->绑定->监听->等待连接->数据交互->关闭
//创建网络通信的套接字
//填充网络结构体
//连接服务器
if(connect() == -1){
perror("connect failed.\n");
exit(-1);
}
do_login(sockfd);
close(sockfd);
return 0;
}
server.c
#include "common.h"
sqlite3 *db; //仅服务器使用
int process_user_or_admin_login_request(int acceptfd,MSG *msg)
{
printf("------------%s-----------%d.\n",__func__,__LINE__);
//封装sql命令,表中查询用户名和密码-存在-登录成功-发送响应-失败-发送失败响应
char sql[DATALEN] = {0};
char *errmsg;
char **result;
int nrow,ncolumn;
msg->info.usertype = msg->usertype;
strcpy(msg->info.name,msg->username);
strcpy(msg->info.passwd,msg->passwd);
printf("usrtype: %#x-----usrname: %s---passwd: %s.\n",msg->info.usertype,msg->info.name,msg->info.passwd);
sprintf(sql,"select * from usrinfo where usertype=%d and name='%s' and passwd='%s';",msg->info.usertype,msg->info.name,msg->info.passwd);
if(sqlite3_get_table(db,sql,&result,&nrow,&ncolumn,&errmsg) != SQLITE_OK){
printf("---****----%s.\n",errmsg);
}else{
//printf("----nrow-----%d,ncolumn-----%d.\n",nrow,ncolumn);
if(nrow == 0){
strcpy(msg->recvmsg,"name or passwd failed.\n");
send(acceptfd,msg,sizeof(MSG),0);
}else{
strcpy(msg->recvmsg,"OK");
send(acceptfd,msg,sizeof(MSG),0);
}
}
return 0;
}
int process_user_modify_request(int acceptfd,MSG *msg)
{
printf("------------%s-----------%d.\n",__func__,__LINE__);
}
int process_user_query_request(int acceptfd,MSG *msg)
{
printf("------------%s-----------%d.\n",__func__,__LINE__);
}
int process_admin_modify_request(int acceptfd,MSG *msg)
{
printf("------------%s-----------%d.\n",__func__,__LINE__);
}
int process_admin_adduser_request(int acceptfd,MSG *msg)
{
printf("------------%s-----------%d.\n",__func__,__LINE__);
}
int process_admin_deluser_request(int acceptfd,MSG *msg)
{
printf("------------%s-----------%d.\n",__func__,__LINE__);
}
int process_admin_query_request(int acceptfd,MSG *msg)
{
printf("------------%s-----------%d.\n",__func__,__LINE__);
}
int process_admin_history_request(int acceptfd,MSG *msg)
{
printf("------------%s-----------%d.\n",__func__,__LINE__);
}
int process_client_quit_request(int acceptfd,MSG *msg)
{
printf("------------%s-----------%d.\n",__func__,__LINE__);
}
int process_client_request(int acceptfd,MSG *msg)
{
printf("------------%s-----------%d.\n",__func__,__LINE__);
switch (msg->msgtype)
{
case USER_LOGIN:
case ADMIN_LOGIN:
process_user_or_admin_login_request(acceptfd,msg);
break;
case USER_MODIFY:
process_user_modify_request(acceptfd,msg);
break;
case USER_QUERY:
process_user_query_request(acceptfd,msg);
break;
case ADMIN_MODIFY:
process_admin_modify_request(acceptfd,msg);
break;
case ADMIN_ADDUSER:
process_admin_adduser_request(acceptfd,msg);
break;
case ADMIN_DELUSER:
process_admin_deluser_request(acceptfd,msg);
break;
case ADMIN_QUERY:
process_admin_query_request(acceptfd,msg);
break;
case ADMIN_HISTORY:
process_admin_history_request(acceptfd,msg);
break;
case QUIT:
process_client_quit_request(acceptfd,msg);
break;
default:
break;
}
}
int main(int argc, const char *argv[])
{
//判断命令行参数
//打开数据库
if(sqlite3_open() != SQLITE_OK){
printf("%s.\n",sqlite3_errmsg(db));
}else{
printf("the database open success.\n");
}
//执行数据库操作
if(sqlite3_exec()!= SQLITE_OK){
printf("%s.\n",errmsg);
}else{
printf("create usrinfo table success.\n");
}
//socket->填充->绑定->监听->等待连接->数据交互->关闭
//创建网络通信的套接字
sockfd = socket();
if(sockfd == -1){
perror("socket failed.\n");
exit(-1);
}
//填充网络结构体
//绑定网络套接字和网络结构体
if(bind() == -1){
printf("bind failed.\n");
exit(-1);
}
//监听套接字,将主动套接字转化为被动套接字
if(listen() == -1){
printf("listen failed.\n");
exit(-1);
}
//通过select实现并发
while(1){
retval =select();
//判断是否是集合里关注的事件
if(判断是连接请求){
//数据交互
acceptfd = accept();
if(acceptfd == -1){
printf("acceptfd failed.\n");
exit(-1);
}
}else{
recvbytes = recv();
if(recvbytes == -1){
printf("recv failed.\n");
continue;
}else if(recvbytes == 0){
printf("peer shutdown.\n");
close(i);
FD_CLR(i, &readfds); //删除集合中的i
}else{
process_client_request(i,&msg);
}
}
}
close(sockfd);
return 0;
}
以下为员工管理系统带调试信息的打印记录: 管理员登陆: 客户端的打印信息 服务器端的打印信息 普通员工登陆: 客户端的打印信息 服务器端的打印信息 @@@@@@@@@@@@@@@@@@@@@@管理员登陆--客户端的测试记录:@@@@@@@@@@@@@@@@@@@@@@ linux@ubuntu:/mnt/hgfs/KernelSource/staff_manage_select/stage4-user$ ./client sockfd :3. ************************************************************* ******** 1:管理员模式 2:普通用户模式 3:退出******** ************************************************************* 请输入您的选择(数字)>>1 ------------admin_or_user_login-----------489. 请输入用户名:admin 请输入密码(6位)admin msg->recvmsg :OK 亲爱的管理员,欢迎您登陆员工管理系统! ************************************************************* * 1:查询 2:修改 3:添加用户 4:删除用户 5:查询历史记录* * 6:退出 * ************************************************************* 请输入您的选择(数字)>>1 ------------do_admin_query-----------35. ************************************************************* ******* 1:按人名查找 2:查找所有 3:退出 ******* ************************************************************* 请输入您的选择(数字)>>1 请输入您要查找的用户名:xiaohui 工号 用户类型 姓名 密码 年龄 电话 地址 职位 入职年月 等级 工资 ====================================================================================== 1004, 1, xiaohui, 1, 31, 1888x, henan, gagagaga, 9012.10.11, 1, 1.0;. ************************************************************* ******* 1:按人名查找 2:查找所有 3:退出 ******* ************************************************************* 请输入您的选择(数字)>>2 工号 用户类型 姓名 密码 年龄 电话 地址 职位 入职年月 等级 工资 ====================================================================================== 1001, 0, admin, admin, 18, 110, 华清远见创客学院, 嵌入式物联网方向讲师, xxx, 5, 1.0;. ====================================================================================== 1003, 1, fengjunhui, 1, 30, 18888888888, henan, gaga, 2015.10.23, 1, 10.0;. ====================================================================================== 1002, 1, lisi, 1, 20, 119, 华清远见创客学院=北京, ooo, 2019.11.11, 1, 50.0;. ====================================================================================== 1004, 1, xiaohui, 1, 31, 1888x, henan, gagagaga, 9012.10.11, 1, 1.0;. ************************************************************* ******* 1:按人名查找 2:查找所有 3:退出 ******* ************************************************************* 请输入您的选择(数字)>>3 ************************************************************* * 1:查询 2:修改 3:添加用户 4:删除用户 5:查询历史记录* * 6:退出 * ************************************************************* 请输入您的选择(数字)>>2 ------------do_admin_modification-----------100. 请输入您要修改只认的工号:1002 *******************请输入要修改的选项******************** ****** 1:姓名 2:年龄 3:家庭住址 4:电话 ****** ****** 5:职位 6:工资 7:入职年月 8:评级 ****** ****** 9:密码 10:退出 ******* ************************************************************* 请输入您的选择(数字)>>2 请输入年龄:30 数据库修改成功!修改结束.