初探Linux网络编程:在线英语词典的实现


在这里插入图片描述

闲话少说

此文章为在线英语词典实现,第一次基本独立做项目,大部分功能没问题,不足的是代码冗余有点多,还可能有些小BUG。

仅做记录并且分享自己学习Linux网络编程的过程,学艺不精,code新人,如有错误欢迎在评论区讨论、指正!,若文章内容对你有帮助,希望可以给我点个赞!


1. 思路构筑(想框架,理流程)

做完本次项目,进一步体会到思路构筑的重要性;若你向我一样经验不足,最好不要脑子一热就狂写一堆,到了项目做到一半发现要大改……说多都是泪。这部分多花时间一点也无妨。

先来看一下做该项目的大致思路:写好大致框架后,一个模块一个模块的添,模块边写边测试。“让服务器做所有事情” 这种想法是大忌。 有些事情让客户端去做会大大降低你项目编码的难度。二者如何分工,请看下文。

1.1 大致思路

1.1.1 功能模块

  • 用户界面:告诉用户可以做些什么事情
  • 账号操作:登录或注册
  • 查询操作:查询单词或者成功查询的历史

1.1.2 服务器

  • 一个数据库,数据库内三张表user, word, history ,分别存放用户,单词,和成功查询单词历史信息
  • 接收请求
    • 1.注册:获得用户 id pwd 并添加到 user 表中
    • 2.登录:获得用户 id pwd 到 user 表中比对 id 和 pwd
    • 3.查单词:获得用户输入单词,到 word 表中查找
    • 4.查历史:根据用户 id,到 history 中查找记录
  • 返回相应结果

1.1.3 客户端

  • 用户界面:提示用户当前可以做什么事情
  • 获得用户请求,并发送给服务器
  • 接收回复结果,做相应处理

1.1.4 交互流程

该项目简单来说就是:用户只有在登录成功之后才可以进入查询环节;进入查询环节之后可以一直查询,直到用户在“查单词“环节输入 ”Q“ 之后才会回退到账号界面。

当然这并不灵活,是后续可以改进的点。

在这里插入图片描述

服务器启动的条件下,用户运行客户端,输入数字(1, 2, 3, 4)即可做对应的事情。

数字对应功能如下图所示

账号界面
在这里插入图片描述

查询界面
在这里插入图片描述


2. 代码

2.1 部分效果展示

登录成功
在这里插入图片描述
单词查询结果
在这里插入图片描述
历史查询结果,打印有点小问题
在这里插入图片描述

2.2 项目概况 readMe.txt

服务器端

  • 主函数 server.c
  • 功能函数 tool.c
  • 编译命令: gcc server.c tool.c -lsqlite3 -o server
  • 运行命令: ./server IP port
  • 初次运行命令: ./server IP port num --> num 任意数字即可
  • 词典数据文件:dict.txt

客户端

  • 主函数 client.c
  • 功能函数 tool.c
  • 编译命令: gcc clien.c tool.c -lsqlite3 -o client
  • 运行命令: ./client IP port

2.3.头文件 tool.h

包含了客户端和服务器需要用到的所有功能模块函数接口(并不是很好,最好客户端和服务器模块功能函数分开放)

#ifndef _TOOL_H_
#define _TOOL_H_

#include <stdio.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#include <signal.h>
#include <wait.h>

#include <time.h>
#include <sqlite3.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define R 1 // register
#define L 2	// login
#define C 3 // check
#define H 4	// history
#define Q 5 // quit
#define GET_ID_ERR 6	//获取id 失败
#define GET_PWD_ERR 7	//获取pwd 失败
#define ID_ERR 8	// 用户id 错误
#define PWD_ERR 9	// 用户pwd 错误


