Linux应用开发项目实战---------在线词典

目录

一. 项目需求分析

二. 程序设计流程图

1. 客户端

2. 服务端

三.程序源码

1. 客户端代码

2. 服务端

四. 项目总结与反思

1. 项目知识点总结

(1)网络编程

(2)文件IO

(3)sqlite3数据库

2. 项目中对缓冲区的运用总结 

3. 客户端与服务端的通信结构体二者要一致, 发多少接收多少


一. 项目需求分析

1. 要求客户端有注册登录模块, 客户端的数据和相关信息存储在服务器的数据库中

2. 客户端登录成功之后可以查询单词, 服务器将数据库文件中的对应单词的意思发送给客户端

3. 客户端还要求可以查询历史记录

二. 程序设计流程图

1. 客户端

 

2. 服务端

 

三.程序源码

1. 客户端代码

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <strings.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>


#define N 64
#define Erro_Handle(msg) do{perror(msg); exit(EXIT_FAILURE);}while(0)
#define DEBUG(msg) printf("======%s=====\n", msg)

//自定义消息类型
enum {
	R, L, Q, H
};
//设置通信结构体
typedef struct _msg {
	int type;
	char name[N];
	char data[256];
	char password[64];

}MSG;

typedef struct sockaddr_in Addr_in;
typedef struct sockaddr Addr;

void do_register(int sockfd, MSG *msg);
int do_login(int sockfd, MSG *msg);
void do_query(int sockfd, MSG *msg);
void do_history(int sockfd, MSG *msg);

int main(int argc, const char *argv[])
{
	int sockfd;
	Addr_in addr;
	
//输入opt
	int opt;
//通信结构体
	MSG msg;

	if(argc < 3) {
		printf("<%s><addr><port>\n", argv[0]);
		exit(0);
	}

	//1. 准备套接字
	if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
		Erro_Handle("sokcet");

	bzero(&addr, sizeof(Addr_in));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(atoi(argv[2]));
	if(!(inet_aton(argv[1], &addr.sin_addr)))
		Erro_Handle("inet_aton");

	if((connect(sockfd, (Addr *)&addr, sizeof(Addr_in))) < 0)
		Erro_Handle("connect");

	
	//2. 用户交互的界面
	while(1) {
		printf("***********************************************\n");
		printf("*1. register       2. login            3. quit*\n");
		printf("***********************************************\n");
		
		scanf("%d", &opt);
		getchar();           //吃掉回车
		switch(opt) {
		case 1:
			do_register(sockfd, &msg);
			break;
		case 2:
			if((do_login(sockfd, &msg)) == 1)
				goto next;
			break;
		case 3:
			printf("program exit!\n");
			close(sockfd);
			exit(0);

		default:
			printf("Invalid opt !\n");
		}
		
	}

next:
	while(1) {
		printf("***********************************************\n");
		printf("*1, query          2. history          3. qiut*\n");
		printf("***********************************************\n");


		scanf("%d", &opt);
		switch(opt) {
		case 1:
			do_query(sockfd, &msg);
			break;
		case 2:
			do_history(sockfd, &msg);
			break;
		case 3:
			printf("quit!\n");
			exit(0);
		default:
			printf("Invalid opt !\n");
		}
	}
	
	return 0;
}

//注册模块
void do_register(int sockfd, MSG *msg) {
	printf("%s %d\n", __FUNCTION__, __LINE__);
	msg->type = R;

	printf("please input your name:");
	scanf("%s", msg->name);
	getchar();
	printf("please input your password:");
	scanf("%s", msg->password);

	if(send(sockfd, msg, sizeof(MSG), 0) < 0)
		Erro_Handle("send");

	if(recv(sockfd, msg, sizeof(MSG), 0) < 0)
		Erro_Handle("recv");

	printf("%s\n", msg->data);
	return;
}

