应用开发!
这款应用程序是在Linux操作系统下完成的,整个项目包含了众多的知识点(文件IO、进程、网络、数据库)。动手操作之前可以先大致设计出流程图,然后根据流程图进行各个模块的实现(注册模块、登录模块、查询模块、查看历史记录等),在每个模块中可以通过一些输出语句来进行bug检测,这在后面的调试过程中可以得到很大的帮助。
一、流程图
客户端:
服务器端:
二、完整代码
客户端:
#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>#define N 32#define R 1 // user - register
#define L 2 // user - login
#define Q 3 // user - query
#define H 4 // user - history// 定义通信双方的信息结构体
typedef struct {int type;char name[N];char data[256];
}MSG;int do_register(int sockfd, MSG *msg)
{msg->type = R;printf("Input name:");scanf("%s", msg->name);getchar();printf("Input passwd:");scanf("%s", msg->data);if(send(sockfd, msg, sizeof(MSG),0) < 0){printf("fail to send.\n");return -1;}if(recv(sockfd, msg, sizeof(MSG), 0) < 0){printf("Fail to recv.\n");return -1;}// ok ! or usr alread exist.printf("%s\n", msg->data);return 0;
}int do_login(int sockfd, MSG *msg)
{msg->type = L;printf("Input name:");scanf("%s", msg->name);getchar();printf("Input passwd:");scanf("%s", msg->data);if(send(sockfd, msg, sizeof(MSG),0) < 0){printf("fail to send.\n");return -1;}if(recv(sockfd, msg, sizeof(MSG), 0) < 0){printf("Fail to recv.\n");return -1;}if(strncmp(msg->data, "OK", 3) == 0){printf("Login ok!\n");return 1;}else {printf("%s\n", msg->data);}return 0;
}int do_query(int sockfd, MSG *msg)
{msg->type = Q;puts("--------------");while(1){printf("Input word:");scanf("%s", msg->data);getchar();//客户端,输入#号,返回到上一级菜单if(strncmp(msg->data, "#", 1) == 0)break;//将要查询的单词发送给服务器if(send(sockfd,msg, sizeof(MSG), 0) < 0){printf("Fail to send.\n");return -1;}// 等待接受服务器,传递回来的单词的注释信息if(recv(sockfd, msg,sizeof(MSG), 0) < 0){printf("Fail to recv.\n");return -1;}printf("%s\n", msg->data);}return 0;
}int do_history(int sockfd, MSG *msg)
{msg->type = H;send(sockfd, msg, sizeof(MSG), 0);// 接受服务器,传递回来的历史记录信息while(1){recv(sockfd, msg, sizeof(MSG), 0);if(msg->data[0] == '\0')break;//输出历史记录信息printf("%s\n", msg->data);}return 0;
}// ./server 192.168.3.196 10000
int main(int argc, const char *argv[])
{int sockfd;struct sockaddr_in serveraddr;int n;MSG msg;if(argc != 3){printf("Usage:%s serverip port.\n", argv[0]);return -1;}if((sockfd = socket(AF_INET, SOCK_STREAM,0)) < 0){perror("fail to socket.\n");return -1;}bzero(&serveraddr, sizeof(serveraddr));serveraddr.sin_family = AF_INET;serveraddr.sin_addr.s_addr = inet_addr(argv[1]);serveraddr.sin_port = htons(atoi(argv[2]));if(connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0){perror("fail to connect");return -1;}while(1){printf("*****************************************************************\n");printf("* 1.register 2.login 3.quit *\n");printf("*****************************************************************\n");printf("Please choose:");scanf("%d", &n);getchar();switch(n){case 1:do_register(sockfd, &msg);break;case 2:if(do_login(sockfd, &msg) == 1){goto next;}break;case 3:close(sockfd);exit(0);break;default:printf("Invalid data cmd.\n");}}next:while(1){printf("*****************************************************\n");printf("* 1.query_word 2.history_record 3.quit *\n");printf("*****************************************************\n");printf("Please choose:");scanf("%d", &n);getchar();switch(n){case 1:do_query(sockfd, &msg);break;case 2:do_history(sockfd, &msg);break;case 3:close(sockfd);exit(0);break;default :printf("Invalid data cmd.\n");}}return 0;
}
服务器端:
#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 N 32#define R 1 // user - register
#define L 2 // user - login
#define Q 3 // user - query
#define H 4 // user - history#define DATABASE "my.db"// 定义通信双方的信息结构体
typedef struct {int type;char name[N];char data[256];
}MSG;int do_client(int acceptfd, sqlite3 *db);
void do_register(int acceptfd, MSG *msg, sqlite3 *db);
int do_login(int acceptfd, MSG *msg, sqlite3 *db);
int do_query(int acceptfd, MSG *msg, sqlite3 *db);
int do_history(int acceptfd, MSG *msg, sqlite3 *db);
int history_callback(void* arg,int f_num,char** f_value,char** f_name);
int do_searchword(int acceptfd, MSG *msg, char word[]);
int get_date(char *date);// ./server 192.168.3.196 10000
int main(int argc, const char *argv[])
{int sockfd;struct sockaddr_in serveraddr;int n;MSG msg;sqlite3 *db;int acceptfd;pid_t pid;if(argc != 3){printf("Usage:%s serverip port.\n", argv[0]);return -1;}//打开数据库if(sqlite3_open(DATABASE, &db) != SQLITE_OK){printf("%s\n", sqlite3_errmsg(db));return -1;}else{printf("open DATABASE success.\n");}if((sockfd = socket(AF_INET, SOCK_STREAM,0)) < 0){perror("fail to socket.\n");return -1;}bzero(&serveraddr, sizeof(serveraddr));serveraddr.sin_family = AF_INET;serveraddr.sin_addr.s_addr = inet_addr(argv[1]);serveraddr.sin_port = htons(atoi(argv[2]));if(bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0){perror("fail to bind.\n");return -1;}// 将套接字设为监听模式if(listen(sockfd, 5) < 0){printf("fail to listen.\n");return -1;}//处理僵尸进程signal(SIGCHLD, SIG_IGN);while(1){if((acceptfd = accept(sockfd, NULL, NULL)) < 0){perror("fail to accept");return -1;}if((pid = fork()) < 0){perror("fail to fork");return -1;}else if(pid == 0) // 儿子进程{//处理客户端具体的消息close(sockfd);do_client(acceptfd, db);}else // 父亲进程,用来接受客户端的请求的{close(acceptfd);}}return 0;
}int do_client(int acceptfd, sqlite3 *db)
{MSG msg;while(recv(acceptfd, &msg, sizeof(msg), 0) > 0){printf("type:%d\n", msg.type);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 data msg.\n");}}printf("client exit.\n");close(acceptfd);exit(0);return 0;
}void do_register(int acceptfd, MSG *msg, sqlite3 *db)
{char * errmsg;char sql[128];sprintf(sql, "insert into usr values('%s', %s);", msg->name, msg->data);printf("%s\n", sql);if(sqlite3_exec(db,sql, NULL, NULL, &errmsg) != SQLITE_OK){printf("%s\n", errmsg);strcpy(msg->data, "usr name already exist.");}else{printf("client register ok!\n");strcpy(msg->data, "OK!");}if(send(acceptfd, msg, sizeof(MSG), 0) < 0){perror("fail to send");return ;}return ;
}int do_login(int acceptfd, MSG *msg , sqlite3 *db)
{char sql[128] = {};char *errmsg;int nrow;int ncloumn;char **resultp;sprintf(sql, "select * from usr where name = '%s' and pass = '%s';", msg->name, msg->data);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(msg->data, "OK");send(acceptfd, msg, sizeof(MSG), 0);return 1;}if(nrow == 0) // 密码或者用户名错误{strcpy(msg->data,"usr/passwd wrong.");send(acceptfd, msg, sizeof(MSG), 0);}return 0;
}int do_searchword(int acceptfd, MSG *msg, char word[])
{FILE * fp;int len = 0;char temp[512] = {};int result;char *p;//打开文件,读取文件,进行比对if((fp = fopen("dict.txt", "r")) == NULL){perror("fail to fopen.\n");strcpy(msg->data, "Failed to open dict.txt");send(acceptfd, msg, sizeof(MSG), 0);return -1;}//打印出,客户端要查询的单词len = strlen(word);printf("%s , len = %d\n", word, len);//读文件,来查询单词while(fgets(temp, 512, fp) != NULL){// printf("temp:%s\n", temp);// abandon abresult = strncmp(temp,word,len);if(result < 0){continue;}if(result > 0 || ((result == 0) && (temp[len]!=' '))){break;}// 表示找到了,查询的单词p = temp + len; // abandon v.akdsf dafsjkj // printf("found word:%s\n", p);while(*p == ' '){p++;}// 找到了注释,跳跃过所有的空格strcpy(msg->data, p);printf("found word:%s\n", msg->data);// 注释拷贝完毕之后,应该关闭文件fclose(fp);return 1;}fclose(fp);return 0;
}int get_date(char *date)
{time_t t;struct tm *tp;time(&t);//进行时间格式转换tp = localtime(&t);sprintf(date, "%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);printf("get date:%s\n", date);return 0;
}int do_query(int acceptfd, MSG *msg , sqlite3 *db)
{char word[64];int found = 0;char date[128] = {};char sql[128] = {};char *errmsg;//拿出msg结构体中,要查询的单词strcpy(word, msg->data);found = do_searchword(acceptfd, msg, word);printf("查询一个单词完毕.\n");// 表示找到了单词,那么此时应该将 用户名,时间,单词,插入到历史记录表中去。if(found == 1){// 需要获取系统时间get_date(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("%s\n", errmsg);return -1;}else{printf("Insert record done.\n");}}else //表示没有找到{strcpy(msg->data, "Not found!");}// 将查询的结果,发送给客户端send(acceptfd, msg, sizeof(MSG), 0);return 0;
}// 得到查询结果,并且需要将历史记录发送给客户端
int history_callback(void* arg,int f_num,char** f_value,char** f_name)
{// record , name , date , word 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;
}int 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, history_callback,(void *)&acceptfd, &errmsg)!= SQLITE_OK){printf("%s\n", errmsg);}else{printf("Query record done.\n");}// 所有的记录查询发送完毕之后,给客户端发出一个结束信息msg->data[0] = '\0';send(acceptfd, msg, sizeof(MSG), 0);return 0;
}
三、总结
在整个开发过程中收获还是挺多的,首先,熟悉了整个应用程序开发的大致流程,在开发之前可以先思考程序由哪几个模块实现,再分析一下每个模块使用什么方式来实现,并且在实现代码的同时做一些提示性语句,方便代码完成之后的调式。其次,对文件IO操作、进程的操作、网络socket编程、数据库的操作有了更深层次的理解和应用。