//用户界面
void UI_account(void);
//查询界面
void UI_check(void);
// 初始化数据库,只是准备,后续使用仍需要手动打开
int database_init(char *dbname);
// 注册
int user_add(int connfd, sqlite3 *db);
// 登陆
int user_login(int connfd, sqlite3 *db);
// 查单词
int check(int connfd, sqlite3 *db, int id);
// 成功历史查询
int history(int connfd, sqlite3 *db, int id);
// 定义用户帐号函数,登陆成功返回用户id
int account(int connfd, sqlite3 *db,int mode);
// 行为函数
int operation(int connfd, sqlite3 *db, int mode, int id);
// 服务器初始化
int server_init(char *ip, char* port);

#endif

2.4 功能函数 tool.c

#include "tool.h"

//帐号界面
void UI_account(void){
	char buf[512] = {0};
	strcat(buf,"******************************************\n");
	strcat(buf,"*                                        *\n");
	strcat(buf,"*     **     输入数字即可开始    **      *\n");
	strcat(buf,"*                                        *\n");
	strcat(buf,"*            1:注册   2:登陆             *\n");
	strcat(buf,"*                                        *\n");
	strcat(buf,"******************************************\n");
	printf("%s", buf);
}

// 查询界面
void UI_check(void){
	char buf[512] = {0};
	strcat(buf,"******************************************\n");
	strcat(buf,"*                                        *\n");
	strcat(buf,"*     **    输入数字即可开始    **       *\n");
	strcat(buf,"*                                        *\n");
	strcat(buf,"*        3:查单词      4:历史查询        *\n");
	strcat(buf,"*                                        *\n");
	strcat(buf,"******************************************\n");
	printf("%s", buf);
}

// 初始化数据库,只是准备,后续使用仍需要手动打开
int database_init(char *dbname)
{
	puts("---             服务器初始化中,请稍候...           ---");
	char sql[256] = {0};
	sqlite3 *db;
	if(-1 == sqlite3_open(dbname, &db)){
		printf("%s open error: %s\n", dbname, sqlite3_errmsg(db));
		return -1;
	}

	/*------------------------------------------------------------------------*/
	// 初始化 user(id int primary key, password int)
	strcpy(sql, "create table if not exists user(id int primary key,\
		password int)");
	if(0 != sqlite3_exec(db, sql, NULL, NULL, NULL)){
		printf("user create error: %s\n", sqlite3_errmsg(db));
		return -1L;
	}

	/*------------------------------------------------------------------------*/
	// 创建 word 数据库
	strcpy(sql, "create table if not exists word(word str(15),\
		meaning text)");
	if(0 != sqlite3_exec(db, sql, NULL, NULL, NULL)){
		printf("word create error: %s\n", sqlite3_errmsg(db));
		return -1;
	}
	//词典文件上传到数据库
	FILE *fp = fopen("dict.txt", "r+");
	if(NULL == fp){
		perror("dict fopen");
		return -1;
	}
	char buf[64] = {0};//词条
	char word[32] = {0}; //单词
	while(fgets(buf, sizeof(buf), fp)){
		//把单词从词条中提取出来
		char *cut = strstr(buf, " ");
		strncpy(word, buf, cut-buf);
		//单词和词条添加到数据库word中
		char sql[128] = {0};
		sprintf(sql, "insert into word values('%s', '%s')",word, buf);
		if(0 != sqlite3_exec(db, sql, NULL, NULL, NULL)){
			printf("word insert: %s\n", sqlite3_errmsg(db));
			return -1;
		}
		memset(buf, 0, sizeof(buf));
		memset(word, 0, sizeof(word));
	}

	/*------------------------------------------------------------------------*/
	// 初始化 history(id int primary key, word str(40), time text)
	strcpy(sql, "create table if not exists history(id int,\
		word str(40), time text)");
	if(0 != sqlite3_exec(db, sql, NULL, NULL, NULL)){
		printf("history create error: %s\n", sqlite3_errmsg(db));
		return -1;
	}
	sqlite3_close(db);
	return 0;
}

