开发环境、工具:
Linux Ubuntu 14.04、sqlite3、gcc、gdb、C 语言
实现功能:
普通用户的注册,服务器将用户信息存入数据库中。
普通用户和管理员(root)用户的登录验证。
客户端传入要查询的单词,服务器端查询字典文件匹配后返回结果,并记录查询的信息和时间。
客户端可查看自己查询的记录,管理员用户可查看所有用户的查询记录。
项目描述:
1、本项目为华清的Linux应用开发综合实战。
2、采用 C/S 架构,基于 TCP/IP 协议,采用多进程实现服务器与多个客户端之间的通信
3、使用结构体 struct 作为信息传递的途径
4、使用标准IO读取词典文件
5、采用轻量级数据库 sqlite3 在服务器记录用户信息和查询记录
功能解析:
流程框架:
客户端:
服务器:
具体实现:
客户端:
/*./client 192.168.10.8 5004*/
#include "net.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];
int flags; //0-普通用户 1-root用户
}MSG;
void usage (char * s); //命令行参数格式输入错误时提示
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 main(int argc, char *argv[])
{
MSG msg;
//1.判断命令行参数是否输入正确
int port;
port = atoi(argv[2]); //字符串转换成整型
if (argc != 3) {
usage(argv[0]);
exit(1);
}
if (port < 5000) {
usage(argv[0]);
exit(1);
}
//2.创建流式套接字
int sockfd;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket");
exit(1);
}
//3.申请服务器连接请求
//3.1填充服务器(结构体)信息
struct sockaddr_in sin;
bzero(&sin, sizeof(sin));
sin.sin_family = AF_INET; //地址族,这里是ipv4
sin.sin_port = htons(port); //服务器端口(转换成网络字节序)
sin.sin_addr.s_addr = inet_addr(argv[1]); //服务器ip地址(点分式ip转换成32位网络字节序ip)
//3.2连接服务器
if (connect(sockfd, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
perror("connect");
exit(1);
}
int n;
while (1) {
printf("\t***********************在线词典*************************\n");
printf("\t********************************************************\n");
printf("\t**1:注册 2:用户登录 3:退出**\n");
printf("\t********************************************************\n");
printf("Please enter your choice:");
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:
printf("已退出程序!\n");
close(sockfd);
exit(0);
break;
default:
printf("Invalid data cmd!");
}
}
next:
while (1) {
printf("\t************************在线词典************************\n");
if (msg.flags == 1) {
printf("\t********************欢迎root用户登录********************\n");
}
printf("\t********************************************************\n");
printf("\t**1:查找单词 2:历史记录 3:退出**\n");
printf("\t********************************************************\n");
printf("Please enter your choice:");
scanf("%d", &n);
getchar();
switch (n) {
case 1:
do_query(sockfd, &msg);
break;
case 2:
do_history(sockfd, &msg);
break;
case 3:
printf("已退出程序!\n");
close(sockfd);
exit(0);
break;
default:
printf("Invalid data cmd!\n");
}
}
return 0;
}
//命令行参数格式输入错误时提示
void usage (char * s) {
printf("Please enter the correct format!\n");
printf("\nUsage:\n\t %s server_ip server_port", s);
printf("\n\t server_ip: tcp server ip address");
printf("\n\t server_port: tcp server port(server_port > 5000)\n\n");
}
//注册
int do_register(int sockfd, MSG *msg) {
msg->type = R;
printf("请输入用户名:");
scanf("%s", msg->name);
getchar();
printf("请输入密码:");
scanf("%s", msg->data);
//将注册信息发送给服务器
if (send(sockfd, msg, sizeof(MSG), 0) < 0) {
perror("send");
return -1;
}
//服务器返回一个回应(ok or usr already exist)
if (recv(sockfd, msg, sizeof(MSG), 0) < 0) {
perror("recv");
return -1;
}
printf("%s\n", msg->data);
}
//登录
int do_login(int sockfd, MSG *msg) {
msg->type = L;
printf("请输入用户名:");
scanf("%s", msg->name);
getchar();
printf("请输入密码:");
scanf("%s", msg->data);
//将登录信息发送给服务器
if (send(sockfd, msg, sizeof(MSG), 0) < 0) {
perror("send");
return -1;
}
//服务器返回一个回应(ok or error)
if (recv(sockfd, msg, sizeof(MSG), 0) < 0) {
perror("recv");
return -1;
}
if (strncmp(msg->data, "成功!", 3) == 0) {
printf("登录成功!\n");
return 1;
}
else {
printf("%s\n", msg->data);
}
return 0;
}
//查找单词
int do_query(int sockfd, MSG *msg) {
msg->type = Q;
while (1) {
puts("--------------------------------------\n");
printf("请输入单词:");
scanf("%s", msg->data);
getchar();
//若客户端输入'#',则返回上级目录
if (strncmp(msg->data, "#", 1) == 0)
break;
//将要查的单词信息发送给服务器
if (send(sockfd, msg, sizeof(MSG), 0) < 0) {
perror("send");
return -1;
}
//服务器返回单词的注释信息
if (recv(sockfd, msg, sizeof(MSG), 0) < 0) {
perror("recv");
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);
puts("---------------------------------------");
while (1) {
recv(sockfd, msg, sizeof(MSG), 0);
if(msg->data[0] == '\0')
break;
printf("%s\n", msg->data);
}
puts("----------------------------------------");
return 0;
}
服务器端:
#include "net.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];
int flags; // 0-普通用户 1-root用户
}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);
int main(int argc, char *argv[])
{
//1.打开数据库
sqlite3 *db;
if (sqlite3_open(DATABASE, &db) != SQLITE_OK) {
printf("%s\n", sqlite3_errmsg(db));
exit(1);
} else {
printf("open DATABASE success!\n");
}
//2.创建流式套接字
int sockfd;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
perror("socket");
exit(1);
}
int b_reuse = 1;
setsockopt (sockfd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof (int));
//3.绑定本地服务器和端口
//3.1填充结构体信息
struct sockaddr_in sin;
bzero(&sin, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(SERV_PORT);
sin.sin_addr.s_addr = htonl(INADDR_ANY);
//3.2绑定
if (bind(sockfd, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
perror("bind");
exit(1);
}
//4.将套接字设置为监听模式
if (listen(sockfd, 5) < 0) {
perror("listen");
exit(1);
}
//5.处理僵尸进程
signal(SIGCHLD, SIG_IGN);
int acceptfd;
pid_t pid;
//6.等待连接请求,处理数据(多进程并发)
while (1) {
if ((acceptfd = accept(sockfd, NULL, NULL)) < 0) {
perror("accept");
exit(1);
}
if ((pid = fork()) < 0) {
perror("fork");
exit(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("客户端已退出\n");
close(acceptfd);
exit(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, "用户名已存在!");
}
else {
printf("client register ok!\n");
strcpy(msg->data, "成功!");
}
if (send(acceptfd, msg, sizeof(MSG), 0) < 0) {
perror("send");
return ;
}
return ;
}
//登录
int do_login(int acceptfd, MSG *msg, sqlite3 *db) {
char *errmsg;
char sql[128];
char **resultp;
int nrow;
int ncloumn;
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) {
if (strncmp(msg->name, "root", 4) == 0) {
msg->flags = 1; //表示此用户为root用户
}
strcpy(msg->data, "成功!");
send(acceptfd, msg, sizeof(MSG), 0);
return 1;
} //没有查到
else if (nrow == 0) {
strcpy(msg->data, "密码或用户名错误!");
send(acceptfd, msg, sizeof(MSG), 0);
}
return 0;
}
//查询
int do_query(int acceptfd, MSG *msg, sqlite3 *db) {
char word[64] = {};
int found = -1;
char date[128] = {};
char sql[128] = {};
char *errmsg;
strcpy(word, msg->data);
found = do_searchword(acceptfd, msg, word);
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 do_history(int acceptfd, MSG *msg, sqlite3 *db) {
char sql[128] = {};
char *errmsg;
if (msg->flags == 1) {
sprintf(sql, "select * from record;");
printf("%s\n", sql);
} else {
sprintf(sql, "select * from record where name = '%s';", msg->name);
printf("%s\n", sql);
}
//查询数据库
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;
}
//得到查询结果,并将历史记录发给客户端
int history_callback(void* arg,int f_num,char** f_value,char** f_name) {
// record , name , data , word
int acceptfd;
MSG msg;
acceptfd = *((int *)arg);
sprintf(msg.data, "%s ,%s, %s", f_value[0],f_value[1], f_value[2]);
printf("%s\n", msg.data);
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("fopen");
strcpy(msg->data, "Fail 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) {
result = strncmp(temp, word, len);
if (result < 0)
continue;
if (result > 0 || ((result == 0) && (temp[len] != ' ')))
break;
//找到了查询的单词
//越过空格去找到注释的位置
p = temp + len; //abandon v.akdsf dafsjkj
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("%s\n", date);
return 0;
}
头文件:
#ifndef _NET_H_
#define _NET_H_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sqlite3.h>
#include <signal.h>
#include <time.h>
#define SERV_PORT 5004
#define SERV_IP 192.168.10.8
#endif
运行截图: