目录
要求
1、开启服务器后,子客户端能通过相关通道进行单词的查询
2、服务器未开启时子客户端将无法查询
3、TCP通信
4、词库选择SQLite存储
示例代码片段
Makefile
all:
gcc server.c -o server
gcc client.c -o client
.PHONY:clean
clean:
rm -f server client
server
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sqlite3.h>
#include <signal.h>
#include <time.h>
#define NAME_LEN 32
#define MSGTYPE_R 11 // register
#define MSGTYPE_L 22 // login
#define MSGTYPE_Q 33 // query
#define MSGTYPE_H 44 // history
#define DATABASE "my.db"
typedef struct {
int type;
char name[NAME_LEN];
char data[256];
}MSG_T;
struct sockaddr_in
{
uint8_t sin_len;
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
char sin_zero[SIN_ZERO_LEN];
#define SIN_ZERO_LEN 8
};
// 注册
int do_register(int sockfd, MSG_T *msg, sqlite3 *db)
{
char sql[128] = {0};
char *errmsg = NULL;
sprintf(sql, "insert into user values('%s', '%s');", msg->name, msg->data);
if (sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK)
{
printf("%s\n", errmsg);
memset(msg->data, 0, strlen(msg->data));
sprintf(msg->data, "user(%s) already exist.", msg->name);
}
else
{
printf("client user(%s) register success..\n", msg->name);
memset(msg->data, 0, strlen(msg->data));
strcpy(msg->data, "register success..\n");
}
// 返回
if (send(sockfd, msg, sizeof(MSG_T), 0) < 0)
{
printf("send error..\n");
return 0;
}
return 1;
}
// 登录, 服务器返回 "OK"表示登录成功
int do_login(int sockfd, MSG_T *msg, sqlite3 *db)
{
int nrow, ncolumn;
char sql[128] = {0};
char *errmsg = NULL, **result = NULL;
sprintf(sql, "select * from user where name='%s' and passwd='%s';", msg->name, msg->data);
if (sqlite3_get_table(db, sql, &result, &nrow, &ncolumn, &errmsg) != SQLITE_OK)
printf("%s\n", errmsg);
else
printf("sqlite3 get table ok! \n");
// 查询成功当数据库中拥有此用户时
if (nrow == 1)
{
printf("client user(%s) login success\n", msg->name);
memset(msg->data, 0, strlen(msg->data));
strcpy(msg->data, "OK");
}
else
{
printf("client user(%s) login fail! \n", msg->name);
memset(msg->data, 0, strlen(msg->data));
strcpy(msg->data, "user or passwd wrong! \n");
}
// 返回
if (send(sockfd, msg, sizeof(MSG_T), 0) < 0)
{
printf("fail to send\n");
return 0;
}
return 1;
}
// 查找单词
int do_searchword(MSG_T *msg, char *word)
{
FILE *fp = NULL;
int word_len = strlen(word);
char row_data[512] = {'\0'};
int res = 0;
char *p; // 指向注释
// 打开文件
if ((fp = fopen("dict.txt", "r")) == NULL)
{
perror("fopen error..\n");
return -1;
}
// 显示查询到的单词
word_len = strlen(word);
printf("%s, len = %d\n", word, word_len );
// 读取文件、行数据(一行一行读取),对比要查询的单词
// 如果成功,该函数返回相同的 str 参数。
// 如果到达文件末尾或者没有读取到任何字符,str 的内容保持不变,并返回一个空指针。
// 如果发生错误,返回一个空指针。
while (fgets(row_data, 512, fp) != NULL)
{
res = strncmp(row_data, word, word_len); // 每行对比前word_len个字节
if (res != 0)
continue;
if (row_data[word_len] != ' ') // 单词跟注释之间没有空格
goto _end;
// 找到了单词,跳过所有的空格
p = row_data + word_len;
while (*p == ' ')
{
p++;
}
strcpy(msg->data, p);
fclose(fp);
return 1;
}
_end:
fclose(fp);
return 0; // 未获取到单词
}
// 获取系统时间
void get_date(char *data)
{
time_t rowtime;
struct tm *info;
// 时间格式转换
info = localtime(&rowtime);
sprintf(data, "%d-%d-%d %d:%d:%d", info->tm_year + 1900, info->tm_mon + 1, info->tm_mday,info->tm_hour, info->tm_min, info->tm_sec);
printf("get date is : %s\n", data);
}
// 单词查询
int do_query(int sockfd, MSG_T *msg, sqlite3 *db)
{
char sql[128] = {0};
char word[64] = {0};
int found = 0;
char date[128] = {0};
char *errmsg;
printf("\n");
// 单词查找
strcpy(word, msg->data);
found = do_searchword(msg, word);
if (found == 1) // 插入到历史记录表
{
get_date(date); // 获取系统时间
//sprintf(sql, "insert into user values('%s', '%s');", msg->name, msg->data);
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("sqlite3 insert record done.\n");
}
}
else if (found == 0)//没有找到
{
memset(msg->data, 0, strlen(msg->data));
strcpy(msg->data, "Not found!\n");
}
else if (found == -1)//dict.txt代开失败
{
memset(msg->data, 0, strlen(msg->data));
strcpy(msg->data, "fail to open dict.txt.");
}
//将查询的结果发送给客户端
send(sockfd, msg, sizeof(MSG_T), 0);
return 0;
}
// 获取查询结果并将历史记录发送客户端
int history_callback(void* arg,int colCount,char** colValue,char** colName)
{
int acceptfd;
MSG_T msg;
acceptfd = *((int *)arg);
sprintf(msg.data, "%s , %s", colValue[1], colValue[2]);
send(acceptfd, &msg, sizeof(MSG_T), 0);
return 0;
}
// 历史记录查询
int do_history(int sockfd, MSG_T *msg, sqlite3 *db)
{
char sql[128] = {0};
char *errmsg;
// 查询数据库
// 会先执行*sql对应的功能命令,然后将结果传递给回调函数,回调函数根据结果再进一步执行
sprintf(sql, "select * from record where name = '%s'", msg->name);
if(sqlite3_exec(db, sql, history_callback,(void *)&sockfd, &errmsg)!= SQLITE_OK)
printf("%s\n", errmsg);
else
printf("sqlite3 query record done.\n");
// 给客户端回复完成发送消息
msg->data[0] = '\0';
send(sockfd, msg, sizeof(MSG_T), 0);
return 0;
}
int do_client(int acceptfd, sqlite3 *db)
{
MSG_T recv_msg;
// 默认是阻塞式接收
// 如果客户端断开连接或是主动发送close关闭连接 , recv会返回0
while (recv(acceptfd, &recv_msg, sizeof(MSG_T), 0) > 0)
{
switch(recv_msg.type)
{
case MSGTYPE_R:
do_register(acceptfd, &recv_msg, db);
break;
case MSGTYPE_L:
do_login(acceptfd, &recv_msg, db);
break;
case MSGTYPE_Q:
do_query(acceptfd, &recv_msg, db);
break;
case MSGTYPE_H:
do_history(acceptfd, &recv_msg, db);
break;
default:
printf("invalid data msg.\n");
}
}
printf("client exit..\n");
close(acceptfd);
exit(0);
return 0;
}
int main(int argc, const char *argv[])
{
int sockfd, acceptfd;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
socklen_t addrlen = sizeof(client_addr);
char ipv4_addr[16];
sqlite3 *db = NULL;
char *errmsg = NULL;
char sql[128] = {0};
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;
}
printf("sqlite3 open %s success.\n", DATABASE);
sprintf(sql, "create table if not exists user(name text primary key, passwd text);");
if (sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK) // 创建用户表
{
printf("%s\n", errmsg);
return -1;
}
memset(sql, 0, sizeof(sql));
sprintf(sql, "create table if not exists record(name text, date text, word text);");
if (sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK)//创建记录表
{
printf("%s\n", errmsg);
return -1;
}
// 创建流式套接字
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("socket create error..\n");
return -1;
}
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
server_addr.sin_port = htons(atoi(argv[2]));
bzero(&(server_addr.sin_zero), sizeof(server_addr.sin_zero));
// 绑定
if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(struct sockaddr)) < 0)
{
perror("bind error..\n");
return -1;
}
// 监听
if (listen(sockfd, 5) < 0)
{
perror("listen error..\n");
exit(1);
}
printf("listen success..\n");
// 僵尸进程处理
signal(SIGCHLD, SIG_IGN);
while (1)
{
if ((acceptfd = accept(sockfd, (struct sockaddr *)&client_addr, &addrlen)) < 0)
{
perror("acceptfd error..\n");
return -1;
}
if (inet_ntop(AF_INET, &client_addr.sin_addr, ipv4_addr, addrlen) < 0)
{
perror("inet_ntop error..\n");
return -1;
}
printf("client(%s:%d) is connected..\n", ipv4_addr, htons(client_addr.sin_port));
// 创建子进程
if ((pid = fork()) < 0)
{
perror("fork create error..\n");
return -1;
}
printf("fork create success..\n");
else if (pid == 0) // 子进程处理客户端消息
{
close(sockfd);
do_client(acceptfd, db);
}
else // 父进程接收客户端连接请求
close(acceptfd);
}
return 0;
}
client
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define NAME_LEN 32
#define MSGTYPE_R 11 // register
#define MSGTYPE_L 22 // login
#define MSGTYPE_Q 33 // query
#define MSGTYPE_H 44 // history
typedef struct {
int type;
char name[NAME_LEN];
char data[256];
}MSG_T;
/*
struct sockaddr
{
uint8_t sa_len;
sa_family_t sa_family;
char sa_data[14];
};
/* members are in network byte order */
/*
struct sockaddr_in
{
uint8_t sin_len;
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
char sin_zero[SIN_ZERO_LEN];
#define SIN_ZERO_LEN 8
};
struct in_addr
{
in_addr_t s_addr;
};
*/
// 注册
int do_register(int sockfd, MSG_T *msg)
{
printf("register..\n");
memset(msg, 0, sizeof(MSG_T));
msg->type = MSGTYPE_R;
printf("input name:"); scanf("%s", msg->name); getchar();
printf("input passwd:"); scanf("%s", msg->data); getchar();
if (send(sockfd, msg, sizeof(MSG_T), 0) < 0)
{
printf("fail to send register msg.\n");
return -1;
}
if (recv(sockfd, msg, sizeof(MSG_T), 0) < 0)
{
printf("register error..\n");
return -1;
}
printf("%s\n", msg->data);
return 1;
}
// 服务器返回 "OK" 表示登录成功
int do_login(int sockfd, MSG_T *msg)
{
printf("login..\n");
memset(msg, 0, sizeof(MSG_T));
msg->type = MSGTYPE_L;
printf("input name:"); scanf("%s", msg->name); getchar();
printf("input passwd:"); scanf("%s", msg->data); getchar();
if (send(sockfd, msg, sizeof(MSG_T), 0) < 0)
{
printf("send register msg error..\n");
return -1;
}
if (recv(sockfd, msg, sizeof(MSG_T), 0) < 0)
{
printf("login error..\n");
return -1;
}
if (strncmp(msg->data, "OK", 3) == 0)
{
printf("login success..\n");
return 1;
}
else
printf("%s\n", msg->data);
return 0;
}
// 单词查询
int do_query(int sockfd, MSG_T *msg)
{
printf("query..\n");
msg->type = MSGTYPE_Q;
while(1)
{
printf("input word:"); scanf("%s", msg->data); getchar();
// 输入"#"退出本次查询
if (strncmp(msg->data, "#", 1) == 0)
break;
// 将查询单词发送给服务器
if (send(sockfd, msg, sizeof(MSG_T), 0) < 0)
{
printf("send error..\n");
return -1;
}
// 将查询到的结果返回
if (recv(sockfd, msg, sizeof(MSG_T), 0) < 0)
{
printf("recv error..\n");
return -1;
}
printf("%s\n", msg->data);
}
return 1;
}
// 历史记录查询
int do_history(int sockfd, MSG_T *msg)
{
printf("history..\n");
msg->type = MSGTYPE_H;
// 消息发送至服务器
if (send(sockfd, msg, sizeof(MSG_T), 0) < 0)
{
printf("send error..\n");
return -1;
}
printf("send success..\n");
while(1)
{
// 将查询到的结果返回
if (recv(sockfd, msg, sizeof(MSG_T), 0) < 0)
{
printf("recv error..\n");
return -1;
}
if (msg->data[0] == '\0')
break;
// 回显历史记录
printf("%s\n", msg->data);
}
return 1;
}
int main(int argc, const char *argv[])
{
int sockfd;
struct sockaddr_in server_addr;
int input_nbr;
MSG_T send_msg;
if (argc != 3)
{
printf("usage: %s serverip port\n", argv[0]);
return -1;
}
// 创建流式套接字
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("socket create error..\n");
return -1;
}
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
server_addr.sin_port = htons(atoi(argv[2]));
bzero(&(server_addr.sin_zero), sizeof(server_addr.sin_zero)); // 清零函数
// 连接
if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(struct sockaddr)) < 0)
{
perror("fail to connect\n");
return -1;
}
// 阻塞等待
while (1)
{
printf("***********************************************\n");
printf("******* 1.register 2.login 3.quit *******\n");
printf("***********************************************\n");
printf("please choose:");
scanf("%d", &input_nbr);
getchar();
switch (input_nbr)
{
case 1:
do_register(sockfd, &send_msg);
break;
case 2:
if (do_login(sockfd, &send_msg) == 1)
goto _login;
break;
case 3:
close(sockfd);
exit(0);
break;
default:
printf("Invalid data cmd..\n");
break;
}
}
_login:
while(1)
{
printf("***********************************************\n");
printf("*****1.query_word 2.history_record 3.quit****\n");
printf("***********************************************\n");
printf("please choose:");
input_nbr = 0;
scanf("%d", &input_nbr);
getchar();
switch (input_nbr)
{
case 1:
do_query(sockfd, &send_msg);
break;
case 2:
do_history(sockfd, &send_msg);
break;
case 3:
close(sockfd);
exit(0);
break;
default:
printf("Invalid data cmd. \n");
break;
}
}
return 0;
}