//登录模块
int do_login(int sockfd, MSG *msg) {
	printf("%s %d\n", __FUNCTION__, __LINE__);
	msg->type = L;

	printf("please your name:");
	scanf("%s", msg->name);
	getchar();
	printf("please input your password:");
	scanf("%s", msg->password);

	if(send(sockfd, msg, sizeof(MSG), 0) < 0)
		Erro_Handle("send");

	if(recv(sockfd, msg, sizeof(MSG), 0) < 0)
		Erro_Handle("recv");

	if(strncmp(msg->data, "ok", 2) == 0) {
		printf("login success. \n");
		return 1;
	} else {
		printf("login faile. \n");
	}

	return 0;
}

//查询模块
void do_query(int sockfd, MSG *msg) {
	printf("%s %d\n", __FUNCTION__, __LINE__);

	msg->type = Q;
	while(1) {
		printf("please input you want query word:");
		scanf("%s", msg->data);
		getchar();

		//返回上一级菜单
		if(msg->data[0] == '#')
			break;

		//发送查询单词请求
		if(send(sockfd, msg, sizeof(MSG), 0) < 0)
			Erro_Handle("send");

		//接收服务端的查询信息
		if(recv(sockfd, msg, sizeof(MSG), 0) < 0)
			Erro_Handle("recv");

		printf("%s\n", msg->data);
	}
	return ;
}
//查询历史记录模块
void do_history(int sockfd, MSG *msg) {
	printf("%s %d\n", __FUNCTION__, __LINE__);
	msg->type = H;
	
	printf("please input you want history of name:");
	scanf("%s", msg->name);
	getchar();

	if(send(sockfd, msg, sizeof(MSG), 0) < 0)
		Erro_Handle("send");
	
	if(recv(sockfd, msg, sizeof(MSG), 0) < 0)
		Erro_Handle("recv");

	printf("%s\n", msg->data);

	return ;
}

2. 服务端

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <strings.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sqlite3.h>
#include <signal.h>
#include <time.h>
#include <sys/epoll.h>

#define N 64
#define BACKLOG 5
#define DATABASE "Dic.db" 
#define Erro_Handle(msg) do{perror(msg); exit(EXIT_FAILURE);}while(0)
#define DEBUG(msg) printf("======%s=====\n", msg)
#define NUM 1024

//自定义消息类型
enum {
	R, L, Q, H
};
//设置通信结构体
typedef struct _msg {
	int type;
	char name[N];
	char data[256];
	char password[64];

}MSG;

typedef struct sockaddr_in Addr_in;
typedef struct sockaddr Addr;

void do_client(int acceptfd, MSG *msg, sqlite3 *db);
void do_register(int acceptfd, MSG *msg, sqlite3 *db);
void do_login(int acceptfd, MSG *msg, sqlite3 *db);
void do_query(int acceptfd, MSG *msg, sqlite3 *db);
void do_history(int acceptfd, MSG *msg, sqlite3 *db);

int main(int argc, char *argv[])
{
	sqlite3 *db;
	int sockfd, acceptfd;
	Addr_in addr, peeraddr;
	socklen_t peeraddr_size = sizeof(Addr_in);
	MSG msg;

	int epfd;
	int nfds;         //epoll_wait的返回值, 代表IO准备的数量
	int  i;
	struct epoll_event tmp, events[NUM];

	if((sqlite3_open(DATABASE, &db)) != SQLITE_OK) {
		printf("%s \n", sqlite3_errmsg(db));
		return -1;
	} else {
		printf("sqlite3_open success. \n");
	}

	if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
		Erro_Handle("socket");

	//设置套接字属性
	int flag = 1;
	setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(int));

	bzero(&addr, sizeof(Addr_in));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(atoi(argv[2]));
	addr.sin_addr.s_addr = inet_addr(argv[1]);

	if((bind(sockfd, (Addr *)&addr, sizeof(Addr_in))) < 0)
		Erro_Handle("bind");

	if(listen(sockfd, BACKLOG) < 0)
		Erro_Handle("listen");

	//signal(SIGCHLD, SIG_IGN);  //子进程结束就回收
	/*采用epoll IO多路复用*/
	if((epfd = epoll_create(1)) < 0)
		Erro_Handle("epoll_create");
	tmp.data.fd = sockfd;
	tmp.events = EPOLLIN;
	if(epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &tmp) < 0)   //将事件tmp结构体加入到events结构体数组中去
		Erro_Handle("epoll_ctl");
	while(1) {
		if((nfds = epoll_wait(epfd, events, NUM, -1)) < 0) {    //看events数组中io就绪的fd数量
			Erro_Handle("epoll_wait");
		}

		for(i = 0; i < nfds; i++) {
			if(events[i].data.fd == sockfd) {
				acceptfd = accept(sockfd, (Addr *)&peeraddr, &peeraddr_size);
				if(acceptfd < 0) {
					Erro_Handle("accept");
				} else {
					printf("[%s %d] connect success.\n", inet_ntoa(peeraddr.sin_addr), \
							ntohs(peeraddr.sin_port));
				}
				tmp.data.fd = acceptfd;
				tmp.events = EPOLLIN;
				epoll_ctl(epfd, EPOLL_CTL_ADD, acceptfd, &tmp);  //把新发生的事件装入到events

			} else {
				bzero(&msg, sizeof(MSG));   //empty structure
				do_client(acceptfd, &msg, db);
			}
		}
	}

	close(sockfd);
	close(acceptfd);

	return 0;
}