/*------------------------------------------------------------------------*/
// 注册
int user_add(int connfd, sqlite3 *db){
	// id是主键,sqlite3会判断 id 唯一性,所以这里不用
	int id, pwd;
	char sql[128] = {0};
	char buf[128] = {0};
	// 接受数据,拼接sql语句
	recv(connfd, (void*)&id, sizeof(id), 0);
	recv(connfd, (void*)&pwd, sizeof(pwd), 0);
	
	if(9 >= id)
		return ID_ERR;// id 不合法

	sprintf(sql, "insert into user values(%d,%d);", id, pwd);
	if(0 != sqlite3_exec(db, sql, NULL, NULL, NULL)){
		printf("add user error: %s\n", sqlite3_errmsg(db));
		return ID_ERR;
	}
	// 注册成功
	printf("用户id:%d  pwd:%d 注册成功!\n",id, pwd);
	return 0;
}

/*------------------------------------------------------------------------*/
// 登陆, 登陆成功返回用户id
int user_login(int connfd, sqlite3 *db){
	int id, pwd;
	char **result;
	int row, col;
	char buf[128] = {0};
	char sql[128] = {0};

	// 获取登陆用户输入的id pwd
	int ret = recv(connfd, &id, sizeof(id), 0);
	if(-1 == ret){
		perror("获取登陆用户id 失败");
		return GET_ID_ERR;
	}else if(0 == ret){
		printf("客户端退出\n");
	}
	ret = recv(connfd, &pwd, sizeof(pwd), 0);
	if(-1 == ret){
		perror("获取登陆用户pwd 失败");
		return GET_PWD_ERR;
	}else if(0 == ret){
		printf("客户端退出\n");
	}
	//判断登陆id合法性
	if(9 >= id){
		return ID_ERR;//不合法
	}

	printf("已登陆用户-----id: %d, pwd: %d\n", id, pwd);

	// 查看 user 表中是否有该用户	
	sprintf(sql, "select * from user where id = %d", id);
	if(0 != sqlite3_get_table(db, sql, &result, &row, &col, NULL)){
		printf("login user error: %s\n", sqlite3_errmsg(db));
		return ID_ERR;//用户不存在
	}

	for(int i=1; i<=row; i++){
		for(int j=0; j<col;j++){
			//判断用户id
			if(id == atoi(result[i * col + j])){
				//判断用户密码
				if(pwd == atoi(result[i*col+j+1])){
					return id;
				}else{
					return PWD_ERR;
				}
			}
		}
	}
	return -1;
}

/*------------------------------------------------------------------------*/
// 可查一次查单词, 且上传成功查询历史
// 返回值:正常查询发送信息给客户端,返回 0 ;出错 -1
int check(int connfd, sqlite3 *db, int id){
	char **result;
	int row, col;
	char sql[128] = {0};
	char buf[128] = {0};
	// 提取 word 表的数据
	strcpy(sql, "select * from word");
	if(0 != sqlite3_get_table(db, sql, &result, &row, &col, NULL)){
		return -1;
	}
	//获得用户单词信息
	char input[32] = {0};
	recv(connfd, input, sizeof(input), 0);
	int input_len = strlen(input);
	printf("客户端获得单词:%s\n", input);
	// 得到Q退出
	if(0 == strncmp(input, "Q", 1)){
		return Q; //
	}
	//判断word
	int index = col;//查询所用下标,因为 col 等于字段数
	int flag = 0;//标记单词查询状态 0:未找到  1:找到
	int max = row*col-2; //单词以及解释的总个数, 2 是字段数 word, meaning
	for(int i=0; i<=row; i++){
		if(1 == flag)//找到直接退出循环
			break;
		for(int j=0; j<col;j++){
			//接收数据库数据(否则无法获取单词真正长度)
			char word[20] = {0};
			strcpy(word, result[index]); 
			int word_len = strlen(word); 
			//判断单词内容与单词长度
			if(input_len == word_len && 0 == strncmp(input, result[index], strlen(input))){
				// 成功查到发送解释 result[index+1]
				sprintf(buf, "%s", result[index+1]);
				send(connfd, buf, strlen(buf), 0);
				memset(buf, 0, sizeof(buf));

				/* -------------------上传历史记录--------------*/
				// 获取查询时间
				time_t tm;
				time(&tm);
				struct tm *tp = localtime(&tm);
				char timmsg[32] = {0};
				sprintf(timmsg,"%d/%d/%d %d:%d:%d\n", tp->tm_year+1900,
						tp->tm_mon+1, tp->tm_mday, tp->tm_hour,
						tp->tm_min, tp->tm_sec);

				char sql[128] = {0};
				sprintf(sql,"insert into history values(%d,'%s','%s')",id,word,timmsg);
				if(-1 == sqlite3_exec(db, sql, NULL, NULL, NULL)){
					return -1;
				}		
				flag = 1;
				break;
			}
			index+=2;
			index %= max;//防止段错误
		}
	}
	//循环退出时 flag 仍为 0 则未找到
	if(0 == flag){
		strcpy(buf, "单词未找到\n");
		send(connfd, buf, strlen(buf), 0);
		return 0;
	}
	return 0; 
}

