Linux在线词典开发流程

项目需求:
按 12345分别实现用户注册(数据要保存到文件里)、登录、退出、查询单词、查询历史记录

客户端开发步骤:
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、创建套接字,接入服务器、配置结构体
2bind()
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;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

￴ㅤ￴￴ㅤ9527超级帅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值