项目介绍

描述:
通过C/S架构实现在线词典,用户在客户端可以注册,登陆,然后可以查询单词,并且保存自己的单词查询记录。

知识点:

  • c语言进阶 Linux基础
  • C/S架构
  • 进程
  • sqlite3数据库
  • 时间函数
  • Makefile

效果图:


客户端

创建一个dict_client文件夹,存放客户端代码

client.h

#ifndef CLIENT_H
#define CLIENT_H
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.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]; //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

cli_dict.c

#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 existprintf("%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;
}

main.c

#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 文件,也可以不做这步,只是多敲几串命令而已,Makefile最大优点就是直接输入make命令就能编译文件,也挺方便。

cli: main.c cli_dict.cgcc *.c -o cli

服务器端

sqlite3

需要在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表需要自己插入单词和含义。

server.h

#ifndef SERVER_H
#define SERVER_H#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sqlite3.h>
#include <signal.h>
//时间函数
#include <time.h>
#include <unistd.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 "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

ser_dict.c

#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);//把单词存在数据库 找到返回1sprintf(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;
}

main.c

#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;
}

Makefile

ser: main.c ser_dict.cgcc 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)。

C语言 Linux网络编程(C/S架构) 在线词典相关推荐

  1. Linux网络编程:用C语言实现的聊天程序(同步通信)

    通过TCP协议,用C语言实现的同步聊天程序,注释写的比较详细,个人觉得对字符串处理比较充分,能够正常编译运行,拿出来和大家分享一下! 1.客户端源代码: [cpp] view plaincopypri ...

  2. Linux网络编程——千峰物联网笔记

    B站视频:千峰物联网学科linux网络编程 网址:https://www.bilibili.com/video/BV1RJ411B761?p=1 目录 第一章:计算机网络概述 1.1计算机网络发展简史 ...

  3. Linux网络编程经典书籍推荐

    Linux网络编程经典书籍推荐 目录(?)[+] 首先要说讲述TCP/IP的书很多,其中有3泰书很全. 分别是<TCP/IP详解>三卷本,<用TCP/IP进行网际互连>三卷本, ...

  4. Linux网络编程实例分析

    最近由于工作原因需要温习一下Linux网络编程的部分基础知识,因此对之前写的Socket网络通信的代码进行了进一步优化和拓展,在不关闭一次Socket连接的基础上,对服务端加入循环读写的功能,同时加入 ...

  5. Linux网络编程必看书籍推荐

    首先要说讲述计算机网络和TCP/IP的书很多. 先要学习网络知识才谈得上编程 讲述计算机网络的最经典的当属Andrew S.Tanenbaum的<计算机网络>第五版,这本书难易适中. &l ...

  6. 【Linux网络编程】TCP编程

    00. 目录 文章目录 00. 目录 01. TCP概述 02. TCP特点 03. TCP中CS架构 04. TCP相关函数 05. TCP服务端示例 06. TCP客户端示例 07. 附录 01. ...

  7. Linux网络编程——黑马程序员笔记

    01P-复习-Linux网络编程 02P-信号量生产者复习 03P-协议 协议: 一组规则. 04P-7层模型和4层模型及代表协议 分层模型结构: OSI七层模型: 物.数.网.传.会.表.应TCP/ ...

  8. Linux网络编程-七

    Linux网络编程-七 web服务器项目 1 web服务器开发准备 1.1 Html语言基础(和Markdown一个性质,某些程度上和Markdown通用,所以我在编辑的时候在<>里都加了 ...

  9. linux网络编程大杂烩==Linux应用编程7

    一.Linux 网络编程框架 1.网络是分层的 (1)OSI 七层模型:应用层.表示层.会话层.传输层.网络层.数据链路层.物理层. (2)网络为什么要分层:互联网及其复杂,需要分层以便更好地实现网络 ...

  10. alin的学习之路(Linux网络编程:一)(网络模型、帧格式、socket套接字、服务器端实现)

    alin的学习之路(Linux网络编程:一)(网络模型.帧格式.socket套接字.服务器端实现) 1. 协议 协议是一组规则,规定了如何发送数据.通信的双发都需要遵守该规则 2. 网络分层结构模型 ...

最新文章

  1. java 显示日历_JAVA显示日历(已知年和该年第一天为星期几)
  2. linux内核是否支持nfs,嵌入式命令:查看设备是否支持nfs
  3. MySQL命名、设计及使用规范《MySQL命名、设计及使用规范》
  4. shell mysql备份脚本_mysql备份脚本(shell)
  5. php pg_fetch_row,pg_fetch_row
  6. java zookeeper 主从热备_zookeeper 核心原理
  7. Markdown常用数学符号
  8. 坯子库无法一键安装插件没用_坯子插件库_SketchUp坯子库(su插件管理器)下载 v2018.3官方版 - 121下载站...
  9. 12306验证码图片获取
  10. 满减折扣促销功能代码优化实战
  11. Mule ESB开发与学习
  12. 物流运交管理系统 货运单管理
  13. JavaScript 解析json例子
  14. 41.Android之图片放大缩小学习
  15. Android10支持volte,Nemo_LG V35 安卓10.0解锁Volte(联通、电信、移动)教程_Nemo社区_LinkNemo_关于分享和探索的好地方...
  16. 《CSS禅意花园》翻译完成 Dflying又回来了!
  17. c语言虚数变量,关于C语言中的Complex(复数类型)和imaginary(虚数类型)
  18. sql语句中插入二进制数据
  19. 精品基于PHP实现的好物优购商城|电商小程序
  20. 基于springboot+layui快速开发框架源码

热门文章

  1. php laravel手册,laravel5.6手册下载|Laravel5.6中文手册pdf最新版下载(附使用方法)_星星软件园...
  2. 如何做决策?SWOT分析
  3. n1盒子救砖_斐讯N1盒子刷机救砖教程
  4. 专访方志朋:2018年仍然是微服务飞速发展的一年
  5. 接口测试用例设计理论
  6. mrpoid模拟器java版_mrpoid2冒泡模拟器下载-mrpoid2模拟器下载3.0安卓版-西西软件下载...
  7. 代数方程与差分方程模型(三):按年龄分组的人口模型
  8. 显卡煲机测试软件,晒物评测 篇三:煲机到底有没有用?森海塞尔木馒头耳机用了3年的评测,随便聊聊...
  9. 2022-2028年中国电力载波通信行业市场行情动态及竞争战略分析报告
  10. mdx格式的词典用什么软件打开_英专生必备词典