基于SQLite数据库的多进程实现TCP并发-在线词典

目录

  • 基于SQLite数据库的多进程实现TCP并发-在线词典
    • 错误信息获取
    • 要求
    • 字典
    • 代码实现
      • 服务器---01server.c
      • 客户端---02client.c
      • tcp.h
      • 执行结果
    • 注意

基于SQLite数据库的多进程实现TCP并发-在线词典

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

代码实现

服务器—01server.c

#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]);
    */
}

客户端—02client.c

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

tcp.h

#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

执行结果

基于SQLite数据库的多进程实现TCP并发-在线词典_第1张图片

  • 数据库

基于SQLite数据库的多进程实现TCP并发-在线词典_第2张图片

注意

  1. 服务器侧—callback 回调函数
指针类型报错
	MSG *msg;
	sprintf(msg->data, "%s | %s",f_value[1],f_value[2]);

你可能感兴趣的:(SQLite,sqlite,数据库,tcp/ip)