目录
3. 客户端与服务端的通信结构体二者要一致, 发多少接收多少
一. 项目需求分析
1. 要求客户端有注册登录模块, 客户端的数据和相关信息存储在服务器的数据库中
2. 客户端登录成功之后可以查询单词, 服务器将数据库文件中的对应单词的意思发送给客户端
3. 客户端还要求可以查询历史记录
二. 程序设计流程图
1. 客户端
2. 服务端
三.程序源码
1. 客户端代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <strings.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define N 64
#define Erro_Handle(msg) do{perror(msg); exit(EXIT_FAILURE);}while(0)
#define DEBUG(msg) printf("======%s=====\n", msg)
//自定义消息类型
enum {
R, L, Q, H
};
//设置通信结构体
typedef struct _msg {
int type;
char name[N];
char data[256];
char password[64];
}MSG;
typedef struct sockaddr_in Addr_in;
typedef struct sockaddr Addr;
void do_register(int sockfd, MSG *msg);
int do_login(int sockfd, MSG *msg);
void do_query(int sockfd, MSG *msg);
void do_history(int sockfd, MSG *msg);
int main(int argc, const char *argv[])
{
int sockfd;
Addr_in addr;
//输入opt
int opt;
//通信结构体
MSG msg;
if(argc < 3) {
printf("<%s><addr><port>\n", argv[0]);
exit(0);
}
//1. 准备套接字
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
Erro_Handle("sokcet");
bzero(&addr, sizeof(Addr_in));
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[2]));
if(!(inet_aton(argv[1], &addr.sin_addr)))
Erro_Handle("inet_aton");
if((connect(sockfd, (Addr *)&addr, sizeof(Addr_in))) < 0)
Erro_Handle("connect");
//2. 用户交互的界面
while(1) {
printf("***********************************************\n");
printf("*1. register 2. login 3. quit*\n");
printf("***********************************************\n");
scanf("%d", &opt);
getchar(); //吃掉回车
switch(opt) {
case 1:
do_register(sockfd, &msg);
break;
case 2:
if((do_login(sockfd, &msg)) == 1)
goto next;
break;
case 3:
printf("program exit!\n");
close(sockfd);
exit(0);
default:
printf("Invalid opt !\n");
}
}
next:
while(1) {
printf("***********************************************\n");
printf("*1, query 2. history 3. qiut*\n");
printf("***********************************************\n");
scanf("%d", &opt);
switch(opt) {
case 1:
do_query(sockfd, &msg);
break;
case 2:
do_history(sockfd, &msg);
break;
case 3:
printf("quit!\n");
exit(0);
default:
printf("Invalid opt !\n");
}
}
return 0;
}
//注册模块
void do_register(int sockfd, MSG *msg) {
printf("%s %d\n", __FUNCTION__, __LINE__);
msg->type = R;
printf("please input your name:");
scanf("%s", msg->name);
getchar();
printf("please input your password:");
scanf("%s", msg->password);
if(send(sockfd, msg, sizeof(MSG), 0) < 0)
Erro_Handle("send");
if(recv(sockfd, msg, sizeof(MSG), 0) < 0)
Erro_Handle("recv");
printf("%s\n", msg->data);
return;
}
//登录模块
int do_login(int sockfd, MSG *msg) {
printf("%s %d\n", __FUNCTION__, __LINE__);
msg->type = L;
printf("please your name:");
scanf("%s", msg->name);
getchar();
printf("please input your password:");
scanf("%s", msg->password);
if(send(sockfd, msg, sizeof(MSG), 0) < 0)
Erro_Handle("send");
if(recv(sockfd, msg, sizeof(MSG), 0) < 0)
Erro_Handle("recv");
if(strncmp(msg->data, "ok", 2) == 0) {
printf("login success. \n");
return 1;
} else {
printf("login faile. \n");
}
return 0;
}
//查询模块
void do_query(int sockfd, MSG *msg) {
printf("%s %d\n", __FUNCTION__, __LINE__);
msg->type = Q;
while(1) {
printf("please input you want query word:");
scanf("%s", msg->data);
getchar();
//返回上一级菜单
if(msg->data[0] == '#')
break;
//发送查询单词请求
if(send(sockfd, msg, sizeof(MSG), 0) < 0)
Erro_Handle("send");
//接收服务端的查询信息
if(recv(sockfd, msg, sizeof(MSG), 0) < 0)
Erro_Handle("recv");
printf("%s\n", msg->data);
}
return ;
}
//查询历史记录模块
void do_history(int sockfd, MSG *msg) {
printf("%s %d\n", __FUNCTION__, __LINE__);
msg->type = H;
printf("please input you want history of name:");
scanf("%s", msg->name);
getchar();
if(send(sockfd, msg, sizeof(MSG), 0) < 0)
Erro_Handle("send");
if(recv(sockfd, msg, sizeof(MSG), 0) < 0)
Erro_Handle("recv");
printf("%s\n", msg->data);
return ;
}
2. 服务端
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <strings.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sqlite3.h>
#include <signal.h>
#include <time.h>
#include <sys/epoll.h>
#define N 64
#define BACKLOG 5
#define DATABASE "Dic.db"
#define Erro_Handle(msg) do{perror(msg); exit(EXIT_FAILURE);}while(0)
#define DEBUG(msg) printf("======%s=====\n", msg)
#define NUM 1024
//自定义消息类型
enum {
R, L, Q, H
};
//设置通信结构体
typedef struct _msg {
int type;
char name[N];
char data[256];
char password[64];
}MSG;
typedef struct sockaddr_in Addr_in;
typedef struct sockaddr Addr;
void do_client(int acceptfd, MSG *msg, sqlite3 *db);
void do_register(int acceptfd, MSG *msg, sqlite3 *db);
void do_login(int acceptfd, MSG *msg, sqlite3 *db);
void do_query(int acceptfd, MSG *msg, sqlite3 *db);
void do_history(int acceptfd, MSG *msg, sqlite3 *db);
int main(int argc, char *argv[])
{
sqlite3 *db;
int sockfd, acceptfd;
Addr_in addr, peeraddr;
socklen_t peeraddr_size = sizeof(Addr_in);
MSG msg;
int epfd;
int nfds; //epoll_wait的返回值, 代表IO准备的数量
int i;
struct epoll_event tmp, events[NUM];
if((sqlite3_open(DATABASE, &db)) != SQLITE_OK) {
printf("%s \n", sqlite3_errmsg(db));
return -1;
} else {
printf("sqlite3_open success. \n");
}
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
Erro_Handle("socket");
//设置套接字属性
int flag = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(int));
bzero(&addr, sizeof(Addr_in));
addr.sin_family = AF_INET;
addr.sin_port = htons(atoi(argv[2]));
addr.sin_addr.s_addr = inet_addr(argv[1]);
if((bind(sockfd, (Addr *)&addr, sizeof(Addr_in))) < 0)
Erro_Handle("bind");
if(listen(sockfd, BACKLOG) < 0)
Erro_Handle("listen");
//signal(SIGCHLD, SIG_IGN); //子进程结束就回收
/*采用epoll IO多路复用*/
if((epfd = epoll_create(1)) < 0)
Erro_Handle("epoll_create");
tmp.data.fd = sockfd;
tmp.events = EPOLLIN;
if(epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &tmp) < 0) //将事件tmp结构体加入到events结构体数组中去
Erro_Handle("epoll_ctl");
while(1) {
if((nfds = epoll_wait(epfd, events, NUM, -1)) < 0) { //看events数组中io就绪的fd数量
Erro_Handle("epoll_wait");
}
for(i = 0; i < nfds; i++) {
if(events[i].data.fd == sockfd) {
acceptfd = accept(sockfd, (Addr *)&peeraddr, &peeraddr_size);
if(acceptfd < 0) {
Erro_Handle("accept");
} else {
printf("[%s %d] connect success.\n", inet_ntoa(peeraddr.sin_addr), \
ntohs(peeraddr.sin_port));
}
tmp.data.fd = acceptfd;
tmp.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, acceptfd, &tmp); //把新发生的事件装入到events
} else {
bzero(&msg, sizeof(MSG)); //empty structure
do_client(acceptfd, &msg, db);
}
}
}
close(sockfd);
close(acceptfd);
return 0;
}
void do_client(int acceptfd, MSG *msg, sqlite3 *db) {
while(recv(acceptfd, msg, sizeof(MSG), 0)) {
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 opt !\n");
exit(0);
}
}
}
void do_register(int acceptfd, MSG *msg, sqlite3 *db) {
char sql[128];
char *errmsg;
//sql语句字符型的加上引号,
sprintf(sql, "insert into usr values('%s', %s);", msg->name, msg->password);
if((sqlite3_exec(db, sql, NULL, NULL, &errmsg)) != SQLITE_OK) {
printf("insert failed .\n");
strcpy(msg->data, "name already");
} else {
printf("inser success .\n");
strcpy(msg->data, "register success.");
}
if(send(acceptfd, msg, sizeof(MSG), 0) < 0)
Erro_Handle("send");
return ;
}
void do_login(int acceptfd, MSG *msg, sqlite3 *db) {
char sql[128];
char *errmsg;
int row, column;
char **result;
sprintf(sql, "select * from usr where name = '%s' and password = '%s'", \
msg->name, msg->password);
if((sqlite3_get_table(db, sql, &result, &row, &column, &errmsg)) != SQLITE_OK) {
printf("%s\n", "sqlite3_get_table failed");
sprintf(msg->data, "login failed. ");
} else {
printf("sqlite3_get_table success.\n");
}
if(row == 1) {
strcpy(msg->data, "ok");
}
if(row == 0) {
strcpy(msg->data, "login failed.");
}
if(send(acceptfd, msg, sizeof(MSG), 0) < 0)
Erro_Handle("send");
return ;
}
int search_word(int acceptfd, MSG *msg, sqlite3 *db, char word[]) {
FILE *fp;
char buf[512];
int len, result;
char *p;
len = strlen(word);
if((fp = fopen("dict.txt", "r")) == NULL) {
Erro_Handle("fopen");
sprintf(msg->data, "fopen");
send(acceptfd, msg, sizeof(MSG), 0);
}
while(fgets(buf, 512, fp) != NULL) {
result = strncmp(buf, word, len);
if(result < 0)
continue;
//找到的算法
if(result == 0 && buf[len] == ' ') {
p = &buf[len];
while(*p == ' ') {
p++;
}
strcpy(msg->data, p);
send(acceptfd, msg, sizeof(MSG), 0);
return 1;
}
}
strcpy(msg->data, "Not found");
send(acceptfd, msg, sizeof(MSG), 0);
return 0;
}
void get_date(char *date) {
struct tm *t;
time_t _time;
_time = time(NULL);
t = localtime(&_time);
sprintf(date, "%4d-%02d-%02d-%02d-%02d-%02d", t->tm_year + 1900, t->tm_mon + 1, \
t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
#if 0
struct tm {
int tm_sec; /* seconds */
int tm_min; /* minutes */
int tm_hour; /* hours */
int tm_mday; /* day of the month */
int tm_mon; /* month */
int tm_year; /* year */
int tm_wday; /* day of the week */
int tm_yday; /* day in the year */
int tm_isdst; /* daylight saving time */
};
#endif
}
//查询模块, 文件IO
void do_query(int acceptfd, MSG *msg, sqlite3 *db) {
char *word;
int found;
char *errmsg;
char sql[128];
word = msg->data;
char date[64]; //获取日期
found = search_word(acceptfd, msg, db, word);
printf("查询一个单词完毕\n");
if(found == 1) {
/*************************************************/
printf("query success.\n");
/*************************************************/
get_date(date);
printf("%s\n", 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("sqlite3_exec insert record failed.\n");
return;
}else {
printf("insert record success.\n");
}
} else {
printf("client query failed.\n");
}
return ;
}
#if 0
void do_history(int acceptfd, MSG *msg, sqlite3 *db) {
char sql[128];
char *errmsg;
char **result;
int row, column, index, i, j;
int len = 0;
sprintf(sql, "select * from record where name = '%s'", msg->name);
if((sqlite3_get_table(db, sql, &result, &row, &column, &errmsg)) != SQLITE_OK) {
printf("sqlite3_get_table failed.\n");
return ;
} else {
printf("select record success.\n");
index = column;
/************************************/
printf("%d %d\n", row, column);
/************************************/
for(i = 0; i < row; i++) {
for(j = 0; j < column; j++) {
len = len + strlen(result[index]);
sprintf(&msg->data[len], "%s | ", result[index++]);
printf("%s | ", result[index++]);
}
putchar(10);
}
send(acceptfd, msg, sizeof(MSG), 0);
}
return ;
}
#endif
int CallBack_history(void* arg,int ncolumns ,char** f_value,char** f_name) {
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;
}
void 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, CallBack_history, (void *)&acceptfd, &errmsg) != SQLITE_OK) {
printf("select record failed.\n");
sprintf(msg->data, "select record failed.");
send(acceptfd, msg, sizeof(MSG), 0);
} else {
printf("select record success.\n");
}
}
四. 项目总结与反思
1. 项目知识点总结
(1)网络编程
a.socket创建通信
b.epoll IO多路复用
c.设置套接字属性
(2)文件IO
a. fgets
b. 文件IO , 标准IO
(3)sqlite3数据库
a. sql语句
b.sqlite3函数接口
2. 项目中对缓冲区的运用总结
如上图, 定义一些指针变量, 往这个指针变量里面拷贝数据, 运行之后段错误。
为什么呢? 原因是这样, 我们常常可以看到这样的代码
char *p = "hello";
这里需要了解双引号的作用:它实际上干了这么三件事情:
(1)在常量区给字符串申请空间
(2)在字符串末尾加‘\0’
(3)返回字符串的首地址
所以指针p只是保存了字符串的首地址当然没问题, 空间也是申请好的, 可要是像图(1)那样给一个指针拷贝一串数据就对内存非法访问了, 没有空间对吧。