闲话少说
此文章为在线英语词典实现,第一次基本独立做项目,大部门功能没题目,不敷的是代码冗余有点多,还可能有些小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);
- }
复制代码 制作者名单
文案:張嘉鑫 视图:張嘉鑫 代码:張嘉鑫 其余图片:来自网络 特殊鸣谢:李伟老师
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |