SQLite数据库用法 : SQLite
多进程实现TCP并发服务器 : 多进程实现TCP并发
全文使用
sqlite3_exec(sql_db, buff, NULL, NULL, &errmsg)
→printf("error[%s]\n",errmsg))
→sqlite3_free(errmsg);
释放错误信息的空间 防止内存泄漏
功能:
客户端可以注册,登入,查询历史信息等操作,使用基于TCP的多线程并发服务器,并使用sqlite3数据库实现查询单词记录的导入以及对用户信息的管理。
1.注册:若用户名已经注册过,需重新注册
2.登录:用户名或密码错误需重新登录
3.查询:输入要查的单词,#键结束查询
4.历史:可以查询当前用户历史查找过的单词
5.退出:退出在线词典
链接:https://pan.baidu.com/s/1Qzkw0Wjwxx76HUP7b7Jldg?pwd=lkrc
提取码:lkrc
#include "tcp.h"
// 信号处理函数 用于回收子进程资源
void deal_signal(int s);
// callback 回调函数
int callback(void *arg, int ncolumn, char **f_value, char **f_name);
// 时间
int gettime(char *time_s);
//创建套接字-填充服务器网络信息结构体-绑定-监听
int socket_bind_listen(const char *argv[]);
int main(int argc, const char *argv[])
{
//数据库初始化
sqlite3 *sql_db = proc_init();
//检验命令行参数个数
if (3 != argc)
{
printf("Usage : %s \n" , argv[0]);
exit(-1);
}
//创建套接字-填充服务器网络信息结构体-绑定-监听
int sockfd = socket_bind_listen(argv);
//用来保存 客户端 信息的结构体
struct sockaddr_in client_addr;
memset(&client_addr, 0, sizeof(client_addr));
socklen_t client_addr_len = sizeof(client_addr);
pid_t pid;
while (1)
{
//阻塞等待客户端连接--一旦有客户端连接就会解除阻塞
int accept_fd = accept(sockfd, (struct sockaddr *)&client_addr, &client_addr_len);
if (-1 == accept_fd)
{
ERRLOG("accept error");
}
/*
inet_ntoa 32位网络字节序二进制地址转换成点分十进制的字符串
ntohs将无符号2字节整型 网络-->主机
*/
printf("客户端[%s : %d]连接\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));
if (0 > (pid = fork())) // 创建子进程
{
ERRLOG("fork error");
}
else if (pid == 0)
{
// 子进程逻辑
do_client_s(accept_fd, sql_db);
}
else
{
//父进程逻辑
/*
注册信号处理函数,用于防止僵尸进程
当子进程结束的时候,父进程会收到一个SIGCHLD的信号
*/
if (signal(SIGCHLD, deal_signal) == SIG_ERR) //捕获SIGCHLD
perror("signal error");
close(accept_fd);
}
}
//关闭监听套接字 一般不关闭
close(sockfd);
return 0;
}
//系统初始化的函数
//打开数据文件、尝试建表
sqlite3 *proc_init()
{
//打开数据库文件
sqlite3 *sql_db = NULL;
int ret = sqlite3_open(FILEname, &sql_db);
if (ret != SQLITE_OK)
{
printf("打开数据库文件 失败");
printf("errcode[%d] errmsg[%s]\n", ret, sqlite3_errmsg(sql_db));
exit(-1);
}
//建表
/*
IF NOT EXISTS 表不存在则创建 表存在则直接使用,而不是报错
引号里面sql语句后面不用加分号
*/
char *errstr = NULL;
//用户-密码
char buff[128] = "CREATE TABLE IF NOT EXISTS user(name text PRIMARY KEY, pass text)";
ret = sqlite3_exec(sql_db, buff, NULL, NULL, &errstr);
if (ret != SQLITE_OK)
{
perror("打开数据库文件 失败");
printf("errcode[%d] errmsg[%s]\n", ret, errstr);
exit(-1);
}
//用户-时间-查询记录
char buff1[128] = "CREATE TABLE IF NOT EXISTS record(name text, date text, word text)";
ret = sqlite3_exec(sql_db, buff1, NULL, NULL, &errstr);
if (ret != SQLITE_OK)
{
perror("建表 失败");
printf("errcode[%d] errmsg[%s]\n", ret, errstr);
exit(-1);
}
//释放错误信息的空间 防止内存泄漏
sqlite3_free(errstr);
return sql_db;
}
//创建套接字-填充服务器网络信息结构体-绑定
int socket_bind_listen(const char *argv[])
{
// 1.创建套接字 /IPV4 /TCP
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
ERRLOG("socket error");
// 2.填充服务器网络信息结构体
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr)); //清空
server_addr.sin_family = AF_INET; // IPV4
//端口号 填 8888 9999 6789 ...都可以
/*
atoi字符串转换成整型数
htons将无符号2字节整型 主机-->网络
*/
server_addr.sin_port = htons(atoi(argv[2]));
// ip地址 要么是当前Ubuntu主机的IP地址 或者
/*
如果本地测试的化 使用 127.0.0.1 也可以
inet_addr字符串转换成32位的网络字节序二进制值
*/
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
//结构体长度
socklen_t server_addr_len = sizeof(server_addr);
// 3.将套接字和网络信息结构体绑定
if (-1 == bind(sockfd, (struct sockaddr *)&server_addr, server_addr_len))
ERRLOG("bind error");
// 4.将套接字设置成被动监听状态
if (-1 == listen(sockfd, 10))
ERRLOG("listen error");
return sockfd;
}
//信号处理函数 用于回收子进程资源
void deal_signal(int s)
{
//回收子进程资源
wait(NULL); //阻塞回收
}
// 子进程执行
void do_client_s(int accept_fd, sqlite3 *sql_db)
{
MSG msg;
while (recv(accept_fd, &msg, sizeof(MSG), 0) > 0) // receive request
{
printf("type = %d\n", msg.type); //选择
printf("data = %s\n", msg.data); //数据
switch (msg.type)
{
case R: // 1. 注册
do_register_s(accept_fd, &msg, sql_db);
break;
case L: // 2. 登录
do_login_s(accept_fd, &msg, sql_db);
break;
case Q: // 3. 查询
do_query_s(accept_fd, &msg, sql_db);
break;
case H: // 4. 历史记录
do_history_s(accept_fd, &msg, sql_db);
break;
}
}
printf("client quit\n");
exit(0);
return;
}
// 1. 注册
void do_register_s(int accept_fd, MSG *msg, sqlite3 *sql_db)
{
char sql_buf[512], *errmsg;
//使用sqlite3_exec函数调用插入函数判断是否能够插入成功
//由于用户名设置为主键,所以如果用户名已经存在就会报错
//大写,小写容易报错
sprintf(sql_buf, "INSERT INTO user VALUES('%s','%s')", msg->name, msg->data);
int ret = sqlite3_exec(sql_db, sql_buf, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf("error: %s\n", errmsg);
sprintf(msg->data, "用户名 %s 已经存在!!!", msg->name);
sqlite3_free(errmsg);//释放错误信息的空间 防止内存泄漏
}
else
{
strcpy(msg->data, "注册成功");
}
//发送数据
if (0 >= send(accept_fd, msg, sizeof(MSG), 0))
ERRLOG("send error");
return;
}
// 2. 登录
void do_login_s(int accept_fd, MSG *msg, sqlite3 *sql_db)
{
char sql_buf[512], *errmsg;
//使用查询函数判断是否存在
sprintf(sql_buf, "SELECT * FROM user WHERE name='%s' AND pass='%s'", msg->name, msg->data);
char **result; // 结果集
int row = 0; // 行数
int column = 0; // 列数
/*
会将 查询的到的结果的 行数写到row的地址里 列数写到column的地址里
让 result 指向 sqlite3_get_table 申请的结果集的内存空间
*/
//查询结果的函数 /结果集 /行数 /列数 /错误信息
int ret =sqlite3_get_table(sql_db, sql_buf, &result, &row, &column, &errmsg);
if (ret != SQLITE_OK)
{
printf("error: %s\n", errmsg);
sqlite3_free(errmsg);//释放错误信息的空间 防止内存泄漏
}
if (0==row) // 结果集没数据,行数row=0
{
sprintf(msg->data, "用户名[%s]与密码[%s]不一致", msg->name,msg->data);
}
else
{
strcpy(msg->data, "登录成功");
}
//发送数据
if (0 >= send(accept_fd, msg, sizeof(MSG), 0))
ERRLOG("send error");
return;
}
// 3. 查询
void do_query_s(int accept_fd, MSG *msg, sqlite3 *sql_db)
{
char sql_buf[512], *errmsg, *p;
char s[300], time_s[128];
int ret;
//定义文件指针
FILE *fp;
fp = fopen(WordFilename, "r"); // 打开字典
if (fp == NULL)
{
printf("create file error\n");
strcpy(msg->data, "dict can not open");
//发送数据
if (0 >= send(accept_fd, msg, sizeof(MSG), 0))
ERRLOG("send error");
}
char word[256]; //单词
strcpy(word, msg->data);
int len = strlen(msg->data);//单词长度
while (fgets(s, sizeof(s), fp) != NULL)
{
//比较单词
ret = strncmp(word, s, len);
if (ret == 0 && s[len] == ' ') //相等 且 下一位是空格
{
p = &s[len]; //指向第一个空格
while (*p == ' ')
{
p++;
} //跳到解释的第一个字母
strcpy(msg->data, p); //解释赋值
//解释发送
goto TOcli;
}
else
{
strcpy(msg->data, "不存在");
}
}
TOcli:
//存在-记录-查询单词成功
if (0 != strcmp(msg->data, "不存在"))
{
gettime(time_s); // 当前时间
//使用sqlite3_exec函数调用插入记录
sprintf(sql_buf, "INSERT INTO record VALUES('%s','%s','%s')", msg->name, time_s, word);
int ret = sqlite3_exec(sql_db, sql_buf, NULL, NULL, &errmsg);
if (ret != SQLITE_OK)
{
printf("error: %s\n", errmsg);
sqlite3_free(errmsg);//释放错误信息的空间 防止内存泄漏
}
}
//发送数据
if (0 >= send(accept_fd, msg, sizeof(MSG), 0))
ERRLOG("send error");
return;
}
//时间
int gettime(char *time_s)
{
time_t t;
time(&t); //秒钟数
struct tm *tp; // tm的结构体
tp = localtime(&t); //将秒钟转化为年月日时分秒的格式
sprintf(time_s, "%d-%d-%d %d:%d:%d", 1900 + tp->tm_year, 1 + tp->tm_mon, tp->tm_mday,
tp->tm_hour, tp->tm_min, tp->tm_sec);
}
// 4. 历史
void do_history_s(int accept_fd, MSG *msg, sqlite3 *sql_db)
{
char sql_buf[512], *errmsg;
//使用sqlite3_exec函数调用查询函数判断是否存在
sprintf(sql_buf, "SELECT * FROM record WHERE name='%s'", msg->name);
//-- /回调函数 /传参
int ret = sqlite3_exec(sql_db, sql_buf, callback, (void *)&accept_fd, &errmsg);
if (ret != SQLITE_OK)
{
printf("error: %s\n", errmsg);
sprintf(msg->data, "用户名 %s 无记录!!!", msg->name);
sqlite3_free(errmsg);//释放错误信息的空间 防止内存泄漏
}
//回复结束标志
strcpy(msg->data, "OVER");
//发送数据
if (0 >= send(accept_fd, msg, sizeof(MSG), 0))
ERRLOG("send error");
return;
}
// callback 回调函数
int callback(void *arg, int ncolumn, char **f_value, char **f_name)
{
//每查询到一条记录 这个函数就会被调用一次!!!!
MSG msg;
char buf[512], *errmsg;
int accept_fd = *(int *)arg; // 接收传递参数 强制类型转换
sprintf(msg.data, "%s | %s", f_value[1], f_value[2]); // 时间 | 单词 ;f_value[0]=用户名
//发送数据
if (0 >= send(accept_fd, &msg, sizeof(MSG), 0))
ERRLOG("send error");
return 0; //必须写返回值 否则报错
/*
// 指针类型报错
MSG *msg;
sprintf(msg->data, "%s | %s",f_value[1],f_value[2]);
*/
}
#include "tcp.h"
//注册
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[])
{
if (3 != argc)
{
printf("Usage : %s \n" , argv[0]);
exit(-1);
}
// 1.创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == sockfd)
{
ERRLOG("socket error");
}
// 2.填充服务器网络信息结构体
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
//端口号 填 8888 9999 6789 ...都可以
server_addr.sin_port = htons(atoi(argv[2]));
// ip地址 要么是当前Ubuntu主机的IP地址 或者
//如果本地测试的化 使用 127.0.0.1 也可以
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
//结构体长度
socklen_t server_addr_len = sizeof(server_addr);
//与服务器建立连接
if (-1 == connect(sockfd, (struct sockaddr *)&server_addr, server_addr_len))
{
ERRLOG("connect error");
exit(-1);
}
printf("---连接服务器成功---\n");
MSG msg;
int n;
while (1)
{
printf("************************************\n");
printf("* 1: 注册 2: 登录 3: 退出 *\n");
printf("************************************\n");
printf("please choose : ");
if (scanf("%d", &n) <= 0)
{
perror("scanf");
exit(-1);
}
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);
}
}
next:
while (1)
{
printf("************************************\n");
printf("* 1: 查询# 2: 历史记录 3: 退出 *\n");
printf("************************************\n");
printf("please choose : ");
if (scanf("%d", &n) <= 0)
{
perror("scanf");
exit(-1);
}
switch (n)
{
case 1: //查询
do_query(sockfd, &msg);
break;
case 2: //历史
do_history(sockfd, &msg);
break;
case 3:
close(sockfd);
exit(0);
}
}
//关闭套接字
close(sockfd);
return 0;
}
//注册
void do_register(int sockfd, MSG *msg)
{
//指定操作码
msg->type = R;
printf("输入用户名: ");
scanf("%s", msg->name);
printf("输入[%s] 密码: ", msg->name);
scanf("%s", msg->data);
//发送数据
if (0 >= send(sockfd, msg, sizeof(MSG), 0))
ERRLOG("send error");
//接收数据并输出
if (0 >= recv(sockfd, msg, sizeof(MSG), 0))
ERRLOG("send error");
printf("%s\n", msg->data);
return;
}
//登录
int do_login(int sockfd, MSG *msg)
{
//指定操作码
msg->type = L;
printf("输入用户名: ");
scanf("%s", msg->name);
printf("输入 [%s] 密码: ", msg->name);
scanf("%s", msg->data);
//发送数据
if (0 >= send(sockfd, msg, sizeof(MSG), 0))
ERRLOG("send error");
//接收数据并输出
if (0 >= recv(sockfd, msg, sizeof(MSG), 0))
ERRLOG("send error");
printf("%s\n", msg->data);
if (strcmp(msg->data, "登录成功") == 0)
{
return 1;
}
return 0;
}
//查询
void do_query(int sockfd, MSG *msg)
{
//指定操作码
msg->type = Q;
while (1)
{
printf("输入单词: ");
scanf("%s", msg->data);
if (strcmp(msg->data, "#") == 0)
{
break;
}
//发送数据
if (0 >= send(sockfd, msg, sizeof(MSG), 0))
ERRLOG("send error");
//接收数据并输出
if (0 >= recv(sockfd, msg, sizeof(MSG), 0))
ERRLOG("send error");
printf("%s\n", msg->data);
}
return;
}
//历史
void do_history(int sockfd, MSG *msg)
{
//指定操作码
msg->type = H;
//发送数据
if (0 >= send(sockfd, msg, sizeof(MSG), 0))
ERRLOG("send error");
while (1)
{
//接收数据并输出
if (0 >= recv(sockfd, msg, sizeof(MSG), 0))
ERRLOG("send error");
if (strcmp(msg->data, "OVER") == 0)
{
break;
}
printf("%s\n", msg->data);
}
return;
}
#ifndef __TCP_H__
#define __TCP_H__
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/*open*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//
#include
#define N 16
#define R 1 // user register注册
#define L 2 // user login登录
#define Q 3 // query word查询
#define H 4 // history record历史
#define FILEname "my1.db"
#define WordFilename "./yh.txt"
//信息结构体
typedef struct
{
int type; //选择
char name[N]; //用户名
char data[256]; // password密码 or word单词 or remark返回数据
} MSG;
#define ERRLOG(errmsg) \
do \
{ \
printf("%s--%s(%d):", __FILE__, __func__, __LINE__); \
perror(errmsg); \
} while (0)
/*---------------------服务器----------------*/
//系统初始化的函数//打开数据文件、尝试建表
sqlite3 *proc_init();
// 1. 注册
void do_register_s(int accept_fd, MSG *msg, sqlite3 *sql_db);
// 2. 登录
void do_login_s(int accept_fd, MSG *msg, sqlite3 *sql_db);
// 3. 查询
void do_query_s(int accept_fd, MSG *msg, sqlite3 *sql_db);
// 4. 历史记录
void do_history_s(int accept_fd, MSG *msg, sqlite3 *sql_db);
// 子进程执行
void do_client_s(int accept_fd, sqlite3 *sql_db);
#endif
指针类型报错
MSG *msg;
sprintf(msg->data, "%s | %s",f_value[1],f_value[2]);