/*------------------------------------------------------------------------*/
// 成功查询的历史
int history(int connfd, sqlite3 *db, int id){
	char sql[128] = {0};
	char buf[128] = {0};
	char **result;
	int row, col;
	sprintf(sql, "select * from history where id=%d", id);
	if(-1 == sqlite3_get_table(db, sql, &result, &row, &col, NULL)){
		sprintf(buf,"select history: %s\n", sqlite3_errmsg(db));
		send(connfd, buf, strlen(buf), 0);
		memset(buf, 0, sizeof(buf));
		return -1;
	}

	int max = col * row + 3;// 3 是字段数
	if(3 == max){// max 为3 该用户无成功查询记录
		strcpy(buf, "NONE!");
		send(connfd, buf, strlen(buf), 0);
		return -1;
	}else{
		strcpy(buf,"\n---------------成功查询历史----------------\n");
		send(connfd, buf, strlen(buf), 0);
		memset(buf, 0, sizeof(buf));
		for(int i=0; i<=row; i++){
			for(int j=0; j<col; j++){	// j 从3 开始,不传字段名
				sprintf(buf, "%s ", result[i*col+j]);
				send(connfd, buf, strlen(buf), 0);
				usleep(1000);
				memset(buf, 0, sizeof(buf));
			}
		}
		// 历史数据发送完成,发送结束信息
		strcpy(buf, "DONE!");
		send(connfd, buf, strlen(buf), 0);
	}
	return 0;
}

/*------------------------------------------------------------------------*/
// 帐号操作,函数注册成功返回 0,登陆成功返回id
int account(int connfd, sqlite3 *db, int mode){
	int id;
	char buf[128] = {0};
	//注册
	if(R == mode){
		int ret = user_add(connfd, db);
		if(0 == ret){
			printf("新用户注册成功!\n");
		}else if(ID_ERR){
			return ID_ERR;
		}
		return 0;//注册成功
	}else if(L ==mode){
		// 登陆
		int ret = user_login(connfd, db);
		if(ID_ERR == ret){
			return ID_ERR;//id不合法
		}else if(PWD_ERR == ret){
			return PWD_ERR;//密码错误
		}else if(GET_ID_ERR == ret){
			return GET_ID_ERR;
		}else if(GET_PWD_ERR == ret){
			return GET_PWD_ERR;
		}
		return ret;
	}else{
		return -1;
	}
	return 0;
}

int operation(int connfd, sqlite3 *db, int mode, int id){
	if(C == mode){
		// 查询
		int ret = check(connfd, db, id);
		if(-1 == ret){
			return -1;
		}else if(Q == ret){
			return Q;
		}
		return 0;
	}else if(H == mode){
		// 历史
		if(-1 == history(connfd, db, id))
			return -1;
		return 0;
	}
}

