员工管理系统

文章详细介绍了如何设计一个员工管理系统,包括服务器通过TCP通信处理客户端请求,账号权限管理,以及数据库操作。服务器模型涉及循环服务器和并发服务器的概念,如多线程、多进程和IO多路复用(select、poll、epoll)。同时,文章讲解了SQLite数据库的API使用,如打开、关闭数据库,执行SQL语句等,并给出了创建数据库表的示例。
摘要由CSDN通过智能技术生成

一、简介       

员工管理系统功能介绍:

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.服务器负责处理操作
    客户端不能直接操作数据库

二、效果

效果

三、源代码

https://download.csdn.net/download/MisakaMikotto/87515629icon-default.png?t=N176https://download.csdn.net/download/MisakaMikotto/87515629

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值