一个帐号在同一时间只能一个人登录( 如QQ另一设备登录前一设备退出 )
#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);
}
#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
#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;
}
#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
选择
)sudo apt-get install dialog
#!/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
epoll_create :创建一个 epoll 对象
epoll_ctl :向 epoll 对象中添加要管理的连接
epoll_wait :等待其管理的连接上的 IO 事件
参考:https://www.zhangshilong.cn/work/221044.html
方法1:
方法2:中断回调函数
epoll_create
时内核在epoll文件系统里建了个file结点;除此之外在内核cache(高速缓存)里建立红黑树用于存储以后epoll_ctl
传来的socket,内核中断处理程序
注册一个回调函数,每当有事件发生时就通过回调函数。epoll_wait
时,会把准备就绪的socket拷贝到 用户态内存,然后清空准备就绪list链表,最后检查这些socket,LT模式
下,如果这些socket上确实有未处理的事件时,该句柄会再次被放回到刚刚清空的准备就绪链表,保证所有的事件都得到正确的处理,ET模式
不会。如果到timeout时间后链表中没有数据也立刻返回。可以根据
读
来解释
可以观察epoll_wait()
的返回次数,重点是【返回次数】和【发送数据的次数】没有任何关系,比如发送了100个byte的数据,但是接收一次只能接收5个byte,那么返回次数会是20次,但是发送数据的次数只有1次。
本质上是【边沿阻塞触发】,因为fd默认是【阻塞】
的,就算为了可以读取完数据,加一个while(recv()),但是当数据读完的时候,是会阻塞的——等待新的数据,所以为了解决阻塞问题,我们可以【设置非阻塞FD】。
client给server发数据,
发送一次epoll_wait就返回一次
,不在乎这一次能不能把数据读完
如何设置非阻塞?
O_WDRW | O_NONBLOCK
。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
1.select监听的文件描述符最大是1024个
2.select用户空间的表会被内核空间返回覆盖,在用户空间需要反复构造表 需要反复从用户空间向内核空间拷贝,效率比较低
3.select被唤醒之后,需要重新遍历文件描述符的表,找到准备好的文件描述符,效率比较低。
1.poll监听的文件描述符没有个数限制
2.poll表没有被覆盖的过程,不需要反复拷贝,效率比较高
3.poll被唤醒之后,需要重新遍历文件描述符的表,找到准备好的文件描述符,效率比较低。
1.epoll监听的文件描述符没有个数限制
2.epoll表没有被覆盖的过程,不需要反复拷贝,效率比较高
3.epoll被唤醒之后,它能够直接拿到准备好的文件描述符,不需要遍历,效率高。
(1)epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。
(2)本身没有最大并发连接的限制,仅受系统中进程能打开的最大文件数目限制;
(3)效率提升:只有活跃的socket才会主动的去调用callback函数;
(4)省去不必要的内存拷贝:epoll通过内核与用户空间mmap同一块内存实现。
(1)如果在并发量低,socket都比较活跃的情况下,select就不见得比epoll慢了。
(2)epoll的跨平台性不够用 只能工作在linux下。
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;
}