项目需求:
按 1、2、3、4、5分别实现用户注册(数据要保存到文件里)、登录、退出、查询单词、查询历史记录
客户端开发步骤:
1、创建套接字,接入服务器
按1注册,进入注册函数,注册完后数据传入服务器
按2登录,登录成功函数返回1,进入二级目录;否则返回0,重新输入
按3退出,exit(0)
2、注册需要一个套接字,需要一个信息结构体
3、二级目录,查询单词、查询历史目录、退出
//客户端框架
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h> // for open
#include <unistd.h> // for close
#include <arpa/inet.h>
#define R 1//register
#define L 2//login
#define Q 3//query
#define H 4//history
int cmd;
struct msg
{
int type;//查询的是注册?登录?
char date[128];//用户数据;查询的时候就是单词,注册的时候就是姓名
char name[32];//保存用户姓名
};
void do_register(int socket,struct msg *c_msg)//用户注册
{
printf("register........\n");
}
int do_login(int socket,struct msg *c_msg)//用户登录
{
printf("login........\n");
return 1;
}
int do_query(int socket,struct msg *c_msg)//查询单词
{
printf("query_word........\n");
return 1;
}
int do_history(int socket,struct msg *c_msg)//查询输入历史记录
{
printf("history_record........\n");
return 1;
}
int main(int argc,char *argv[])
{
if(argc != 3)
{
printf("you input is invalid\n");
exit(0);
}
struct msg c_msg;
//创建套接字
int c_fd;
c_fd = socket(AF_INET,SOCK_STREAM,0);//TCP协议,英特尔网域
if(c_fd == -1)//创建套接字失败
{
printf("creat socket failed\n");
perror("socket");
exit(-1);
}
//约定好IP端口和地址号
struct sockaddr_in c_addr;//创建服务器地址
bzero(&c_addr,sizeof(struct sockaddr_in));//清空服务器内容的作用
c_addr.sin_family = AF_INET;//默认使用英特尔网域
c_addr.sin_port = htons(atoi(argv[2]));//设置端口号
inet_aton(argv[1],&c_addr.sin_addr);
bind(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in)); //连接到服务器
int mark = connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr));//如果标志位为-1说明连接失败
if(mark == -1)//连接服务器失败
{
printf("link server failed\n");
perror("connect");
exit(-1);
}
while(1)//进入一级菜单
{
printf("*************************************\n");
printf("** 1.register 2.login 3.quit **\n");
printf("*************************************\n");
printf("please input cmd\n");
scanf("%d",&cmd);
getchar();
switch(cmd)
{
case 1:
do_register(c_fd,&c_msg);
break;
case 2:
if(do_login(c_fd,&c_msg)==1);
goto next;
break;
case 3:
close(c_fd);
exit(0);
break;
default:
printf("you input a invalid cmd\n");
}
}
next://进入二级菜单
while(1)
{
printf("************************************************\n");
printf("** 1.query_word 2.history_record 3.quit **\n");
printf("************************************************\n");
printf("please input cmd\n");
scanf("%d",&cmd);//进入二级菜单就无一级菜单,所以可复用cmd命令
getchar();
switch(cmd)
{
case 1:
do_query(c_fd,&c_msg);
break;
case 2:
do_history(c_fd,&c_msg);
break;
case 3:
close(c_fd);
exit(0);
break;
default:
printf("you input a invalid cmd\n");
}
}
return 0;
}
服务端开发步骤:
打开数据库
1、创建套接字,接入服务器、配置结构体
2、bind()
3、监听有无客户端连接进来
4、
//服务端框架
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sqlite3.h>
#include <signal.h>
#include <fcntl.h> // for open
#include <unistd.h> // for close
#include <arpa/inet.h>
#define R 1//register
#define L 2//login
#define Q 3//query
#define H 4//history
#define DATABASE "my.db"
char cmd;
struct msg s_msg;
int s_fd;
int c_fd;//用来反映客户端ID号
pid_t pid;
struct msg
{
int type;//查询的是注册?登录?
char date[128];//用户数据;查询的时候就是单词,注册的时候就是姓名
char name[32];//保存用户姓名
};
void do_register(int socket,struct msg *s_msg,sqlite3 *db)//用户注册
{
printf("register........\n");
}
int do_login(int socket,struct msg *s_msg,sqlite3 *db)//用户登录
{
printf("login........\n");
return 0;
}
int do_query(int socket,struct msg *s_msg,sqlite3 *db)//查询单词
{
printf("query_word........\n");
return 1;
}
int do_history(int socket,struct msg *s_msg,sqlite3 *db)//查询输入历史记录
{
printf("history_record........\n");
return 1;
}
int do_client(int c_fd,sqlite3 *db)
{
struct msg s_msg;
while(recv(c_fd,&s_msg,sizeof(struct msg),0)>0)//正确接收,且阻塞式接收
{
switch(s_msg.type)
{
case R:
do_register(c_fd,&s_msg,db);
break;
case L:
do_login(c_fd,&s_msg,db);
break;
case Q:
do_query(c_fd,&s_msg,db);
break;
case H:
do_history(c_fd,&s_msg,db);
default:
printf("invalid MSG\n");
}
}
//跳出循环即意味着客户端退出,recv返回值为0
printf("client exit...\n");
close(c_fd);
exit(0);
}
//服务端代码
int main(int argc,char *argv[])
{
if(argc != 3)
{
printf("you input is invalid\n");
exit(0);
}
sqlite3 *db;//创建个数据库
if(sqlite3_open(DATABASE,&db) != SQLITE_OK)//判断数据库是否打开成功
{
printf("%s\n",sqlite3_errmsg(db));//打印错误消息
}
//创建套接字
s_fd = socket(AF_INET,SOCK_STREAM,0);//TCP协议,英特尔网域
if(s_fd == -1)//创建套接字失败
{
printf("creat socket failed\n");
perror("socket");
exit(-1);
}
//约定好IP端口和地址号
struct sockaddr_in s_addr;//创建服务器地址
bzero(&s_addr,sizeof(s_addr));//清空服务器内容的作用
s_addr.sin_family = AF_INET;//默认使用英特尔网域
s_addr.sin_port = htons(atoi(argv[2]));//设置端口号
inet_aton(argv[1],&s_addr.sin_addr);
int mark = bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in)); //连接到套接字
if(mark == -1)//连接服务器失败
{
printf("lbind failed\n");
perror("bind");
exit(-1);
}
if(listen(s_fd,10)<0)
{
printf("listen failed\n");
}
signal(SIGCHLD,SIG_IGN);//专门处理僵尸进程
//进入一级菜单
while(1)
{
if((c_fd = accept(s_fd,NULL,NULL))<0)//接收客户端
{
perror("fail to accept");
}
if((pid = fork())<0)
{
perror("fail to creat fork");
return -1;
}
else if(pid == 0)//子进程处理请求
{
close(s_fd);
do_client(c_fd,db);
}
else//父进程,用来接收客户端请求
{
close(c_fd);
}
}
return 0;
}
//编译完成后,先sqlite3 my.db 创建一个数据库
create table usr(name text primary key,pass text);//创建用户名数据库
create table record(name text,pass text,word text);//创建历史记录数据库
.schema//查看数据库;里面数据
select * from usr //查看usr数据表里面的数据
.quit//退出数据库
在…>后输入“;” 在按下回车,即可退出此模式。
.tables:查看所有数据库列表
多行注释:ctrl+k
取消多行注释:ctrl+alt+k
//sprintf作用:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
char str[20];
double f=14.309948;
sprintf(str,“%6.2f”,f);
printf(“%s\n”,str);
}
结果:14.32
总结:sprintf作用是吧后面字符串内容插入到str里面。
```c
整个项目中 c_fd = acceptfd;
/recv的返回值:<0 出错; =0 连接关闭; >0 接收到数据大小
//注册模块
客户端注册流程:
1、先把结构体type赋值为R,客
2、客户端获得姓名密码,发送到已连接的套接字里面;此时父进程连接网络后,先获得客户端套接字,
3、再把里面的数据接收到recv_msg里面,
4、根据type进行注册服务,根据注册是否成功修改recv_msg里面的date,再把recv_msg里面的消息发给客户端套接字进行判断。
//客户端注册模块
void do_register(int c_fd,struct msg *c_msg)//用户注册
{
c_msg->type = R;
printf("please input name:");
scanf("%s",c_msg->name);
getchar();
printf("please input passwd:");
scanf("%s",c_msg->date);
printf("register........\n");
if(send(c_fd,c_msg,sizeof(struct msg),0)<0)
{
printf("fail to send.\n");
exit(0);
}
if(recv(c_fd,c_msg,sizeof(struct msg),0)<0)
{
printf("fail to recv.\n");
exit(0);
}
printf("%s\n",c_msg->date);
}
sqlite3_exec()
int sqlite3_exec(
sqlite3*, /* An open database */
const char *sql, /* SQL to be evaluated */
int (*callback)(void*,int,char**,char**), /* Callback function */
void *, /* 1st argument to callback */
char **errmsg /* Error msg written here */
);
sqlite3* : open 打开的数据库
const char* sql, : 执行的sql功能语句
*callback, : sql语句对应的回调函数
void* data, : 传递给回调函数的 指针参数
char **errmsq : 错误信息
1、构造一个字符串,sprintf用于存放插入数据库的字符串
2、将字符串发送给数据库sqlite3_exec(),如果发生失败给结构体data赋值name exist
3、把结构体发给套接字,客户端套接字接收,来查看注册结果
//服务端注册模块
void do_register(int c_fd,struct msg *s_msg,sqlite3 *db)//用户注册
{
char *errmsg;
char sql[256];
sprintf(sql,"insert into usr values('%s', %s);", s_msg->name, s_msg->date);
if(sqlite3_exec(db,sql, NULL, NULL, &errmsg) != SQLITE_OK)
{
printf("%s\n",errmsg);
strcpy(s_msg->date,"usr name exist.");
}
else
{
printf("client register is ok!\n");
strcpy(s_msg->date,"OK!");
}
if(send(c_fd,s_msg,sizeof(struct msg),0)<0)
{
printf("fail to send.\n");
}
}
登录模块:
type=L
1、客户端输入姓名密码放入结构体,把结构体发送给服务器那边的结构体接收
2、服务器用查询函数查询数据库里面数据,查询成功now为1返回给结构体ok,
查询失败now=0,返回给结构体fail
3、服务器把结构体发送给套接字,客户端接收里面的内容如果是OK则返回1代表登录成功进入二级菜单
否则打印错误信息
//客户端代码:
int do_login(int c_fd,struct msg *c_msg)//用户登录
{
printf("login ...\n");
c_msg->type = L;
printf("input name:");
scanf("%s", c_msg->name);
getchar();
printf("input passwd:");
scanf("%s", c_msgmsg->date);
getchar();
if (send(c_fd, c_msg, sizeof(struct msg), 0) < 0)
{
printf("fail to send.\n");
return -1;
}
if (recv(c_fd, c_msg, sizeof(struct msg), 0) < 0)
{
printf("fail to recv.\n");
return -1;
}
//登录成功
if (strncmp(c_msg->date, "OK", 3) == 0)
{
printf("login ok! \n");
return 1;
}
else
{
printf("%s\n", c_msg->date);
return 0;
}
}
//服务端代码:
int do_login(int c_fd,struct msg *s_msg,sqlite3 *db)//用户登录
{
printf("login........\n");
char sql[256] = {};//128太小会警告
char *errmsg;
int nrow;
int ncloumn;
char **resultp;
sprintf(sql, "select * from usr where name = '%s' and pass = '%s';", s_msg->name, s_msg->date);
printf("%s\n", sql);
if(sqlite3_get_table(db, sql, &resultp, &nrow, &ncloumn, &errmsg)!= SQLITE_OK)//数据库中未查询到信息
{
printf("%s\n", errmsg);
return -1;
}
else
{
printf("get_table ok!\n");
}
// 查询成功,数据库中拥有此用户
if(nrow == 1)
{
strcpy(s_msg->date, "OK");//这里把OK放进data里,client.c中才会那么比较
send(c_fd, s_msg, sizeof(struct msg), 0);
return 1;
}
if(nrow == 0) // 密码或者用户名错误,或者写else就可以
{
strcpy(s_msg->date,"usr/passwd wrong.");
send(c_fd, s_msg, sizeof(struct msg), 0);
return 0;
}
}
查询模块:
1、type=Q;
2、请输入单词放入c_msg,while循环,按#退出
3、将所查询单词发给服务器
4、从等待接收服务器传递来的单词注释信息
5、打印服务器返回的信息
服务端:
先拿出来单词,再做一个查询单词函数,找到的话found赋值为1
找到单词就把用户名、单词、时间记录到记录表中,返回一个信息,差不多又一个信息
searchword() 如何填充历史记录
searchword()
1、把单词拿出放入一个字符串
2、不断从文件中读取查找单词,只有当strncmp()=0时,并且单词下一个字母为空才认为找到单词
C 库函数 char *fgets(char *str, int n, FILE *stream) 从指定的流 stream 读取一行,
并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,
或者到达文件末尾时,它会停止,具体视情况而定。
//服务端代码:
int do_searchword(struct msg *s_msg, char *word)
{
FILE *fp = NULL;
int word_len;
char row_data[512] = {'\0'};
int mark = 0;
char *p; //指向注释
//打开文件
if ((fp = fopen("dict.txt", "r")) == NULL)
{
perror("fail to open dict.txt.\n");
return -1;
}
//打印客户端要查询的单词
word_len = strlen(word);
printf("%s, len = %d\n", word, word_len );
//读取文件 行数据(一行一行读取),对比要查询的单词
//如果成功,该函数返回相同的 str 参数。
//如果到达文件末尾或者没有读取到任何字符,str 的内容保持不变,并返回一个空指针。
//如果发生错误,返回一个空指针。
while (fgets(row_data, 512, fp) != NULL)//把词典文件放入rowdata里面
{
mark = strncmp(row_data, word, word_len);//每行对比前word_len个字节,两个字符串比大小
if (mark != 0)
continue;
if (row_data[word_len] != ' ') //单词跟注释之间没有空格
goto _end;
// 找到了单词,跳过所有的空格
p = row_data + word_len;
while (*p == ' ')
{
p++;
}
strcpy(s_msg->date, p);
fclose(fp);
return 1;
}
_end:
fclose(fp);
return 0; //文件对比完,单词未找到
}
void get_date(char *data)
{
time_t rowtime; //typedef long time_t;
// struct tm {
// int tm_sec; /* Seconds (0-60) */
// int tm_min; /* Minutes (0-59) */
// int tm_hour; /* Hours (0-23) */
// int tm_mday; /* Day of the month (1-31) */
// int tm_mon; /* Month (0-11) */
// int tm_year; /* Year - 1900 */
// int tm_wday; /* Day of the week (0-6, Sunday = 0) */
// int tm_yday; /* Day in the year (0-365, 1 Jan = 0) */
// int tm_isdst; /* Daylight saving time */
// };
struct tm *info;
// rowtime = the number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC)
time(&rowtime);//查看当前时间存放到rowtime里面
//进行时间格式转换
info = localtime(&rowtime);
sprintf(data, "%d-%d-%d %d:%d:%d", info->tm_year + 1900, info->tm_mon + 1, info->tm_mday,info->tm_hour+15, info->tm_min, info->tm_sec);
printf("get date is : %s\n", data);
}
int do_query(int c_fd,struct msg *s_msg,sqlite3 *db)//查询单词
{
char sql[128] = {0};
char word[64] = {0};
int found = 0;
char date[128] = {0};
char *errmsg;
printf("\n");//显示换行
//单词查找
strcpy(word, s_msg->date);
found = do_searchword(s_msg, word);
if (found == 1)// 找到了单词,需要将 name,date,word 插入到历史记录表中去
{
get_date(date);//获取系统时间
//sprintf(sql, "insert into user values('%s', '%s');", msg->name, msg->data);
sprintf(sql, "insert into record values('%s', '%s', '%s')", s_msg->name, date, word);
if (sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK)
{
printf("%s\n", errmsg);
return -1;
}
else
{
printf("sqlite3 insert record done.\n");
}
}
else if (found == 0)//没有找到
{
memset(s_msg->date, 0, strlen(s_msg->date));
strcpy(s_msg->date, "Not found!\n");
}
else if (found == -1)//dict.txt代开失败
{
memset(s_msg->date, 0, strlen(s_msg->date));
strcpy(s_msg->date, "fail to open dict.txt.");
}
//将查询的结果发送给客户端
if(send(c_fd, s_msg, sizeof(struct msg), 0)<0)
{
printf("send fail\n");
}
return 0;
}
//客户端代码:
int do_query(int c_fd,struct msg *c_msg)//查询单词
{
printf("query ...\n");
c_msg->type = Q;
while(1)
{
printf("input word:");
scanf("%s", c_msg->date);
getchar();
// 输入是"#"表示退出本次查询
if (strncmp(c_msg->date, "#", 1) == 0)
break;
//将要查询的单词发送给服务器
if (send(c_fd, c_msg, sizeof(struct msg), 0) < 0)
{
printf("fail to send.\n");
return -1;
}
//等待服务器,传递回来的单次的注释信息
if (recv(c_fd, c_msg, sizeof(struct msg), 0) < 0)
{
printf("fail to recv.\n");
return -1;
}
printf("%s\n", c_msg->date);
}
return 1;
}
查询历史记录模块:
1、客户端只改变消息类型值为H,并且将消息类型传递给服务端,然后接收服务端消息并且打印服务端消息
2、服务端根据消息类型,在数据库中插入查询命令,并且将结果返回到一个字符串
3、把字符串的值(单词,时间)拼接到客户端结构体,把结构体发给公共套接字
4、发完消息就将date[0]第一个赋值为'\0',表示结束。
客户端查询历史记录:
int do_history(int c_fd,struct msg *c_msg)//查询输入历史记录
{
printf("history ...\n");
c_msg->type = H;
//将消息发送给服务器
if (send(c_fd, c_msg, sizeof(struct msg), 0) < 0)
{
printf("fail to send.\n");
return -1;
}
while(1)
{
//等待服务器,传递回来的单次的注释信息
if (recv(c_fd, c_msg, sizeof(struct msg), 0) < 0)//接收失败退出
{
printf("fail to recv.\n");
return -1;
}
if (c_msg->date[0] == '\0')//接收一个'\0'也退出
break;
//输出历史记录信息
printf("%s\n", c_msg->date);
}
return 1;
}
服务端查询历史记录:
int history_callback(void* arg,int colCount,char** colValue,char** colName)//主要用于接收服务端返回的历史记录
{
// record : name, date, word ,查询的历史记录放入colValue里面
int c_fd;
struct msg c_msg;
c_fd = *((int *)arg);
sprintf(c_msg.date, "%s , %s", colValue[1], colValue[2]);//只需要知道单词和查询时间
send(c_fd, &c_msg, sizeof(struct msg), 0);//把单词和查询时间发送给套接字,客户端去接收
return 0;
}
// 历史记录查询
int do_history(int c_fd, struct msg *c_msg, sqlite3 *db)
{
char sql[128] = {0};
char *errmsg;
//查询数据库
//会先执行*sql对应的功能命令,然后将结果传递给回调函数,回调函数根据结果再进一步执行
//关于回调函数详细可参考https://blog.csdn.net/u012351051/article/details/90382391
sprintf(sql, "select * from record where name = '%s'", c_msg->name);//查询的时候已登录,名字是登录时候就有
if(sqlite3_exec(db, sql, history_callback,(void *)&c_fd, &errmsg)!= SQLITE_OK)
{
printf("%s\n", errmsg);
}
else
{
printf("sqlite3 query record done.\n");
}
// 所有的记录查询发送完毕之后,给客户端发出一个结束信息
c_msg->date[0] = '\0';
send(c_fd, c_msg, sizeof(struct msg), 0);
return 0;
}
客户端所有代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h> // for open
#include <unistd.h> // for close
#include <arpa/inet.h>
#define R 1//register
#define L 2//login
#define Q 3//query
#define H 4//history
int cmd;
struct msg
{
int type;//查询的是注册?登录?
char date[128];//用户数据;查询的时候就是单词,注册的时候就是姓名
char name[32];//保存用户姓名
};
int do_register(int c_fd,struct msg *c_msg)//用户注册
{
c_msg->type = R;
printf("please input name:");
scanf("%s",c_msg->name);
getchar();
printf("please input passwd:");
scanf("%s",c_msg->date);
printf("register........\n");
if(send(c_fd,c_msg,sizeof(struct msg),0)<0)
{
printf("fail to send.\n");
return -1;
}
if(recv(c_fd,c_msg,sizeof(struct msg),0)<0)
{
printf("fail to recv.\n");
return -1;
}
printf("%s\n",c_msg->date);
return 1;
}
int do_login(int c_fd,struct msg *c_msg)//用户登录
{
printf("login ...\n");
c_msg->type = L;
printf("input name:");
scanf("%s", c_msg->name);
getchar();
printf("input passwd:");
scanf("%s", c_msg->date);
getchar();
if (send(c_fd, c_msg, sizeof(struct msg), 0) < 0)
{
printf("fail to send.\n");
return -1;
}
if (recv(c_fd, c_msg, sizeof(struct msg), 0) < 0)
{
printf("fail to recv.\n");
return -1;
}
//登录成功
if (strncmp(c_msg->date, "OK", 3) == 0)
{
printf("login ok! \n");
return 1;
}
else
{
printf("%s\n", c_msg->date);
return 0;
}
}
int do_query(int c_fd,struct msg *c_msg)//查询单词
{
printf("query ...\n");
c_msg->type = Q;
while(1)
{
printf("input word:");
scanf("%s", c_msg->date);
getchar();
// 输入是"#"表示退出本次查询
if (strncmp(c_msg->date, "#", 1) == 0)
break;
//将要查询的单词发送给服务器
if (send(c_fd, c_msg, sizeof(struct msg), 0) < 0)
{
printf("fail to send.\n");
return -1;
}
//等待服务器,传递回来的单次的注释信息
if (recv(c_fd, c_msg, sizeof(struct msg), 0) < 0)
{
printf("fail to recv.\n");
return -1;
}
printf("%s\n", c_msg->date);
}
return 1;
}
int do_history(int c_fd,struct msg *c_msg)//查询输入历史记录
{
printf("history ...\n");
c_msg->type = H;
//将消息发送给服务器
if (send(c_fd, c_msg, sizeof(struct msg), 0) < 0)
{
printf("fail to send.\n");
return -1;
}
while(1)
{
//等待服务器,传递回来的单次的注释信息
if (recv(c_fd, c_msg, sizeof(struct msg), 0) < 0)//接收失败退出
{
printf("fail to recv.\n");
return -1;
}
if (c_msg->date[0] == '\0')//接收一个'\0'也退出
break;
//输出历史记录信息
printf("%s\n", c_msg->date);
}
return 1;
}
int main(int argc,char *argv[])
{
if(argc != 3)
{
printf("you input is invalid\n");
exit(0);
}
struct msg c_msg;
//创建套接字
int c_fd;
c_fd = socket(AF_INET,SOCK_STREAM,0);//TCP协议,英特尔网域
if(c_fd == -1)//创建套接字失败
{
printf("creat socket failed\n");
perror("socket");
exit(-1);
}
//约定好IP端口和地址号
struct sockaddr_in c_addr;//创建服务器地址
bzero(&c_addr,sizeof(struct sockaddr_in));//清空服务器内容的作用
c_addr.sin_family = AF_INET;//默认使用英特尔网域
c_addr.sin_port = htons(atoi(argv[2]));//设置端口号
inet_aton(argv[1],&c_addr.sin_addr);
bind(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr_in)); //连接到服务器
int mark = connect(c_fd,(struct sockaddr *)&c_addr,sizeof(struct sockaddr));//如果标志位为-1说明连接失败
if(mark == -1)//连接服务器失败
{
printf("link server failed\n");
perror("connect");
exit(-1);
}
while(1)//进入一级菜单
{
printf("*************************************\n");
printf("** 1.register 2.login 3.quit **\n");
printf("*************************************\n");
printf("please input cmd\n");
scanf("%d",&cmd);
getchar();
switch(cmd)
{
case 1:
do_register(c_fd,&c_msg);
break;
case 2:
if(do_login(c_fd,&c_msg)==1);
goto next;
break;
case 3:
close(c_fd);
exit(0);
break;
default:
printf("you input a invalid cmd\n");
}
}
next://进入二级菜单
while(1)
{
printf("************************************************\n");
printf("** 1.query_word 2.history_record 3.quit **\n");
printf("************************************************\n");
printf("please input cmd\n");
scanf("%d",&cmd);//进入二级菜单就无一级菜单,所以可复用cmd命令
getchar();
switch(cmd)
{
case 1:
do_query(c_fd,&c_msg);
break;
case 2:
do_history(c_fd,&c_msg);
break;
case 3:
close(c_fd);
exit(0);
break;
default:
printf("you input a invalid cmd\n");
}
}
return 0;
}
//服务端代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sqlite3.h>
#include <signal.h>
#include <fcntl.h> // for open
#include <unistd.h> // for close
#include <arpa/inet.h>
#include <time.h>
#define R 1//register
#define L 2//login
#define Q 3//query
#define H 4//history
#define DATABASE "my.db"
char cmd;
struct msg s_msg;
int s_fd;
int c_fd;//用来反映客户端ID号
pid_t pid;
struct msg
{
int type;//查询的是注册?登录?
char date[128];//用户数据;查询的时候就是单词,注册的时候就是姓名
char name[32];//保存用户姓名
};
int do_register(int c_fd,struct msg *s_msg,sqlite3 *db)//用户注册
{
char *errmsg;
char sql[256];
sprintf(sql,"insert into usr values('%s', %s);", s_msg->name, s_msg->date);
if(sqlite3_exec(db,sql, NULL, NULL, &errmsg) != SQLITE_OK)
{
printf("%s\n",errmsg);
strcpy(s_msg->date,"usr name exist.");
}
else
{
printf("client register is ok!\n");
strcpy(s_msg->date,"OK!");
}
if(send(c_fd,s_msg,sizeof(struct msg),0)<0)
{
printf("fail to send.\n");
return 0;
}
return 1;
}
int do_login(int c_fd,struct msg *s_msg,sqlite3 *db)//用户登录
{
printf("login........\n");
char sql[256] = {};//128太小会警告
char *errmsg;
int nrow;
int ncloumn;
char **resultp;
sprintf(sql, "select * from usr where name = '%s' and pass = '%s';", s_msg->name, s_msg->date);
printf("%s\n", sql);
if(sqlite3_get_table(db, sql, &resultp, &nrow, &ncloumn, &errmsg)!= SQLITE_OK)//数据库中未查询到信息
{
printf("%s\n", errmsg);
return -1;
}
else
{
printf("get_table ok!\n");
}
// 查询成功,数据库中拥有此用户
if(nrow == 1)
{
strcpy(s_msg->date, "OK");//这里把OK放进data里,client.c中才会那么比较
send(c_fd, s_msg, sizeof(struct msg), 0);
return 1;
}
if(nrow == 0) // 密码或者用户名错误,或者写else就可以
{
strcpy(s_msg->date,"usr/passwd wrong.");
send(c_fd, s_msg, sizeof(struct msg), 0);
return 0;
}
}
int do_searchword(struct msg *s_msg, char *word)
{
FILE *fp = NULL;
int word_len;
char row_data[512] = {'\0'};
int mark = 0;
char *p; //指向注释
//打开文件
if ((fp = fopen("dict.txt", "r")) == NULL)
{
perror("fail to open dict.txt.\n");
return -1;
}
//打印客户端要查询的单词
word_len = strlen(word);
printf("%s, len = %d\n", word, word_len );
//读取文件 行数据(一行一行读取),对比要查询的单词
//如果成功,该函数返回相同的 str 参数。
//如果到达文件末尾或者没有读取到任何字符,str 的内容保持不变,并返回一个空指针。
//如果发生错误,返回一个空指针。
while (fgets(row_data, 512, fp) != NULL)//把词典文件放入rowdata里面
{
mark = strncmp(row_data, word, word_len);//每行对比前word_len个字节,两个字符串比大小
if (mark != 0)
continue;
if (row_data[word_len] != ' ') //单词跟注释之间没有空格
goto _end;
// 找到了单词,跳过所有的空格
p = row_data + word_len;
while (*p == ' ')
{
p++;
}
strcpy(s_msg->date, p);//把单词注释放入到s_msg里面
fclose(fp);
return 1;
}
_end:
fclose(fp);
return 0; //文件对比完,单词未找到
}
void get_date(char *data)//把做好的日期放入data里面
{
time_t rowtime; //typedef long time_t;
// struct tm {
// int tm_sec; /* Seconds (0-60) */
// int tm_min; /* Minutes (0-59) */
// int tm_hour; /* Hours (0-23) */
// int tm_mday; /* Day of the month (1-31) */
// int tm_mon; /* Month (0-11) */
// int tm_year; /* Year - 1900 */
// int tm_wday; /* Day of the week (0-6, Sunday = 0) */
// int tm_yday; /* Day in the year (0-365, 1 Jan = 0) */
// int tm_isdst; /* Daylight saving time */
// };
struct tm *info;
// rowtime = the number of seconds since the Epoch, 1970-01-01 00:00:00 +0000 (UTC)
time(&rowtime);//查看当前时间存放到rowtime里面
//进行时间格式转换
info = localtime(&rowtime);
sprintf(data, "%d-%d-%d %d:%d:%d", info->tm_year + 1900, info->tm_mon + 1, info->tm_mday,info->tm_hour+15, info->tm_min, info->tm_sec);
printf("get date is : %s\n", data);
}
int do_query(int c_fd,struct msg *s_msg,sqlite3 *db)//查询单词
{
char sql[128] = {0};
char word[64] = {0};
int found = 0;
char date[128] = {0};
char *errmsg;
printf("\n");//显示换行
//单词查找
strcpy(word, s_msg->date);
found = do_searchword(s_msg, word);
if (found == 1)// 找到了单词,需要将 name,date,word 插入到历史记录表中去
{
get_date(date);//获取系统时间,存放到date里面
//sprintf(sql, "insert into user values('%s', '%s');", msg->name, msg->data);
sprintf(sql, "insert into record values('%s', '%s', '%s')", s_msg->name, date, word);
if (sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK)
{
printf("%s\n", errmsg);
return -1;
}
else
{
printf("sqlite3 insert record done.\n");
}
}
else if (found == 0)//没有找到
{
memset(s_msg->date, 0, strlen(s_msg->date));
strcpy(s_msg->date, "Not found!\n");
}
else if (found == -1)//dict.txt代开失败
{
memset(s_msg->date, 0, strlen(s_msg->date));
strcpy(s_msg->date, "fail to open dict.txt.");
}
//将查询的结果发送给客户端
if(send(c_fd, s_msg, sizeof(struct msg), 0)<0)
{
printf("send fail\n");
}
return 0;
}
int history_callback(void* arg,int colCount,char** colValue,char** colName)//主要用于接收服务端返回的历史记录
{
// record : name, date, word ,查询的历史记录放入colValue里面
int c_fd;
struct msg c_msg;
c_fd = *((int *)arg);
sprintf(c_msg.date, "%s , %s", colValue[1], colValue[2]);//只需要知道单词和查询时间
send(c_fd, &c_msg, sizeof(struct msg), 0);//把单词和查询时间发送给套接字,客户端去接收
return 0;
}
// 历史记录查询
int do_history(int c_fd, struct msg *c_msg, sqlite3 *db)
{
char sql[128] = {0};
char *errmsg;
//查询数据库
//会先执行*sql对应的功能命令,然后将结果传递给回调函数,回调函数根据结果再进一步执行
//关于回调函数详细可参考https://blog.csdn.net/u012351051/article/details/90382391
sprintf(sql, "select * from record where name = '%s'", c_msg->name);//查询的时候已登录,名字是登录时候就有
if(sqlite3_exec(db, sql, history_callback,(void *)&c_fd, &errmsg)!= SQLITE_OK)
{
printf("%s\n", errmsg);
}
else
{
printf("sqlite3 query record done.\n");
}
// 所有的记录查询发送完毕之后,给客户端发出一个结束信息
c_msg->date[0] = '\0';
send(c_fd, c_msg, sizeof(struct msg), 0);
return 0;
}
int do_client(int c_fd,sqlite3 *db)
{
struct msg s_msg;
while(recv(c_fd,&s_msg,sizeof(struct msg),0)>0)//正确接收,且阻塞式接收
{
printf("type:%d\n",s_msg.type);
switch(s_msg.type)
{
case R:
do_register(c_fd,&s_msg,db);
break;
case L:
do_login(c_fd,&s_msg,db);
break;
case Q:
do_query(c_fd,&s_msg,db);
break;
case H:
do_history(c_fd,&s_msg,db);
default:
printf("invalid MSG\n");
}
}
//跳出循环即意味着客户端退出,recv返回值为0
printf("client exit...\n");
close(c_fd);
exit(0);
}
//服务端代码
int main(int argc,char *argv[])
{
if(argc != 3)
{
printf("you input is invalid\n");
exit(0);
}
sqlite3 *db;//创建个数据库
if(sqlite3_open(DATABASE,&db) != SQLITE_OK)//判断数据库是否打开成功
{
printf("%s\n",sqlite3_errmsg(db));//打印错误消息
}
else
{
printf("open DATABASE success\n");
}
//创建套接字
s_fd = socket(AF_INET,SOCK_STREAM,0);//TCP协议,英特尔网域
if(s_fd == -1)//创建套接字失败
{
printf("creat socket failed\n");
perror("socket");
exit(-1);
}
//约定好IP端口和地址号
struct sockaddr_in s_addr;//创建服务器地址
bzero(&s_addr,sizeof(s_addr));//清空服务器内容的作用
s_addr.sin_family = AF_INET;//默认使用英特尔网域
s_addr.sin_port = htons(atoi(argv[2]));//设置端口号
inet_aton(argv[1],&s_addr.sin_addr);
int mark = bind(s_fd,(struct sockaddr *)&s_addr,sizeof(struct sockaddr_in)); //连接到套接字
if(mark == -1)//连接服务器失败
{
printf("lbind failed\n");
perror("bind");
exit(-1);
}
if(listen(s_fd,10)<0)
{
printf("listen failed\n");
}
signal(SIGCHLD,SIG_IGN);//专门处理僵尸进程
//进入一级菜单
while(1)
{
if((c_fd = accept(s_fd,NULL,NULL))<0)//接收客户端
{
perror("fail to accept");
}
if((pid = fork())<0)
{
perror("fail to creat fork");
return -1;
}
else if(pid == 0)//子进程处理请求
{
close(s_fd);
do_client(c_fd,db);
}
else//父进程,用来接收客户端请求
{
close(c_fd);
}
}
return 0;
}