/*------------------------------------------------------------------------*/
//服务器初始化
int server_init(char *ip, char* port)
{
	// 创建套接字
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(-1 == sockfd){
		perror("socket");
		return -1;
	}
	// 绑定
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = inet_addr(ip);
	addr.sin_port = htons(atoi(port));
	if(-1 == bind(sockfd, (struct sockaddr *)&addr, sizeof(addr))){
		perror("connect");
		return -1;
	}
	// 监听
	if(-1 == listen(sockfd, 10)){
		perror("listen");
		return -1;
	}
	printf("----------------服务启动!----------------\n");
	printf("---          等待客户端连接           ---\n");
	return sockfd;
}

2.5 客户端 client.c

// -------------------------Client---------------------------
#include "tool.h"

int main(int argc, char *argv[]){

	// 判断运行指令合法性
	if(3 != argc){
		printf("Input like: %s IP port\n", argv[0]);
		return -1;
	}
	// 创建套接字
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(-1 == sockfd){
		perror("socket");
		return -1;
	}
	// 连接服务器
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = inet_addr(argv[1]);
	addr.sin_port = htons(atoi(argv[2]));
	if(-1 == connect(sockfd, (struct sockaddr *)&addr, sizeof(addr))){
		perror("connect");
		return -1;
	}

	while(1){
		/*---------------------帐号操作----------------------*/
		long mode = 0;	//用户模式数
		int ret = 0;	//接收各种函数返回结果
		char ui[512] = {0};//接收UI
		char buf[128] = {0};//存储信息

		// 调用帐号UI
		UI_account();

		// 发送mode
		printf("输入您的模式选择数:\n");
		scanf("%ld", &mode);
		getchar();
		send(sockfd,(void*)&mode, sizeof(mode), 0);

		if(R == mode){
			/*-----------------------注册---------------------*/
			int id, pwd;
			printf("输入您要注册的用户id(需至少为两位数)\n");
			scanf("%d", &id);
			send(sockfd, (void*)&id, sizeof(id), 0);

			printf("输入您的密码\n");
			scanf("%d", &pwd);
			send(sockfd, (void*)&pwd, sizeof(pwd), 0);
			printf("您的 id:%d pwd: %d\n", id, pwd);

			recv(sockfd, (void*)&id, sizeof(id), 0); //接收回复
			if(0 == id){
				printf("注册成功\n");
				continue;
			}else if(-1 == id){
				printf("注册失败\n");
				continue;
			}else if(ID_ERR == id){
				printf("用户已存在\n");
				continue;
			}
		}else if(L == mode){
			/*-----------------------登陆---------------------*/
			int id, pwd;
			printf("输入登陆的用户id(需至少为两位数)\n");
			scanf("%d", &id);
			send(sockfd, (void*)&id, sizeof(id), 0);

			printf("输入密码\n");
			scanf("%d", &pwd);
			send(sockfd, (void*)&pwd, sizeof(pwd), 0);

			recv(sockfd, &id, sizeof(id), 0); //接收回复
			if(-1 == id){
				printf("登陆失败\n");
				continue;
			}else if(0 == id){
				printf("您不能以 0 为用户名\n");
				continue;
			}else if(GET_ID_ERR == id){
				printf("获取id失败\n");
				continue;
			}else if(GET_PWD_ERR == id){
				printf("获取pwd失败\n");
				continue;
			}else if(ID_ERR == id){
				printf("用户id不合法\n");
				continue;	
			}else if(PWD_ERR == id){
				printf("密码错误\n");
				continue;	
			}else if(9 < id){
				/*-------------------登陆成功, 查询开始--------------------*/
				while(1){
					// 调用查询UI
					UI_check();

					// 发送mode
					printf("输入查询模式选择数:\n");
					scanf("%ld", &mode);
					getchar();
					send(sockfd, (void*)&mode, sizeof(mode), 0);
					if(C == mode){
						/*----------------------查询-----------------------*/	
						//发送单词信息,输入的是Q则退出,当然Q也要发给服务器
						printf("输入您要查询的单词,输入 Q 退出\n");
						fgets(buf, sizeof(buf), stdin);
						send(sockfd, buf, strlen(buf)-1, 0);
						if(0 == strncmp(buf, "Q", 1)){
							break;
						}
						memset(buf, 0, sizeof(buf));
						//接受结果	
						char result[64] = {0};
						recv(sockfd, result, sizeof(result), 0);
						printf("%s\n", result);
						continue;
					}else if(H == mode){
						while(1){
							char buf[128] = {0};
							int ret = recv(sockfd, buf, sizeof(buf), 0);
							if(-1 == ret){
								perror("history recv");
								break;
							}else if(0 == ret){
								printf("客户端退出\n");
								break;
							}
							// 查询成功 
							printf("%s", buf);
							// 历史数据读取完成
							if(0 == strncmp(buf, "DONE!", 5)){
								printf("\n");
								break;
							}else if(0 == strncmp(buf, "NONE!", 5)){
								//暂无历史数据
								printf("\n");
								break;
							}
						}
						continue; //返回查询界面
					}else{
						puts("请输入正确的选项数");
						continue;
					}
				}
			}
		}
	}
}

