一、简介
员工管理系统功能介绍:
1)服务器负责管理所有员工表单(以数据库形式),其他客户端可通过网络连接服务器来查询员工表单。
2)需要账号密码登陆,其中需要区分管理员账号还是普通用户账号。
3)管理员账号可以查看、修改、添加、删除员工信息,同时具有查询历史记录功能,管理员要负责管理所有的普通用户。
4)普通用户只能查询修改与本人有关的相关信息,其他员工信息不得查看修改。
5)服务器能同时相应多台客户端的请求功能。并发
流程图
服务器:
客户端:
原理
【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.服务器负责处理操作
客户端不能直接操作数据库
二、效果
效果