void do_client(int acceptfd, MSG *msg, sqlite3 *db) {
	while(recv(acceptfd, msg, sizeof(MSG), 0)) {
		switch(msg->type) {
		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 opt !\n");
			exit(0);
		}	
	}
}

void do_register(int acceptfd, MSG *msg, sqlite3 *db) {
	char sql[128];
	char *errmsg;

	//sql语句字符型的加上引号, 
	sprintf(sql, "insert into usr values('%s', %s);", msg->name, msg->password);
	if((sqlite3_exec(db, sql, NULL, NULL, &errmsg)) != SQLITE_OK) {
		printf("insert failed .\n");
		strcpy(msg->data, "name already");
	} else {
		printf("inser success .\n");
		strcpy(msg->data, "register success.");
	}
	if(send(acceptfd, msg, sizeof(MSG), 0) < 0)
		Erro_Handle("send");

	return ;
}


void do_login(int acceptfd, MSG *msg, sqlite3 *db) {
	char sql[128];
	char *errmsg;
	int row, column;
	char **result;

	sprintf(sql, "select * from usr where name = '%s' and password = '%s'", \
			msg->name, msg->password);

	if((sqlite3_get_table(db, sql, &result, &row, &column, &errmsg)) != SQLITE_OK) {
		printf("%s\n", "sqlite3_get_table failed");
		sprintf(msg->data, "login failed. ");
	} else {
		printf("sqlite3_get_table success.\n");
	}

	if(row == 1) {
		strcpy(msg->data, "ok");
	}
	if(row == 0) {
		strcpy(msg->data, "login failed.");
	}

	if(send(acceptfd, msg, sizeof(MSG), 0) < 0)
		Erro_Handle("send");

	return ;
}

int search_word(int acceptfd, MSG *msg, sqlite3 *db, char word[]) {
	FILE *fp;
	char buf[512];
	int len, result;
	char *p;
	len = strlen(word);

	if((fp = fopen("dict.txt", "r")) == NULL) {
		Erro_Handle("fopen");
		sprintf(msg->data, "fopen");
		send(acceptfd, msg, sizeof(MSG), 0);
	}

	while(fgets(buf, 512, fp) != NULL) {
		result = strncmp(buf, word, len);

		if(result < 0)
			continue;
		//找到的算法
		if(result == 0 && buf[len] == ' ') {
			p = &buf[len];
			while(*p == ' ') {
				p++;
			}
			strcpy(msg->data, p);
			send(acceptfd, msg, sizeof(MSG), 0);
			return 1;
		} 


	}
	strcpy(msg->data, "Not found");
	send(acceptfd, msg, sizeof(MSG), 0);
	return 0;

}

void get_date(char *date) {
	struct tm *t;
	time_t _time;
	
	_time = time(NULL);
	t = localtime(&_time);
	sprintf(date, "%4d-%02d-%02d-%02d-%02d-%02d", t->tm_year + 1900, t->tm_mon + 1, \
			t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);

#if 0
	struct tm {
		int tm_sec;         /* seconds */
		int tm_min;         /* minutes */
		int tm_hour;        /* hours */
		int tm_mday;        /* day of the month */
		int tm_mon;         /* month */
		int tm_year;        /* year */
		int tm_wday;        /* day of the week */
		int tm_yday;        /* day in the year */
		int tm_isdst;       /* daylight saving time */
	};
#endif

}


