一、功能需求:
我们首先明确一下,我们要制作的这个小服务器,需要具备什么功能
1.1、用户的注册和登录
使用sqlite3数据库,插入新的用户和查询用户的名字和密码是否匹配
1.2、查询单词
单词及其解释中,保存在一个文本文件当中。
需要打开文件,在其中查询
1.3用户查询记录
用户每次查询单词都会将用户名,查询的单词,查询的时间保存在sqlite3表中
二、服务器端代码流程
2.1打开数据库
#define DATABASE "my.db"
sqlite3* db;
/*打开数据库*/
if(sqlite3_open(DATABASE, &db) != SQLITE_OK)
{
printf("%s\n" , sqlite3_errmsg(db));
}
else
{
printf("open %s success\n",DATABASE);
}
2.2、配置并开启tcp服务器
sockfd = socket(AF_INET,SOCK_STREAM, 0);
if(sockfd < 0)
{
perror("socket");
return -1;
}
/*填写信息*/
bzero(&serveraddr,sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);//本机所有网络接口都可以连接进来
serveraddr.sin_port = htons(port);
/*绑定*/
if(bind(sockfd , (struct sockaddr *) &serveraddr, sizeof(serveraddr)) < 0)
{
perror("fail to bind.\n");
return -1;
}
/*设置监听模式*/
if(listen(sockfd , 5) < 0)
{
printf("Listen is fail.\n");
return -1;
}
/*忽略子进程,能自动回收*/
signal(SIGCHLD, SIG_IGN);
2.3、等待客户端连接
while (1)
{
/*
accept 第一个参数是服务器对外的fd接口
第二个参数是存放客户端数据的地址
第三个参数是用来存放客户端数据大小的一个地址
*/
if((acceptfd = accept(sockfd, NULL, NULL)) < 0)
{
perror("fail to accept.\n");
return -1;
}
printf("client connect.\n");
if((pid = fork()) < 0)
{
perror("fork fail.\n");
return -1;
}
else if(pid == 0)//开辟一个子进程来处理与客户端的通信
{
close(sockfd);
printf("go to client.\n");
do_client(acceptfd, db);//调用客户端处理函数
}
else
{
close(acceptfd);//主程序中关闭这个客户端的连接
}
}
return 0;
}
2.4、根据客户端的请求标志位,去执行不同的功能函数
/**
* @brief @客户端服务函数
*
* @param acceptfd 客户端fd
* @param db 数据库操作指针
*/
int do_client(int acceptfd,sqlite3* db)
{
MSG msg;
printf("In client hanshu.\n");
while (recv(acceptfd , &msg, sizeof(MSG) ,0) > 0)
{
printf("type: %d\n",msg.type);
switch(msg.type)
{
/*
#define R 1 //User - register
#define L 2 //User - Login
#define Q 3 //User - query
#define H 4 //User - history
#define root 5 //root - login
*/
case R:
do_register(acceptfd ,&msg, db);//注册
break;
case L:
do_login(acceptfd, &msg, db);//登录
break;
case Q:
do_query(acceptfd, &msg, db);//查询单词
break;
case H:
do_history(acceptfd, &msg, db);//查询历史记录
break;
default:
printf("Invalid data msg.\n");
}
}
printf("client exit.\n");
close(acceptfd);
exit(0);
}
2.5、在sqlite3数据库中创建两个表
![](https://img-blog.csdnimg.cn/9a72ac8fcbf243d0897fed106fed0e8b.png)
/*
通过primary key 主键不可重复熟悉,避免用户名的重复
text就是字符串 和char一样
*/
CREATE TABLE user(name text primary key, passwd text);
/*
记录用户的查询记录
用户名、查询时间 、 查询的单词
*/
CREATE TABLE record(name text, date text, word text);
2.6、用户注册函数
生成一个sql的命令字符串,用insert命令去插入新的用户数据
用sqlite3_exec函数来执行命令语句,错误信息会用最后一个参数指向。如果出错就会返回一个小于0的数,那就代表着插入失败了
中间两个NULL就是执行查询功能时的回调函数,以及要传给回调函数的参数
/**
* @brief 用户注册函数
*
* @param acceptfd 与客户端的连接接口
* @param msg
* @param db
* @return int
*/
int do_register(int acceptfd, MSG *msg,sqlite3* db)
{
char sql[BufLen] = {};
char *errmsg = NULL;
sprintf(sql ,"insert into user values('%s' , '%s');",msg->name,msg->data);
printf("%s\n" ,sql);
/*执行插入命令*/
if(sqlite3_exec(db, sql ,NULL ,NULL ,&errmsg) < 0)
{
printf("%s\n", errmsg);
return -1;
}
else
{
printf("register succeeful.\n");
bzero(msg->data,sizeof(msg->data));
strcpy(msg->data,"OK");
}
/*发送成功指令*/
if(send(acceptfd, msg, sizeof(MSG), 0) < 0)
{
perror("send");
return -1;
}
return 0;
}
2.7、用户登录函数
使用select命令,查询表中是否有用户名和密码都匹配的用户
然后使用不带回调函数的sqlite3_get_table来进行查询,第一个函数就是数据库的指针,第二个是要执行的命令,第三个是一个三维数组,保存着所有的查询结果,第四个是一共查出多少行(相当于有多少个结果),第五个就是每一行对应的结果有多少个字段
查询后直接判断nrow的值是不是1,是就代表查到匹配的用户,那么就可以回复客户端OK,代表登录成功
/**
* @brief 进行用户登录
*
* @param acceptfd 客户端套接字接口
* @param msg 数据结构体
* @param db 数据库指针
* @return int succeed 0 ,err -1
*/
int do_login(int acceptfd,MSG *msg,sqlite3* db)
{
char sql[BufLen] = {};
int nrow;
int ncolumn;
char *errmsg;
char **resultp;
sprintf(sql ,"select * from user where name = '%s' and passwd = '%s';", msg->name, msg->data);
printf("%s\n", sql);
/*resultp是一个三维数组,nrow是行 相当于查询出多少条结果,ncolum是列 相当于有多少个字段*/
sqlite3_get_table(db, sql, &resultp ,&nrow , &ncolumn , &errmsg);
if(nrow == 1)
{
bzero(msg->data,sizeof(msg->data));
strcpy(msg->data,"OK");
printf("msg->data: %s\n", msg->data);
printf("succeeful login in.\n");
if(send(acceptfd , msg, sizeof(MSG) , 0) < 0)
{
perror("send");
return -1;
}
}
else
{
strcpy(msg->data,"NOTOK");
printf("not ok.\n");
if(send(acceptfd , msg, sizeof(MSG) , 0) < 0)
{
perror("send");
return -1;
}
}
return 0;
}
2.8查询单词
通过两个函数来实现单词查询的功能
一个是do_query函数,在客户端服务函数中被调用,然后调用found函数,通过found函数的返回值来判断单词是否被找到。然后对单词的查询进行记录
单词查询的关键在于result = strncmp(buf ,word ,len);这个函数的返回值
因为函数的返回值如果不为0,那么就代表着两个字符串并不相等
一般是buf中的字符减去word中的字符
单词在文件中存放的顺序是按ascii表中的值,从小到大排序的,所以如果匹配的返回值是0,并且后面跟的不是空格(代表只是别人单词的一部分),那也就代表着后面也不会再有匹配的单词,那么就可以直接中断查询。
比如说 abg 减去 abc 互相减到第三个字母,返回值大于0 那么也就代表后面不会有abc了,后面单词的第三个字母的ascii值只会比现在这个abg大。
abcd 减去 abc abcd总体的ascii值比abc大 ,所以说到了abcd还没有找到匹配的单词,后面也不会有了。
使用fgets函数,可以一行一行的读取文件中的字符串
查询完之后就将时间等信息记录带另外一个表当中
/**
* @brief 查询单词
*
* @param acceptfd
* @param msg
* @param db
* @return int
*/
int do_query(int acceptfd,MSG *msg,sqlite3* db)
{
/*
1、给客户端发送查询的结果
2、在record.db中记录查询的时间和信息
3、客户端发送过来的data放单词,name里面放用户名
*/
char word[64] = {};
int ret;
char date[128];
char sql[1024];
char *errmsg;
/*data是放在要查询的单词,和查询到的结果 ,name放的是用户名*/
/*将名字复制到另外的数组中,避免互相影响*/
strcpy(word, msg->data);
ret = do_found(fileName,msg,word);
printf("查询完毕!\n");
/*结果放在msg->data当中*/
if(ret == 1)
{
/*找到了*/
/*将结果复制给*/
/*获取时间*/
do_gettime(date);
printf("%s 找到了对应的注释.\n",word);
printf("%s\n",date);
/*插入记录*/
sprintf(sql,"Insert into record values('%s','%s','%s');" , msg->name , date , word);
printf("%s \n ", sql);
if(sqlite3_exec(db , sql , NULL , NULL ,&errmsg) != SQLITE_OK)
{
printf("sqlite3_exec: %s \n" , errmsg);
}
}
else
{
/*没找到*/
printf("没找到.\n");
strcpy(msg->data , "not found");
}
/*发送数据*/
//strcpy(msg->data, word);
if(send(acceptfd, msg, sizeof(MSG) , 0) < 0)
{
perror("send");
return -1;
}
return 0;
}
/**
* @brief 在文件中查询单词
*
* @param file 文件名字
* @param msg 数据结构体
* @param word 单词
* @return int 0——没找到 1——找到了
*/
int do_found(char *file,MSG *msg,char *word)
{
/*打开文件进行比对*/
FILE *fp;
char buf[512] = {};
int len = 0;
int result;
char *p;
if((fp = fopen(file, "r")) == NULL)
{
printf("open fail.\n");
strcpy(msg->data , "fail to open file.");
return 0;
}
len = strlen(word);
printf("word: %s\n", word);
int i = 0;
/*成功打开*/
//fgets读到文件末尾会返回NULL
while(fgets(buf , 512 , fp) != NULL)
{
/*字典中的单词是按照字符数值的大小,从大到小排列的*/
i++;
result = strncmp(buf ,word ,len);
printf("第%d次查询\n" , i);
printf("result : %d len: %d \n",result,len);
printf("buf : %sword : %s\n" , buf , word);
if(result < 0)
{
bzero(buf , sizeof(buf));
continue;
}
/*遇到比自己大的还没找到,匹配到前面一部分 代表着后面也不会找到了*/
//result > 0 || ((result == 0) && buf[len] != ' ')
if(result > 0 || (result == 0) && buf[len] != ' ')
{
printf("found 寻找结束\n");
break;
}
p = buf + len;
/*跳过空格阶段*/
while (*p == ' ')
{
p++;
}
//将查询结果放在data中
strcpy(msg->data, p);
printf("found : %s\n", msg->data);
/*关闭文件*/
fclose(fp);
return 1;
}
/*没找到*/
fclose(fp);
return 0;
}
2.9查询用户的查找记录
使用sqlite3_exec(db,sql,history_callback, (void *)msg, &errmsg)进行查询
每查到一行表的数据,就会调用 history_callback函数,并且把查询的结果和msg作为参数传给history_callback。
在查询函数中添加了一个root用户的识别,如果是root用户,那么就可以查询所有用户的记录
/**
* @brief exec函数查询时的回调函数
*
* @param arg 用户调用exec时要传给回调函数的参数
* @param argc 数据一共有多少个字段
* @param argv 保存查询结果的数组,每个元素对应一个字段的值
* @param argv_name 保存字段名字的数组
* @return int
*/
static int history_callback(void *arg, int argc,char ** argv , char **argv_name)
{
/*msg->type里面放了网络通信的fd*/
MSG *msg = NULL;
msg = (MSG *)arg;
/*就打印时间和查询的单词*/
if(msg->root_flag == 0)
{
sprintf(msg->data, "%s , %s",argv[1] , argv[2]);
printf("type : %d name : %s data : %s" , msg->type , msg->name ,msg->data);
}
else
{
sprintf(msg->data, "%s , %s , %s",argv[0] ,argv[1] , argv[2]);
printf("type : %d name : %s data : %s" , msg->type , msg->name ,msg->data);
}
/*发送*/
if(send(msg->type , msg , sizeof(MSG) , 0) < 0)
{
perror("send ");
return -1;
}
return 0;
}
/**
* @brief 查看用户历史记录
*
* @param acceptfd 网络通信接口
* @param msg 数据结构体
* @param db 数据库指针
* @return int 0--正常 1--出错了
*/
int do_history(int acceptfd,MSG *msg,sqlite3* db)
{
char sql[128];//sql数据库执行语句存放的字符串
char *errmsg;
msg->type = acceptfd;//将通信接口fd也传给回调函数
printf("name = '%s' \n" , msg->name);
if(msg->root_flag == 0)
{
/*普通用户*/
sprintf(sql, "select * from record where name='%s';" , msg->name);
}
else
{
/*root用户*/
sprintf(sql, "select * from record;");
}
printf("sql : %s \nroot_flag : %d\n", sql,msg->root_flag);
printf("sqlite3_exec\n");
/*执行查询函数,并且在函数中将数据全部传递给客户端*/
if(sqlite3_exec(db,sql,history_callback, (void *)msg, &errmsg) < 0)
{
printf("%s \n", errmsg);
return 1;
}
printf("sqlite3_exec end.\n");
/*数据全部发送完成之后,要发送第一个字符为空的数据,告诉客户端传输已经完成*/
msg->data[0] = '\0';
if(send(acceptfd , msg , sizeof(MSG) , 0) < 0)
{
perror("send");
return 1;
}
return 0;
}
2.10服务器端完整代码
.c
#include "exchange.h"
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <sqlite3.h>
#include <signal.h>
#include <time.h>
#define DATABASE "my.db"
#define BufLen 1024
#define fileName "dict.txt"
int do_client(int acceptfd,sqlite3* db);
int do_register(int , MSG *,sqlite3*);
int do_login(int ,MSG *,sqlite3* );
int do_query(int ,MSG *, sqlite3*);
int do_history(int ,MSG *, sqlite3*);
int do_root(int ,MSG *,sqlite3 *);
int do_found(char *file,MSG *msg,char *word);
void do_gettime(char *);
int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in serveraddr;
int acceptfd;//接受服务器的fd
pid_t pid;
sqlite3* db;
/*打开数据库*/
if(sqlite3_open(DATABASE, &db) != SQLITE_OK)
{
printf("%s\n" , sqlite3_errmsg(db));
}
else
{
printf("open %s success\n",DATABASE);
}
sockfd = socket(AF_INET,SOCK_STREAM, 0);
if(sockfd < 0)
{
perror("socket");
return -1;
}
/*填写信息*/
bzero(&serveraddr,sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
serveraddr.sin_port = htons(port);
/*绑定*/
if(bind(sockfd , (struct sockaddr *) &serveraddr, sizeof(serveraddr)) < 0)
{
perror("fail to bind.\n");
return -1;
}
/*设置监听模式*/
if(listen(sockfd , 5) < 0)
{
printf("Listen is fail.\n");
return -1;
}
/*忽略子进程*/
signal(SIGCHLD, SIG_IGN);
while (1)
{
/*
accept 第一个参数是服务器对外的fd接口
第二个参数是存放客户端数据的地址
第三个参数是用来存放客户端数据大小的一个地址
*/
if((acceptfd = accept(sockfd, NULL, NULL)) < 0)
{
perror("fail to accept.\n");
return -1;
}
printf("client connect.\n");
if((pid = fork()) < 0)
{
perror("fork fail.\n");
return -1;
}
else if(pid == 0)//开辟一个子进程来处理与客户端的通信
{
close(sockfd);
printf("go to client.\n");
do_client(acceptfd, db);//调用客户端处理函数
}
else
{
close(acceptfd);//主程序中关闭这个客户端的连接
}
}
return 0;
}
/**
* @brief @客户端服务函数
*
* @param acceptfd 客户端fd
* @param db 数据库操作指针
*/
int do_client(int acceptfd,sqlite3* db)
{
MSG msg;
printf("In client hanshu.\n");
while (recv(acceptfd , &msg, sizeof(MSG) ,0) > 0)
{
printf("type: %d\n",msg.type);
switch(msg.type)
{
/*
#define R 1 //User - register
#define L 2 //User - Login
#define Q 3 //User - query
#define H 4 //User - history
#define root 5 //root - login
*/
case R:
do_register(acceptfd ,&msg, db);
break;
case L:
do_login(acceptfd, &msg, db);
break;
case Q:
do_query(acceptfd, &msg, db);
break;
case H:
do_history(acceptfd, &msg, db);
break;
default:
printf("Invalid data msg.\n");
}
}
printf("client exit.\n");
close(acceptfd);
exit(0);
}
/**
* @brief 用户注册函数
*
* @param acceptfd 与客户端的连接接口
* @param msg
* @param db
* @return int
*/
int do_register(int acceptfd, MSG *msg,sqlite3* db)
{
char sql[BufLen] = {};
char *errmsg = NULL;
sprintf(sql ,"insert into user values('%s' , '%s');",msg->name,msg->data);
printf("%s\n" ,sql);
/*执行插入命令*/
if(sqlite3_exec(db, sql ,NULL ,NULL ,&errmsg) < 0)
{
printf("%s\n", errmsg);
return -1;
}
else
{
printf("register succeeful.\n");
bzero(msg->data,sizeof(msg->data));
strcpy(msg->data,"OK");
}
/*发送成功指令*/
if(send(acceptfd, msg, sizeof(MSG), 0) < 0)
{
perror("send");
return -1;
}
return 0;
}
/**
* @brief 进行用户登录
*
* @param acceptfd 客户端套接字接口
* @param msg 数据结构体
* @param db 数据库指针
* @return int succeed 0 ,err -1
*/
int do_login(int acceptfd,MSG *msg,sqlite3* db)
{
char sql[BufLen] = {};
int nrow;
int ncolumn;
char *errmsg;
char **resultp;
sprintf(sql ,"select * from user where name = '%s' and passwd = '%s';", msg->name, msg->data);
printf("%s\n", sql);
/*resultp是一个三维数组,nrow是行 相当于查询出多少条结果,ncolum是列 相当于有多少个字段*/
sqlite3_get_table(db, sql, &resultp ,&nrow , &ncolumn , &errmsg);
if(nrow == 1)
{
bzero(msg->data,sizeof(msg->data));
strcpy(msg->data,"OK");
printf("msg->data: %s\n", msg->data);
printf("succeeful login in.\n");
if(send(acceptfd , msg, sizeof(MSG) , 0) < 0)
{
perror("send");
return -1;
}
}
else
{
strcpy(msg->data,"NOTOK");
printf("not ok.\n");
if(send(acceptfd , msg, sizeof(MSG) , 0) < 0)
{
perror("send");
return -1;
}
}
return 0;
}
/**
* @brief 查询单词
*
* @param acceptfd
* @param msg
* @param db
* @return int
*/
int do_query(int acceptfd,MSG *msg,sqlite3* db)
{
/*
1、给客户端发送查询的结果
2、在record.db中记录查询的时间和信息
3、客户端发送过来的data放单词,name里面放用户名
*/
char word[64] = {};
int ret;
char date[128];
char sql[1024];
char *errmsg;
/*data是放在要查询的单词,和查询到的结果 ,name放的是用户名*/
/*将名字复制到另外的数组中,避免互相影响*/
strcpy(word, msg->data);
ret = do_found(fileName,msg,word);
printf("查询完毕!\n");
/*结果放在msg->data当中*/
if(ret == 1)
{
/*找到了*/
/*将结果复制给*/
/*获取时间*/
do_gettime(date);
printf("%s 找到了对应的注释.\n",word);
printf("%s\n",date);
/*插入记录*/
sprintf(sql,"Insert into record values('%s','%s','%s');" , msg->name , date , word);
printf("%s \n ", sql);
if(sqlite3_exec(db , sql , NULL , NULL ,&errmsg) != SQLITE_OK)
{
printf("sqlite3_exec: %s \n" , errmsg);
}
}
else
{
/*没找到*/
printf("没找到.\n");
strcpy(msg->data , "not found");
}
/*发送数据*/
//strcpy(msg->data, word);
if(send(acceptfd, msg, sizeof(MSG) , 0) < 0)
{
perror("send");
return -1;
}
return 0;
}
/**
* @brief 在文件中查询单词
*
* @param file 文件名字
* @param msg 数据结构体
* @param word 单词
* @return int 0——没找到 1——找到了
*/
int do_found(char *file,MSG *msg,char *word)
{
/*打开文件进行比对*/
FILE *fp;
char buf[512] = {};
int len = 0;
int result;
char *p;
if((fp = fopen(file, "r")) == NULL)
{
printf("open fail.\n");
strcpy(msg->data , "fail to open file.");
return 0;
}
len = strlen(word);
printf("word: %s\n", word);
int i = 0;
/*成功打开*/
//fgets读到文件末尾会返回NULL
while(fgets(buf , 512 , fp) != NULL)
{
/*字典中的单词是按照字符数值的大小,从大到小排列的*/
i++;
result = strncmp(buf ,word ,len);
printf("第%d次查询\n" , i);
printf("result : %d len: %d \n",result,len);
printf("buf : %sword : %s\n" , buf , word);
if(result < 0)
{
bzero(buf , sizeof(buf));
continue;
}
/*遇到比自己大的还没找到,匹配到前面一部分 代表着后面也不会找到了*/
//result > 0 || ((result == 0) && buf[len] != ' ')
if(result > 0 || (result == 0) && buf[len] != ' ')
{
printf("found 寻找结束\n");
break;
}
p = buf + len;
/*跳过空格阶段*/
while (*p == ' ')
{
p++;
}
//将查询结果放在data中
strcpy(msg->data, p);
printf("found : %s\n", msg->data);
/*关闭文件*/
fclose(fp);
return 1;
}
/*没找到*/
fclose(fp);
return 0;
}
/**
* @brief exec函数查询时的回调函数
*
* @param arg 用户调用exec时要传给回调函数的参数
* @param argc 数据一共有多少个字段
* @param argv 保存查询结果的数组,每个元素对应一个字段的值
* @param argv_name 保存字段名字的数组
* @return int
*/
static int history_callback(void *arg, int argc,char ** argv , char **argv_name)
{
/*msg->type里面放了网络通信的fd*/
MSG *msg = NULL;
msg = (MSG *)arg;
/*就打印时间和查询的单词*/
if(msg->root_flag == 0)
{
sprintf(msg->data, "%s , %s",argv[1] , argv[2]);
printf("type : %d name : %s data : %s" , msg->type , msg->name ,msg->data);
}
else
{
sprintf(msg->data, "%s , %s , %s",argv[0] ,argv[1] , argv[2]);
printf("type : %d name : %s data : %s" , msg->type , msg->name ,msg->data);
}
/*发送*/
if(send(msg->type , msg , sizeof(MSG) , 0) < 0)
{
perror("send ");
return -1;
}
return 0;
}
/**
* @brief 查看用户历史记录
*
* @param acceptfd 网络通信接口
* @param msg 数据结构体
* @param db 数据库指针
* @return int 0--正常 1--出错了
*/
int do_history(int acceptfd,MSG *msg,sqlite3* db)
{
char sql[128];//sql数据库执行语句存放的字符串
char *errmsg;
msg->type = acceptfd;//将通信接口fd也传给回调函数
printf("name = '%s' \n" , msg->name);
if(msg->root_flag == 0)
{
/*普通用户*/
sprintf(sql, "select * from record where name='%s';" , msg->name);
}
else
{
/*root用户*/
sprintf(sql, "select * from record;");
}
printf("sql : %s \nroot_flag : %d\n", sql,msg->root_flag);
printf("sqlite3_exec\n");
/*执行查询函数,并且在函数中将数据全部传递给客户端*/
if(sqlite3_exec(db,sql,history_callback, (void *)msg, &errmsg) < 0)
{
printf("%s \n", errmsg);
return 1;
}
printf("sqlite3_exec end.\n");
/*数据全部发送完成之后,要发送第一个字符为空的数据,告诉客户端传输已经完成*/
msg->data[0] = '\0';
if(send(acceptfd , msg , sizeof(MSG) , 0) < 0)
{
perror("send");
return 1;
}
return 0;
}
/**
* @brief 查看所有用户的记录
*
* @param acceptfd 通信接口fd
* @param msg 数据结构体
* @param db 数据库指针
* @return int 0正常 -1--出错
*/
int do_root(int acceptfd,MSG *msg,sqlite3* db)
{
char sql[256] = {};
bzero(sql , sizeof(sql));
sprintf(sql, "select * from record;");
}
/**
* @brief 将时间记录在传进来的字符数组里面
*
* @param data
*/
void do_gettime(char data[])
{
time_t t;
struct tm *tp;
time(&t);
tp = localtime(&t);
bzero(data , 0);
/*将数据生成在刚刚的字符串里面*/
sprintf(data,"%d-%d-%d %d:%d:%d",tp->tm_year + 1900,tp->tm_mon +1,tp->tm_mday, tp->tm_hour, tp->tm_min , tp->tm_sec);
}
.h
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define datanum 32
#define R 1 //User - register
#define L 2 //User - Login
#define Q 3 //User - query
#define H 4 //User - history
#define root 5 //root - login
#define port 5001
/*通信双方信息结构体
两者需要保持一致
*/
typedef struct
{
int type;//类型
int root_flag;
char name[datanum]; //单词名字
char data[256]; //单词翻译
}MSG;
三、客户端代码及流程
3.1、代码总览
客户端代码主要涉及和服务器通信,处理字符串判断服务器回复的数据
#include "exchange.h"
/*root login*/
int do_rootLogin(int socketfd,MSG *msg)
{
bzero(msg,sizeof(MSG));
msg->type = root;
printf("Input root passwd:\n");
scanf("%s",msg->data);
getchar();
if(send(socketfd,msg,sizeof(MSG), 0) < 0)
{
printf("fail to send\n");
return -1;
}
/*接受数据直接放在这个msg结构体里面*/
if(recv(socketfd , msg , sizeof(MSG), 0) < 0)
{
printf("fail to recv\n");
return -1;
}
if(strncmp(msg->data ,"OK" , 3) == 0)
{
printf("root login...\n");
return 1;
}
printf("%s\n",msg->data);
return -1;
}
/*注册账号*/
int do_register(int sockfd,MSG *msg)
{
printf("register ...\n");
msg->type = R;
printf("Input name:");
scanf("%s",msg->name);
getchar();
printf("Input passwd:");
scanf("%s",msg->data);
getchar();
if(send(sockfd,msg,sizeof(MSG),0) < 0)
{
printf("fail to send.\n");
return -1;
}
printf("send.\n");
if(recv(sockfd, msg ,sizeof(MSG), 0) < 0)
{
printf("fail to recv");
return -1;
}
printf("recv.\n");
printf("%s\n",msg->data);
return 1;
}
/**
* @brief 用户登录函数
*
* @param socketfd
* @param msg
* @return int
* 发送和接受消息验证最好都放在一个函数里面去实现,通过返回值来判断是否成功
*/
int do_login(int socketfd,MSG *msg)
{
printf("login ...\n");
/*登录标记位*/
bzero(msg,sizeof(MSG));
msg->type = L;
/*用户输入信息*/
printf("Input name:\n");
scanf("%s",msg->name);
getchar();
printf("Input passwd:\n");
scanf("%s",msg->data);
getchar();
if(send(socketfd, msg, sizeof(MSG), 0) < 0)
{
printf("send fail.\n");
return -1;
}
/*不要sizeof指针*/
if(recv(socketfd, msg ,sizeof(MSG), 0) < 0)
{
printf("recv fail.\n");
return -1;
}
/*验证服务器返回的信息*/
/*strncmp 对比前n的字符,如果一样的话就会返回0*/
/*返回OK代表可以*/
if(strncmp(msg->data , "OK" ,3) == 0)
{
/*验证成功*/
printf("Login ok\n");
/*登录成功之后判断用户是不是root,如果是,标志位置1*/
int ret;
if((ret = strncmp(msg->name,"root" , 5)) == 0)
{
msg->root_flag = 1;
printf("是root用户\n");
}
printf("ret : %d\n", ret);
printf("name %s \n",msg->name);
return 1;
}
else
{/*登录失败*/
printf("Login not ok\n");
printf("%s\n",msg->data);
return -1;
}
printf("msg->data :%s\n",msg->data);
return -1;
}
/*
查询单词函数
*/
int do_query(int scoketfd,MSG *msg)
{
printf("query... \n");
msg->type = Q;
puts("--------------------");
while (1)
{
printf("Input word\n");
scanf("%s",msg->data);
getchar();
/*如果用户输入#代表退出*/
if(strncmp(msg->data,"#",1) == 0)
{
break;
}
if(send(scoketfd, msg ,sizeof(MSG) ,0) < 0)
{
printf("send fail.\n");
return -1;
}
/*等待服务器传递回来的数据,然后直接打印就好了*/
if(recv(scoketfd , msg, sizeof(MSG), 0) < 0)
{
printf("recv fail.\n");
return -1;
}
printf("%s\n",msg->data);
}
return 0;
}
int do_history(int scoketfd,MSG *msg)
{
printf("history ...\n");
/*1、先发送查询信息*/
msg->type = H;
printf("root_flag : %d\n", msg->root_flag);
if(send(scoketfd, msg, sizeof(MSG), 0) < 0)
{
printf("send fail.\n");
return -1;
}
/*将接收到的数据都显示出来 ,指导收到空的字符串,代表结束*/
while (1)
{
recv(scoketfd, msg, sizeof(MSG), 0);
/*收到\0 就代表着发送完成*/
if(msg->data[0] == '\0')
{
break;
}
else
{
printf("%s\n", msg->data);
}
}
return 0;
}
// server 192.168.137.87
int main(int argc ,char **argv)
{
if(argc != 3)
{
printf("Usage : %s server port \n",argv[0]);
return -1;
}
int socketfd;
struct sockaddr_in serveraddr;
int n;
MSG msg;
/*创建套接字,连接服务器*/
if((socketfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)//第三个参数选0,类型会自动匹配第二个参数的类型
{
perror("fail to socket \n");
return -1;
}
bzero(&serveraddr,sizeof(serveraddr));
/*填充结构体信息*/
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2]));//转换为网络字节
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);//将点分转换成网络字节序
/*连接服务器*/
if(connect(socketfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr)) == -1)
{
perror("fail to connect");
return -1;
}
/*进入一级菜单*/
while (1)
{
printf("************************************************\n");
printf("1.register 2.login 3.root login 4.quit \n");
printf("************************************************\n");
printf("please choose:\n");
scanf("%d",&n);
getchar();
switch (n)
{
case 1:
do_register(socketfd,&msg);
break;
case 2:
if(do_login(socketfd,&msg) == 1)
{
goto next;
}
break;
case 3:
if(do_rootLogin(socketfd,&msg) == 1)
{
goto ROOT;
}
break;
case 4:
close(socketfd);
exit(0);
break;
default:
printf("Invalid data cmd.\n");
break;
}
}
next:
while (1)
{
printf("************************************************\n");
printf("1.query_word 2.history_record 3. quit\n");
printf("************************************************\n");
printf("please choose:\n");
scanf("%d",&n);
getchar();
switch(n)
{
case 1:
do_query(socketfd,&msg);
break;
case 2:
do_history(socketfd, &msg);
break;
case 3:
close(socketfd);
exit(0);
break;
default:
printf("Invalid data cmd.\n");
break;
}
}
ROOT:
while (1)
{
printf("************************************************\n");
printf("1.query_word 2.history_record 3. quit\n");
printf("************************************************\n");
printf("please choose:\n");
}
return 0;
}