2.6 服务器 server.c

//		-------------------------Server----------------------------
#include "tool.h"

/* ----------------------------main------------------------------  */
int main(int argc, char *argv[])
{
	if(3 > argc){
		printf("参数缺失,您至少需要输入三个参数如:\n./server IP port\n");
		puts("");
		printf("第一次运行服务器,需额外输入任意数字:\n./server IP port num\n");
		return -1;
	}

	// 若输入了模式(第一次运行服务器,没有创建数据库)
	if(4 == argc)
		database_init("data.db");

	sqlite3* db;
	if(0 != sqlite3_open("data.db", &db)){
		printf("data base open: %s\n", sqlite3_errmsg(db));
		return -1;
	}
	// 初始化服务器,得到监听套接字
	int listenfd = server_init(argv[1], argv[2]);	
	if(-1 == listenfd)
		return -1;

	/*---------               子进程处理用户交互              ----------*/

	/*-----------------一个连接一个进程--------------------*/
	while(1){
		long mode = 0;// 接收用户模式
		int ret = 0;//接受一些函数的运行结果
		struct sockaddr_in client;
		socklen_t len = sizeof(client);
		int connfd = accept(listenfd, (struct sockaddr*)&client, &len);
		if(-1 == connfd){
			perror("connect");
			return -1;
		}
		puts("客户端连接成功!");

		pid_t pid = fork();
		if(-1 == pid){
			perror("fork");
			return -1;
		}else if(0 == pid){
			while(1){
				/* --------------------- 帐号操作 ----------------------*/
				// 用户登陆操作信息
				ret = recv(connfd, (void*)&mode, sizeof(mode), 0);
				if(-1 == ret){
					perror("recv mode");
					break;
				}else if(0 == ret){
					printf("客户端退出\n");
					break;
				}
			/* ------------------帐号操作 --------------------*/
				// 帐号函数,登陆成功返回登陆id
				int id = account(connfd, db, mode);
				send(connfd, (void*)&id, sizeof(id), 0);//发送account的结果
				if(9 < id){
					/* ------------------ 登陆成功开始查询 --------------------*/
					while(1){
						// 接收mode
						if(-1 == recv(connfd, (void*)&mode, sizeof(mode),0)){
							perror("recv mode:");
							exit(-1);
						}else if(0 == ret){
							printf("客户端退出\n");
							exit(-1);
						}
						int ret = operation(connfd, db, mode, id);
						if(-1 == ret){
							break;
						}else if(0 == ret){
							continue;
						}else if(Q == ret){
							break;
						}
					}
				}
			}
		}
		close(connfd);
	}
	/* -------------------------- 父进程 ------------------------------*/
	wait(NULL);
	close(listenfd);
	sqlite3_close(db);
	exit(0);
}

制作者名单

在这里插入图片描述
文案:張嘉鑫
视图:張嘉鑫
代码:張嘉鑫
其余图片:来自网络
特别鸣谢:李伟老师

  • 14
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值