//查询模块, 文件IO
void do_query(int acceptfd, MSG *msg, sqlite3 *db) {
	char *word;
	int found;
	char *errmsg;
	char sql[128];
	word = msg->data;
	char date[64];      //获取日期

	found = search_word(acceptfd, msg, db, word);
	printf("查询一个单词完毕\n");
	if(found == 1) {
		/*************************************************/
		printf("query success.\n");
		/*************************************************/

		get_date(date);
		printf("%s\n", date);
		sprintf(sql, "insert into record values('%s', '%s', '%s')", msg->name, date, word);
		if((sqlite3_exec(db, sql, NULL, NULL, &errmsg)) != SQLITE_OK) {
			printf("sqlite3_exec insert record failed.\n");
			return;
		}else {
			printf("insert record success.\n");
		}

	} else {
		printf("client query failed.\n");
	}
	return ;
}

#if 0
void do_history(int acceptfd, MSG *msg, sqlite3 *db) {	
	char sql[128];
	char *errmsg;
	char **result;
	int row, column, index, i, j;
	int len = 0;

	sprintf(sql, "select * from record where name = '%s'", msg->name);
	if((sqlite3_get_table(db, sql, &result, &row, &column, &errmsg)) != SQLITE_OK) {
		printf("sqlite3_get_table failed.\n");
		return ;
	} else {
		printf("select record success.\n");
		index = column;
		/************************************/
		printf("%d %d\n", row, column);
		/************************************/
		for(i = 0; i < row; i++) {
			for(j = 0; j < column; j++) {
				len = len + strlen(result[index]);
				sprintf(&msg->data[len], "%s | ", result[index++]);
				printf("%s | ", result[index++]);
			}
			putchar(10);
		}
		send(acceptfd, msg, sizeof(MSG), 0);
	}

	return ;
}
#endif

int CallBack_history(void* arg,int ncolumns ,char** f_value,char** f_name) {
	int acceptfd;
	MSG msg;
	acceptfd = *((int *)arg);

	sprintf(msg.data, "%s %s", f_value[1], f_value[2]);
	send(acceptfd, &msg, sizeof(MSG), 0);

	return 0;
}

void do_history(int acceptfd, MSG *msg, sqlite3 *db) {
	char sql[128];
	char *errmsg;

	sprintf(sql, "select * from record where name = '%s';", msg->name);
	if(sqlite3_exec(db, sql, CallBack_history, (void *)&acceptfd, &errmsg) != SQLITE_OK) {
		printf("select record failed.\n");
		sprintf(msg->data, "select record failed.");
		send(acceptfd, msg, sizeof(MSG), 0);
	} else {
		printf("select record success.\n");
	}
}

四. 项目总结与反思

1. 项目知识点总结

(1)网络编程

               a.socket创建通信

               b.epoll IO多路复用

               c.设置套接字属性

(2)文件IO

               a. fgets

               b. 文件IO , 标准IO

(3)sqlite3数据库

               a. sql语句

               b.sqlite3函数接口

2. 项目中对缓冲区的运用总结 

(1)
如上图, 定义一些指针变量, 往这个指针变量里面拷贝数据, 运行之后段错误。
为什么呢?  原因是这样, 我们常常可以看到这样的代码
char *p = "hello";

这里需要了解双引号的作用:它实际上干了这么三件事情:

(1)在常量区给字符串申请空间

(2)在字符串末尾加‘\0’

(3)返回字符串的首地址

所以指针p只是保存了字符串的首地址当然没问题, 空间也是申请好的, 可要是像图(1)那样给一个指针拷贝一串数据就对内存非法访问了, 没有空间对吧。

3. 客户端与服务端的通信结构体二者要一致, 发多少接收多少

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

@daiwei

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

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

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

打赏作者

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

抵扣说明:

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

余额充值