描述:
通过C/S架构实现在线词典,用户在客户端可以注册,登陆,然后可以查询单词,并且保存自己的单词查询记录。
知识点:
创建一个dict_client文件夹,存放客户端代码
#ifndef CLIENT_H
#define CLIENT_H
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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]; //password或word信息
}MSG;
int do_register(int sockfd, MSG *msg);
int do_login(int sockfd, MSG *msg);
int do_query(int sockfd, MSG *msg);
int do_history(int sockfd, MSG *msg);
int client_connect(char *ip, int port);
#endif
#include "client.h"
int do_register(int sockfd, MSG *msg){
msg->type = R;
printf("Input name:");
scanf("%s", msg->name);
getchar();
printf("Input password:");
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 user 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 password:");
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("OK\n");
return 1;
}
else{
printf("%s\n", msg->data);
}
return 0;
}
int do_query(int sockfd, MSG *msg){
puts("查询--------------");
char name[10]={0};
strcpy(name,msg->name);
while(1){
msg->type = Q;
strcpy(msg->name,name);
printf("Input word[quit:#]:");
scanf("%s", msg->data);
//printf("%s %s\n",msg->name,msg->data);
//客户端输入# 返回上一级菜单
if(strncmp(msg->data,"#",1)==0)
break;
//将要查询的单词发送给服务器
if(send(sockfd, msg, sizeof(MSG), 0)<0)
{
printf("Fail to send.\n");
return -2;
}
//等待接收服务器传递回来的单词注释信息
if(recv(sockfd, msg, sizeof(MSG), 0) < 0)
{
printf("Fail to recv.\n");
return -2;
}
printf("%s\n", msg->data);
}
return 0;
}
int do_history(int sockfd, MSG *msg){
msg->type = H;
send(sockfd,msg,sizeof(MSG),0);
printf("%s的查找记录--------\n", msg->name);
//接收服务器传递回来的历史记录信息
while(1)
{
recv(sockfd,msg,sizeof(MSG),0);
if('\0'==msg->data[0])
break;
//打印历史记录信息
printf("%s\n",msg->data);
}
return 0;
}
int client_connect(char *ip, int port){
int sockfd;
struct sockaddr_in serveraddr;
//创建流式套接字
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(ip);
serveraddr.sin_port = htons(port);
//连接服务器
if(connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0){
perror("fail to connect");
return -1;
}
return sockfd;
}
#include "client.h"
int main(int argc, char *argv[])
{
int n,m;
char dart1[32],dart2[32];
MSG msg;
if(argc != 3){
printf("Usage:%s serverip port\n", argv[0]);
return -1;
}
int sockfd = client_connect(argv[1], atoi(argv[2]));
//一级菜单
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");
n = 0;
break; //若没有break,输入aaa会执行3次switch
}
}
//二级菜单
next:
while(1){
printf("###########################################\n");
printf("*1.query 2.history 3.quit*\n");
printf("###########################################\n");
printf("Please choose:");
getchar();
scanf("%d", &m);
switch(m){
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");
break;//若没有break,输入aaa会执行3次switch
}
}
return 0;
}
创建Makefile 文件,也可以不做这步,只是多敲几串命令而已,Makefile最大优点就是直接输入make命令就能编译文件,也挺方便。
cli: main.c cli_dict.c
gcc *.c -o cli
需要在linux中安装sqlite3,没有安装的输入这两条命令
sudo apt-get install sqlite3
sudo apt-get install libsqlite3-dev
然后创建数据库 sqlite3 dictionaryOL.db;
在sqlite3 shell中创建表
# 改变user表结构
CREATE TABLE user(id INTEGER PRIMARY KEY,name char unique,pwd char);
# 创建dict 存放字典内容(单词,含义)
CREATE TABLE dict(dict_id int primary key,word char,mean char);
# 统计字典里有多个意思的单词
select word from dict group by word having count(*)>=2;
# 创建record 记录用户所查询过的单词(姓名,时间,单词)
CREATE TABLE record(name char,date char,word char);
dict表需要自己插入单词和含义。
#ifndef SERVER_H
#define SERVER_H
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//时间函数
#include
#include
#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 "dictionaryOL.db"
//定义通信双方的信息结构体
typedef struct{
int type;
char name[N];
char data[256]; //password或word信息
}MSG;
int do_client(int acceptfd,sqlite3 *db);
int 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 search_callback(void *para,int f_num,char **f_value,char **f_name);
int history_callback(void *para,int f_num,char **f_value,char **f_name);
void get_date(char *date);
int do_client(int acceptfd,sqlite3 *db);
int server_init(char *ip, int port);
#endif
#include "server.h"
int do_register(int acceptfd, MSG *msg,sqlite3 *db){
char *errmsg;
char sql[512];
sprintf(sql,"insert into user values(null,\"%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 -1;
}
return 0;
}
int do_login(int acceptfd, MSG *msg,sqlite3 *db){
char sql[512] = {0};
char *errmsg;
int nrow;
int ncloumn;
char **resultp;
sprintf(sql,
"select * from user where name='%s' and pwd='%s';",
msg->name,msg->data);
printf("%s\n", sql);
if(sqlite3_get_table(db,sql,&resultp,
&nrow,&ncloumn,&errmsg))
{
printf("%s\n", errmsg);
return -1;
}
if(nrow == 1)
{
//查询成功,有此用户
strcpy(msg->data,"OK");
send(acceptfd,msg,sizeof(MSG),0);
return 1;
}
else
{
//密码或者用户名错误
strcpy(msg->data,"user/password/worng");
send(acceptfd,msg,sizeof(MSG),0);
return 0;
}
return 1;
}
int do_query(int acceptfd, MSG *msg,sqlite3 *db){
printf("正在查询....");
char word[256]={0};
char mean[100]={0};
int found=0;
char date[255]={0};
char sql[1024]={0};
char *errmsg;
//msg结构体中要查询的单词
strcpy(word,msg->data);
printf("%s\n",msg->data);
//把单词存在数据库 找到返回1
sprintf(sql,"select mean from dict where word=\'%s\'",word);
printf("%s\n", sql);
int rc=sqlite3_exec(db,sql,search_callback,mean,&errmsg);
if(rc!=SQLITE_OK){
fprintf(stderr,"失败:%s\n",sqlite3_errmsg(db));
return -1;
}
if(strlen(mean)!=0){
strcpy(msg->data,mean);
found = 1;
}
printf("查询一个单词完毕\n");
if(1==found)//找到单词,同时将用户名,时间,单词,放到历史记录表中
{
memset(sql,0,sizeof(sql));
//需要获取系统时间
get_date(date);
sprintf(sql,"insert into record values( '%s','%s',\"%s\")",msg->name,date,word);
printf("%s\n", sql);
if(SQLITE_OK!=sqlite3_exec(db,sql,NULL,NULL,&errmsg))
{
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 do_history(int acceptfd, MSG *msg,sqlite3 *db){
char sql[128]={0};
char *errmsg;
sprintf(sql,"select * from record where name = '%s'",msg->name);
//查询数据库
if(SQLITE_OK!=sqlite3_exec(db,sql,history_callback,(void *)&acceptfd,&errmsg))
{
printf("%s\n",errmsg);
}
else
{
printf("已查看历史\n");
}
//所有的记录查询发送完毕后给客户端发送结束信息
msg->data[0]='\0';
send(acceptfd,msg,sizeof(MSG),0);
return 0;
}
int search_callback(void *para,int f_num,char **f_value,char **f_name){
strcat((char*)para,f_value[0]);
printf("mean: %s\n",(char*)para);
return 0;
}
int history_callback(void *para,int f_num,char **f_value,char **f_name){
int acceptfd =*(int*)para;
MSG msg;
sprintf(msg.data,"%s: %s",f_value[2],f_value[1]);
send(acceptfd,&msg,sizeof(MSG),0);
return 0;
}
void get_date(char *date){
//获取时间
time_t t;
struct tm *p;
time(&t);
p = localtime(&t);
sprintf(date,"%d-%d-%d %d:%d:%d",
p->tm_year+1900,p->tm_mon+1,p->tm_mday,
p->tm_hour,p->tm_min,p->tm_sec);
}
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");
}
}
close(acceptfd);
exit(0);
return 0;
}
int server_init(char *ip, int port){
int sockfd;
struct sockaddr_in serveraddr;
//创建流式套接字
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("0.0.0.0");
serveraddr.sin_port = htons(8008);
//绑定客户端
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);
printf("服务器成功运行--------\n");
return sockfd;
}
#include "server.h"
int main(int argc, char *argv[])
{
int sockfd;
MSG msg;
sqlite3 *db;
pid_t pid;
int acceptfd;
struct sockaddr_in cddr={0};
socklen_t len = sizeof(cddr);
//打开数据库
if(sqlite3_open(DATABASE, &db)!=SQLITE_OK)
{
printf("%s\n", sqlite3_errmsg(db));
return -1;
}
sockfd = server_init("0.0.0.0", 8008);
while(1){
//套接字 客户端地址 客户端大小
if((acceptfd = accept(sockfd,(void *)&cddr,&len))<0)
{
perror("fail to accept");
return -1;
}
//解析客户端ip 和 port 需要用到inet_ntoa() ntohs()转换
printf("IP:%s PORT:%d connected.\n",inet_ntoa(cddr.sin_addr),
ntohs(cddr.sin_port));
pid = fork();
if(pid < 0){
perror("fail to fork");
return -1;
}
else if(pid == 0)
{
//处理客户端具体的消息
close(sockfd);
do_client(acceptfd, db);
}
else
{
//父进程,用来接收客户端请求
close(acceptfd);
}
}
return 0;
}
ser: main.c ser_dict.c
gcc main.c ser_dict.c -o ser -lsqlite3
一定要注意gcc前面是Tab键,否则会报错,记得加上 -lsqlite3 连接一下库
直接make就能编译,真的方便。若报警告不管,报错不能生成 cli 执行文件 或 ser 执行文件
# 先把服务器端打开
./ser
# 再把客户端打开,如果你有多台电脑互联也可以试试其他电脑ip
./cli 127.0.0.1 8008
# 若不想注册可用 用户名:bob 密码:123 登录。
服务器端代码步骤:
1.建立套接字 2.设置ip和端口号 3.绑定套接字 4.监听 5.接收客户端请求 6.创建子进程进行通信
客户端代码步骤:
1.建立套接字 2.设置ip和端口号 3.连接服务器 4.与服务器进行通信
数据库:
sqlite3_op()打开数据库 这里有一个 sqlite3 *db数据库操作句柄
sqlite3_exec()用来执行sql语句,里面有一个回调函数,基本上只有查询的时候用到。
sqlite3_get_table()参数char **resultp获取的就是他的结果 用resultp[1]这种形式访问,就像二维数组用一维的方式访问一样。
在客户端菜单界面用到switch语句,在输入scanf之后需要用getchar()带走脏字符,这是一个循环,若不这样做,下一次会读取到这个脏字符,但是还是没有完全解决。fgets也不好。
可以同时登陆一个账号,未解决变量作用域问题。
sprintf()会输入无线长度的字符串,不得直接使用无长度限制的字符拷贝函数。不应直接使用legacy的字符串拷贝、输入函数,如strcpy、strcat、sprintf、wcscpy、mbscpy等,这些函数的特征是:可以输出一长串字符串,而不限制长度。如果环境允许,应当使用其_s安全版本替代,或者使用n版本函数(如:snprintf,vsnprintf)。