目录
项目要求:
-
登录注册功能,不能重复登录,重复注册
-
单词查询功能
-
历史记录功能,存储单词,意思,以及查询时间
-
基于TCP,支持多客户端连接
-
采用数据库保存用户信息与历史记录
-
将dict.txt的数据导入到数据库中保存。
-
按下ctrl+c退出客户端后,注销该客户端的登录信息
格式要求:
-
main函数只跑逻辑,不允许跑功能代码
-
功能代码封装成函数
代码
服务器代码
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include <sqlite3.h>
#include<time.h>
//打印错误信息的宏函数
#define ERR_MSG(msg) do{\
fprintf(stderr," __%d__",__LINE__);\
perror(msg);\
}while(0);
#define PORT 8888 //1024~49151端口号
#define IP "192.168.31.31" //本机IP,用ifcongfig查看
//定义协议
#define R 1 //注册
#define L 2 //登录
#define O 3 //注销退出
#define M 4 //修改
#define Q 5 //查询
#define H 6 //历史记录
typedef struct
{
int type; //存储协议
char name[128];
char data[128];
int flag; //判断是否登录
}MSG;
int do_register(int newfd,MSG *msg,sqlite3 *db);
int do_login(int newfd,MSG *msg,sqlite3 *db);
int do_query(int newfd,MSG *msg,sqlite3 *db);
int do_history(int newfd,MSG *msg,sqlite3 *db);
int do_logout(int newfd,MSG *msg,sqlite3 *db);
int do_clint(int newfd,sqlite3 *db)
{
MSG msg;
memset(&msg,0,sizeof(msg));
//服务器接收客户端信息,通过规定协议判断客户端请求
while (recv(newfd,&msg,sizeof(MSG),0)>0)
//while (1)
{
//recv(newfd,&msg,sizeof(msg),0);
printf("type %d\n",msg.type);
printf("name %s\n",msg.name);
printf("data %s\n",msg.data);
//根据结构体中的协议号调用程序响应处理
switch(msg.type)
{
case R:
do_register(newfd,&msg,db);
break;
case L:
do_login(newfd,&msg,db);
break;
case Q:
do_query(newfd,&msg,db);
break;
case H:
do_history(newfd,&msg,db);
break;
case O:
do_logout(newfd,&msg,db);
break;
default:
printf("GGGGGGGGG\n");
break;
}
}
char sql[128];
sprintf(sql,"delete from msg where name='%s';",msg.name);
//printf("%s\n",sql);
char* errmsg = NULL;
if(sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK)
{
fprintf(stderr, "__%d__ sqlite3_exec:%s\n", __LINE__, errmsg);
return -1;
}
else
{
printf("用户注销>>>>X_X\n");
strcpy(msg.data,"退出成功~");
}
printf("客户端退出...............\n");
close(newfd);
exit(0);
}
int do_register(int newfd,MSG *msg,sqlite3 *db)//注册函数
{
char * errmsg;
char sql[300];
//查重
int nrow, ncloumn; //数据库行数和列数
char **resultp; //定义二级指针保存SOL语句执行的结果
//拼接SOL语句,查询用户名和密码与数据库内容是否匹配
sprintf(sql, "select * from msg where name='%s';", msg->name);
//printf("%s\n", sql); //调试语句
if (sqlite3_get_table(db,sql,&resultp,&nrow,&ncloumn,&errmsg)!=SQLITE_OK)
{
printf("%s\n",errmsg);
return -1;
}
//printf("%d\n",nrow);
if(nrow==1)
{
strcpy(msg->data,"重复注册!!!");
send(newfd,msg,sizeof(*msg),0);
return 1;
}
else
{
//printf("%s\n",sql);
//printf("%s\n",msg->data);
//printf("flag=%d\n",msg->flag);
//向数据库中插入信息,flag为0,表示未登录
sprintf(sql,"insert into msg values('%s',\'%s\',%d);",msg->name,msg->data,msg->flag);
//printf("%s\n",sql);
char* errmsg = NULL;
if(sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK)
{
fprintf(stderr, "__%d__ sqlite3_exec:%s\n", __LINE__, errmsg);
return -1;
}
//printf("insert success\n");
//strcpy(msg->data,"插入成功");
else
{
strcpy(msg->data,"注册成功");
}
if (send(newfd,msg,sizeof(*msg),0)<0)
{
ERR_MSG("send");
return -1;
}
}
}
int do_login(int newfd,MSG *msg,sqlite3 *db)//登录处理函数
{
char * errmsg;
char sql[300];
//查重
//char *errmsg;
int nrow, ncloumn; //数据库行数和列数
char **resultp; //定义二级指针保存SOL语句执行的结果
//拼接SOL语句,查询用户名和密码与数据库内容是否匹配
sprintf(sql, "select * from msg where name='%s' and passwd='%s';", msg->name,msg->data);
//printf("%s\n", sql); //调试语句
if (sqlite3_get_table(db,sql,&resultp,&nrow,&ncloumn,&errmsg)!=SQLITE_OK)
{
printf("%s\n",errmsg);
return -1;
}
if(nrow==1)
{
//if(resultp[5]==1)
if(strcmp(resultp[5],"1")==0)//flag=1表示用户已登录 返回信息给客户端
{
strcpy(msg->data,"该用户已登录,洗洗睡吧");
send(newfd,msg,sizeof(*msg),0);
return -1;
}
bzero(sql,sizeof(sql));
msg->flag=1;
sprintf(sql,"update msg set flag=%d where name='%s' and passwd='%s';",msg->flag,msg->name,msg->data);
//printf("%s\n",sql);
if(sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK)
{
fprintf(stderr, "__%d__ sqlite3_exec:%s\n", __LINE__, errmsg);
return -1;
}
strcpy(msg->data,"OK");
send(newfd,msg,sizeof(*msg),0);
return 1;
}
else
{
strcpy(msg->data,"!!!账号密码有误!!!");
send(newfd,msg,sizeof(MSG),0);
}
return 0;
}
int do_query(int newfd,MSG *msg,sqlite3 *db)//查询单词
{
char tm[128];
char * errmsg;
char sql[300];
//查重
//char *errmsg;
int nrow, ncloumn; //数据库行数和列数
char **resultp; //定义二级指针保存SOL语句执行的结果
//拼接SOL语句,查询用户名和密码与数据库内容是否匹配
sprintf(sql, "select * from dict where word='%s';", msg->data);
//printf("%s\n", sql); //调试语句
if (sqlite3_get_table(db,sql,&resultp,&nrow,&ncloumn,&errmsg)!=SQLITE_OK)
{
printf("%s\n",errmsg);
return -1;
}
//printf("%d\n",nrow);
if(nrow==1)
{
time_t t;
struct tm *info = NULL;
t = time(NULL);
info = localtime(&t);
//\r转义字符,将光标重新移动回开头
sprintf(tm,"%d-%02d-%02d %02d:%02d:%02d\r", \
info->tm_year+1900, info->tm_mon+1, info->tm_mday,\
info->tm_hour, info->tm_min, info->tm_sec);
//strcpy(tm,asctime(t));
//printf("%s\n",tm);
//printf("zhongwen %s\n",resultp[3]);
sprintf(sql,"insert into history values('%s',\'%s\','%s');",msg->name,tm,msg->data);
//printf("%s\n",sql);
char* errmsg = NULL;
if(sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK)
{
fprintf(stderr, "__%d__ sqlite3_exec:%s\n", __LINE__, errmsg);
return -1;
}
strcpy(msg->data,resultp[3]);
send(newfd,msg,sizeof(*msg),0);
return 1;
}
else
{
strcpy(msg->data,"NO");
send(newfd,msg,sizeof(MSG),0);
}
return 0;
}
int do_history(int newfd,MSG *msg,sqlite3 *db)//历史记录查询
{
printf("test history\n");
//char his[256];//拼接历史记录
char * errmsg;
char sql[300];
//查重
//char *errmsg;
int nrow, ncloumn; //数据库行数和列数
char **resultp; //定义二级指针保存SOL语句执行的结果
//拼接SOL语句,查询用户名和密码与数据库内容是否匹配
//sprintf(sql, "select * from history;");
strcpy(sql,"select * from history;");
//printf("%s\n", sql); //调试语句
if (sqlite3_get_table(db,sql,&resultp,&nrow,&ncloumn,&errmsg)!=SQLITE_OK)
{
printf("%s\n",errmsg);
return -1;
}
if(nrow!=0)
{
//printf("query successful.\n");
//printf("test hang:%d lie:%d\n",nrow,ncloumn);
int i=0;
for (i=3; i<(nrow+1)*ncloumn; i+=3)
{
bzero(msg->data,sizeof(msg->data));
/* printf("ceshi %s\n",msg->data);
printf("%s\n",resultp[i]);
printf("%s\n",resultp[i+1]);
printf("%s\n",resultp[i+2]);*/
sprintf(msg->data,"%s %s %s",resultp[i],resultp[i+2],resultp[i+1]);//拼接记录信息写入结构体
printf("%s\n",msg->data);
if(send(newfd,msg,sizeof(*msg),0)<0)
{
ERR_MSG("send");
return -1;
}
bzero(msg->data,sizeof(msg->data));
}
msg->data[0]='\0';
if(send(newfd,msg,sizeof(MSG),0)<0)
{
ERR_MSG("send");
return -1;
}
return 1;
}
else
return 0;
}
int do_logout(int newfd,MSG* msg,sqlite3 *db)//注销函数
{
char sql[128];
sprintf(sql,"delete from msg where name='%s';",msg->name);//从数据库中删除用户信息
printf("%s\n",sql);
char* errmsg = NULL;
if(sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK)
{
fprintf(stderr, "__%d__ sqlite3_exec:%s\n", __LINE__, errmsg);
return -1;
}
else
{
printf("用户已注销,客户端GG。X_X\n");
strcpy(msg->data,"退出成功~");
}
if(send(newfd,msg,sizeof(MSG),0)<0)
{
ERR_MSG("send");
return -1;
}
}
int main(int argc, const char *argv[])
{
//创建流式套接字
int sfd=socket(AF_INET,SOCK_STREAM,0);
if (sfd < 0)
{
ERR_MSG("socket");
return -1;
}
printf("create socket success\n");
//允许端口快速复用
int reuse=1;
if(setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse))<0)
{
ERR_MSG("setsockopt");
return -1;
}
//填充地址信息结构体,真实地址信息结构体与协议族相关
//AF_INET,所以详情看》man 7 ip
struct sockaddr_in sin;
sin.sin_family =AF_INET;
sin.sin_port =htons(PORT); //网络字节序
sin.sin_addr.s_addr =inet_addr(IP); //网络字节序的IP地址
//将地址信息结构体绑定到套接字上
if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin))<0)
{
ERR_MSG("bind");
return -1;
}
printf("bind success\n");
//将套接字设置为被动监听状态,让内核去监听是否有客户端链接
if (listen(sfd,10)<0)
{
ERR_MSG("listen");
return -1;
}
printf("listen success\n");
//打开数据库
sqlite3* db = NULL;
if(sqlite3_open("./dict.db", &db) != SQLITE_OK)
{
printf("err_code:%d\n", sqlite3_errcode(db));
printf("errmsg:%s\n", sqlite3_errmsg(db));
fprintf(stderr, "__%d__ sqlite3_open failed\n", __LINE__);
return -1;
}
printf("sqlite3_open:dict.db success\n");
//创建信息表
char* sql = "create table if not exists msg ( name char, passwd char,flag int);" ;
char* errmsg = NULL;
if(sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK)
{
fprintf(stderr, "__%d__ sqlite3_exec:%s\n", __LINE__, errmsg);
return -1;
}
printf("create table msg success\n");
//创建历史记录表
char* sql2 = "create table if not exists history (name char, datatime char, word char);" ;
//char* errmsg2 = NULL;
if(sqlite3_exec(db, sql2, NULL, NULL, &errmsg) != SQLITE_OK)
{
fprintf(stderr, "__%d__ sqlite3_exec:%s\n", __LINE__, errmsg);
return -1;
}
printf("create table success\n");
struct sockaddr_in cin;
socklen_t addrlen= sizeof(cin);
//从已完成的队列头中,取出一个客户端的信息,创建生成一个新的套接字文件描述符
//该文件描述符才是与客户端通信的文件描述符!!!!
/* int newfd=accept(sfd,(struct sockaddr*)&cin,&addrlen);
if (newfd<0)
{
perror("accept");
return -1;
}*/
//网络字节序的IP--->点分十进制 网络字节序port转换成主机字节序
///printf("[%s : %d] newfd = %d \n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),newfd);
printf("***************************等待客户端链接*****************************\n");
while (1)
{
int newfd=accept(sfd,(struct sockaddr*)&cin,&addrlen);
if (newfd<0)
{
perror("accept");
return -1;
}
printf("[%s : %d] newfd = %d \n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),newfd);
pid_t pid=fork();
if (pid<0)
{
ERR_MSG("fork");
exit(-1);
}else if(pid==0)//子进程
{
//处理客户端请求
close(sfd);
do_clint(newfd,db);
}else
{
//父进程
close(newfd);
}
close(newfd);
}
close(sfd);
// close(newfd);
return 0;
}
客户端
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#define ERR_MSG(msg) do{\
fprintf(stderr," __%d__",__LINE__);\
perror(msg);\
}while(0);
#define PORT 8888 //1024~49151端口号
#define IP "192.168.31.31" //本机IP,用ifcongfig查看
typedef struct
{
int type; //存储协议
char name[128];
char data[128];
int flag; //判断是否登录
}MSG;
//定义协议
#define R 1 //注册
#define L 2 //登录
#define O 3 //注销退出
#define Q 5 //查询
#define H 6 //历史记录
//注册函数
int do_register(int sfd,MSG *msg)
{
//printf("%ld\n",sizeof(*msg));
msg->type=R;
msg->flag=0;
printf("请输入账号>>>");//账号输入
scanf("%s",msg->name);
getchar();
printf("请输入密码>>>");//密码输入
scanf("%s",msg->data);
getchar();
//printf("ceshi %s\n",msg->name);
//printf("ceshi %s\n",msg->data);
//memcpy(send_buf,&msg,sizeof(msg));
//客户端先发送
if (send(sfd,msg,sizeof(*msg),0)<0)
//if (send(sfd,send_buf,sizeof(send_buf),0)<0)
{
ERR_MSG("send");
return -1;
}
//memset(&msg,0,sizeof(msg));
//后接收
if (recv(sfd,msg,sizeof(*msg),0)<0)
{
ERR_MSG("recv");
return -1;
}
printf("%s\n",msg->data);
return 0;
}
//登录函数
int do_login(int sfd,MSG *msg)
{
msg->type=L;
printf("请输入账号>>>");//账号输入
scanf("%s",msg->name);
getchar();
printf("请输入密码>>>");//密码输入
scanf("%s",msg->data);
getchar();
//客户端先发送
if (send(sfd,msg,sizeof(*msg),0)<0)
{
ERR_MSG("send");
return -1;
}
//后接收
if (recv(sfd,msg,sizeof(*msg),0)<0)
{
ERR_MSG("recv");
return -1;
}
//if(strncmp(msg->data,"OK",3)==0)
if(strcmp(msg->data,"OK")==0)
{
printf("***************登陆成功****************\n\n");
// printf("***************************************\n");
return 1;
}else
{
//printf("登录失败\n");
printf("%s\n",msg->data);
return 0;
}
return 0;
}
//退出登录函数
int do_logout(int sfd,MSG *msg)
{
if (msg->flag==0)
{
printf("用户未登录\n");
return -1;
}
msg->type=O;//发送退出登录协议
printf("%d\n",msg->type);
//客户端先发送
if (send(sfd,msg,sizeof(*msg),0)<0)
{
ERR_MSG("send");
return -1;
}
//后接收
if (recv(sfd,msg,sizeof(*msg),0)<0)
{
ERR_MSG("recv");
return -1;
}
printf("%s\n",msg->data);
return 0;
}
//查询函数
int do_query(int sfd,MSG *msg)
{
msg->type=Q;
//输入要查询的单词
printf("请输入要查询的单词\n");
fgets(msg->data,sizeof(msg->data),stdin);
msg->data[strlen(msg->data)-1]='\0';
//printf("test %s\n",msg->data);
//客户端先发送
if (send(sfd,msg,sizeof(MSG),0)<0)
{
ERR_MSG("send");
return -1;
}
//后接收
if (recv(sfd,msg,sizeof(*msg),0)<0)
{
ERR_MSG("recv");
return -1;
}
if (strcmp(msg->data,"NO")==0)
{
printf("XXXXXXX未查询到对应单词XXXXXXXXX\n");
}else
{
printf("中文意思为:%s\n",msg->data);
}
return 0;
}
//查询历史记录
int do_history(int sfd,MSG *msg)
{
msg->type=H;
//printf("test %d\n",msg->type);
if (send(sfd,msg,sizeof(MSG),0)<0)
{
printf("发送失败\n");
return -1;
}
while (1)
{
if (recv(sfd,msg,sizeof(MSG),0)<0)
{
ERR_MSG("recv");
return -1;
}
if (msg->data[0]==0)
{
break;
}
printf("%s\n",msg->data);
}
return 0;
}
int main(int argc, const char *argv[])
{
MSG msg;
//printf("%ld\n",sizeof(msg));
//创建流式套接字
int sfd=socket(AF_INET,SOCK_STREAM,0);
if (sfd<0)
{
ERR_MSG("socket");
return -1;
}
//绑定客户端的地址信息结构体》非必须填写
//填充要连接的服务器的地址信息结构体
struct sockaddr_in sin;
sin.sin_family =AF_INET;
sin.sin_port =htons(PORT);
sin.sin_addr.s_addr=inet_addr(IP);
//链接服务器
if (connect(sfd,(struct sockaddr*)&sin,sizeof(sin))<0)
{
ERR_MSG("connect");
return -1;
}
start:
while (1) //登录交互界面
{
printf("***************************************\n");
printf("****************1:注册*****************\n");
printf("****************2:登录*****************\n");
printf("****************3:退出*****************\n");
printf("***************************************\n");
printf("请选择>>>");
int n;
scanf("%d",&n);
getchar();//吸收垃圾字符
switch(n)
{
case 1:
do_register(sfd,&msg);
break;
case 2:
if(do_login(sfd,&msg)==1)
goto next;
break;
case 3:
close(sfd);
exit(0);
break;
default:
printf("请输入正确选项!!!\n");
}
}
next:
while (1)
{
printf("***************************************\n");
printf("****************1:查询翻译*************\n");
printf("****************2:查询历史*************\n");
printf("****************3:退出登录*************\n");
printf("***************************************\n");
printf("请选择>>>");
int n;
scanf("%d",&n);
getchar();//吸收垃圾字符
switch(n)
{
case 1:
do_query(sfd,&msg);
break;
case 2:
do_history(sfd,&msg);
break;
case 3:
do_logout(sfd,&msg);
goto start;
break;
default:
printf("请输入正确选项!!!\n");
break;
}
}
close(sfd);
return 0;
}
运行截图
注册
首次注册
重复注册
数据库
登录
正常登录
重复登录
数据库
登陆后flag为1
查询
数据库
查询后将信息插入数据库
查询历史
退出
按程序退出或者ctrl+c结束,都将注销用户,从数据库中删除用户信息
数据库