LINUX 下 用C语言编写 TCP/IP通信的 sqlite3数据库服务器

2023-11-07

一、功能需求:

        我们首先明确一下,我们要制作的这个小服务器,需要具备什么功能

        1.1、用户的注册和登录

        使用sqlite3数据库,插入新的用户和查询用户的名字和密码是否匹配

        1.2、查询单词

        单词及其解释中,保存在一个文本文件当中。

        需要打开文件,在其中查询

        1.3用户查询记录

        用户每次查询单词都会将用户名,查询的单词,查询的时间保存在sqlite3表中

二、服务器端代码流程

        2.1打开数据库

    #define     DATABASE    "my.db"    
    sqlite3* db;

    /*打开数据库*/
    if(sqlite3_open(DATABASE, &db) != SQLITE_OK)
    {
        printf("%s\n" , sqlite3_errmsg(db));
    }
    else
    {
        printf("open %s success\n",DATABASE);
    }

        2.2、配置并开启tcp服务器

sockfd = socket(AF_INET,SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        perror("socket");
        return -1;
    }

    /*填写信息*/
    bzero(&serveraddr,sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);//本机所有网络接口都可以连接进来
    serveraddr.sin_port = htons(port);

    /*绑定*/
    if(bind(sockfd , (struct sockaddr *) &serveraddr, sizeof(serveraddr)) < 0)
    {
        perror("fail to bind.\n");
        return -1;
    }

    /*设置监听模式*/
    if(listen(sockfd , 5) < 0)
    {
        printf("Listen is fail.\n");
        return -1;

    }

    /*忽略子进程,能自动回收*/
    signal(SIGCHLD, SIG_IGN);

        2.3、等待客户端连接

    while (1)
    {
        /*
        accept 第一个参数是服务器对外的fd接口
        第二个参数是存放客户端数据的地址
        第三个参数是用来存放客户端数据大小的一个地址
        */
        if((acceptfd = accept(sockfd, NULL, NULL)) < 0)
        {
            perror("fail to accept.\n");
            return -1;
        }

        printf("client connect.\n");
        if((pid = fork()) < 0)
        {
            perror("fork fail.\n");
            return -1;
        }
        else if(pid == 0)//开辟一个子进程来处理与客户端的通信
        {
            close(sockfd);
            printf("go to client.\n");
            do_client(acceptfd, db);//调用客户端处理函数
        }
        else
        {
            close(acceptfd);//主程序中关闭这个客户端的连接
        }
    }
    return 0;
}

        2.4、根据客户端的请求标志位,去执行不同的功能函数

/**
 * @brief @客户端服务函数
 * 
 * @param acceptfd  客户端fd
 * @param db        数据库操作指针
 */
int do_client(int acceptfd,sqlite3* db)
{
    MSG msg;
    printf("In client hanshu.\n");
    while (recv(acceptfd , &msg, sizeof(MSG) ,0) > 0)
    {
        printf("type: %d\n",msg.type);
        switch(msg.type)
        {
            /*
                #define     R       1    //User - register
                #define     L       2    //User - Login
                #define     Q       3    //User - query
                #define     H       4    //User - history
                #define     root    5    //root - login
            */
        
            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("client exit.\n");
    close(acceptfd);
    exit(0);
}

       2.5、在sqlite3数据库中创建两个表

     

/*
    通过primary key 主键不可重复熟悉,避免用户名的重复
    text就是字符串  和char一样
*/
CREATE TABLE user(name text primary key, passwd text);

/*
    记录用户的查询记录
    用户名、查询时间 、 查询的单词
*/
CREATE TABLE record(name text, date text, word text);

         2.6、用户注册函数

        生成一个sql的命令字符串,用insert命令去插入新的用户数据

        用sqlite3_exec函数来执行命令语句,错误信息会用最后一个参数指向。如果出错就会返回一个小于0的数,那就代表着插入失败了

        中间两个NULL就是执行查询功能时的回调函数,以及要传给回调函数的参数

/**
 * @brief 用户注册函数
 * 
 * @param acceptfd 与客户端的连接接口
 * @param msg 
 * @param db 
 * @return int 
 */
int do_register(int acceptfd, MSG *msg,sqlite3* db)
{
    char sql[BufLen] = {};
    char *errmsg = NULL;
    
    sprintf(sql ,"insert into user values('%s' , '%s');",msg->name,msg->data);
    printf("%s\n" ,sql);
    /*执行插入命令*/
    if(sqlite3_exec(db, sql ,NULL ,NULL ,&errmsg) < 0)
    {
        printf("%s\n", errmsg);
        return -1;
    }
    else
    {
        printf("register succeeful.\n");
        bzero(msg->data,sizeof(msg->data));
        strcpy(msg->data,"OK");
    }

/*发送成功指令*/
    if(send(acceptfd, msg, sizeof(MSG), 0) < 0)
    {
        perror("send");
        return -1;
    }

    
    return 0;
}

        2.7、用户登录函数

        使用select命令,查询表中是否有用户名和密码都匹配的用户

        然后使用不带回调函数的sqlite3_get_table来进行查询,第一个函数就是数据库的指针,第二个是要执行的命令,第三个是一个三维数组,保存着所有的查询结果,第四个是一共查出多少行(相当于有多少个结果),第五个就是每一行对应的结果有多少个字段

        查询后直接判断nrow的值是不是1,是就代表查到匹配的用户,那么就可以回复客户端OK,代表登录成功

/**
 * @brief 进行用户登录
 * 
 * @param acceptfd 客户端套接字接口
 * @param msg 数据结构体
 * @param db 数据库指针
 * @return int succeed 0 ,err -1
 */
int do_login(int acceptfd,MSG *msg,sqlite3* db)
{
    char sql[BufLen] = {};
    int nrow;
    int ncolumn;
    char *errmsg;
    char **resultp;
    sprintf(sql ,"select * from user where name = '%s' and passwd = '%s';", msg->name, msg->data);
    printf("%s\n", sql);

    /*resultp是一个三维数组,nrow是行 相当于查询出多少条结果,ncolum是列 相当于有多少个字段*/
    sqlite3_get_table(db, sql, &resultp ,&nrow , &ncolumn , &errmsg);


    if(nrow == 1)
    {
        bzero(msg->data,sizeof(msg->data));
        strcpy(msg->data,"OK");
        printf("msg->data: %s\n", msg->data);
        printf("succeeful login in.\n");
        if(send(acceptfd , msg, sizeof(MSG) , 0) < 0)
        {
            perror("send");
            return -1;
        }

    }
    else
    {
        strcpy(msg->data,"NOTOK");
        printf("not ok.\n");
        if(send(acceptfd , msg, sizeof(MSG) , 0) < 0)
        {
            perror("send");
            return -1;
        }        
    }
    return 0;
}

        2.8查询单词

        通过两个函数来实现单词查询的功能

        一个是do_query函数,在客户端服务函数中被调用,然后调用found函数,通过found函数的返回值来判断单词是否被找到。然后对单词的查询进行记录

        单词查询的关键在于result = strncmp(buf ,word ,len);这个函数的返回值

        因为函数的返回值如果不为0,那么就代表着两个字符串并不相等

        一般是buf中的字符减去word中的字符

        单词在文件中存放的顺序是按ascii表中的值,从小到大排序的,所以如果匹配的返回值是0,并且后面跟的不是空格(代表只是别人单词的一部分),那也就代表着后面也不会再有匹配的单词,那么就可以直接中断查询。

        比如说  abg  减去 abc  互相减到第三个字母,返回值大于0  那么也就代表后面不会有abc了,后面单词的第三个字母的ascii值只会比现在这个abg大。

        abcd    减去  abc   abcd总体的ascii值比abc大  ,所以说到了abcd还没有找到匹配的单词,后面也不会有了。

        使用fgets函数,可以一行一行的读取文件中的字符串

        查询完之后就将时间等信息记录带另外一个表当中

/**
 * @brief 查询单词
 * 
 * @param acceptfd 
 * @param msg 
 * @param db 
 * @return int 
 */
int do_query(int acceptfd,MSG *msg,sqlite3* db)
{
    /*
    1、给客户端发送查询的结果
    2、在record.db中记录查询的时间和信息
    3、客户端发送过来的data放单词,name里面放用户名
    */
    char word[64] = {};
    int ret;
    char date[128];
    char sql[1024];
    char *errmsg;
    

    /*data是放在要查询的单词,和查询到的结果 ,name放的是用户名*/
    /*将名字复制到另外的数组中,避免互相影响*/
    strcpy(word, msg->data);

    ret = do_found(fileName,msg,word);
    printf("查询完毕!\n");
    /*结果放在msg->data当中*/

    if(ret == 1)
    {
        /*找到了*/
        /*将结果复制给*/
        
        /*获取时间*/
        do_gettime(date);
        printf("%s 找到了对应的注释.\n",word);
        printf("%s\n",date);

        /*插入记录*/
        sprintf(sql,"Insert into record values('%s','%s','%s');" , msg->name , date , word);
        printf("%s \n ", sql);
        if(sqlite3_exec(db , sql , NULL , NULL ,&errmsg) != SQLITE_OK)
        {
            printf("sqlite3_exec: %s \n" , errmsg);
        }

        
    }
    else
    {
        /*没找到*/
        printf("没找到.\n");
        strcpy(msg->data , "not found");
    }

    /*发送数据*/
    //strcpy(msg->data, word);
    if(send(acceptfd, msg, sizeof(MSG) , 0) < 0)
    {
        perror("send");
        return -1;
    }
    return 0; 
}

/**
 * @brief 在文件中查询单词
 * 
 * @param file 文件名字
 * @param msg 数据结构体
 * @param word 单词
 * @return int 0——没找到   1——找到了
 */
int do_found(char *file,MSG *msg,char *word)
{
    /*打开文件进行比对*/
    FILE *fp;
    char buf[512] = {};
    int len = 0;
    int result;
    char *p;

    if((fp = fopen(file, "r")) == NULL)
    {
        printf("open fail.\n");
        strcpy(msg->data , "fail to open file.");
        return 0;
    }

    len = strlen(word);
    printf("word: %s\n", word);
    int i = 0;
    /*成功打开*/
    //fgets读到文件末尾会返回NULL
    while(fgets(buf , 512 , fp) != NULL)
    {
        /*字典中的单词是按照字符数值的大小,从大到小排列的*/
        i++;
        
        result = strncmp(buf ,word ,len);
        printf("第%d次查询\n" , i);
        printf("result : %d len: %d \n",result,len);
        printf("buf : %sword : %s\n" , buf , word);
        if(result < 0)
        {
            
            bzero(buf , sizeof(buf));
            continue;
        }

        /*遇到比自己大的还没找到,匹配到前面一部分   代表着后面也不会找到了*/
        //result > 0 || ((result == 0) && buf[len] != ' ')
        if(result > 0 || (result == 0) && buf[len] != ' ')
        {
            printf("found 寻找结束\n");
            break;
        }
        


        p = buf + len;

        /*跳过空格阶段*/
        while (*p == ' ')
        {
            p++;
        }

        //将查询结果放在data中
        strcpy(msg->data, p);
        printf("found : %s\n", msg->data);

        /*关闭文件*/
        fclose(fp);
        return 1;
    }


    /*没找到*/
    fclose(fp);
    return 0;
}

        2.9查询用户的查找记录

        使用sqlite3_exec(db,sql,history_callback, (void *)msg, &errmsg)进行查询

       每查到一行表的数据,就会调用 history_callback函数,并且把查询的结果和msg作为参数传给history_callback。

        在查询函数中添加了一个root用户的识别,如果是root用户,那么就可以查询所有用户的记录

/**
 * @brief exec函数查询时的回调函数
 * 
 * @param arg 用户调用exec时要传给回调函数的参数
 * @param argc 数据一共有多少个字段
 * @param argv 保存查询结果的数组,每个元素对应一个字段的值
 * @param argv_name 保存字段名字的数组
 * @return int 
 */
static int history_callback(void *arg, int argc,char ** argv , char **argv_name)
{
    /*msg->type里面放了网络通信的fd*/
    MSG *msg = NULL;
    msg = (MSG *)arg;
    
    /*就打印时间和查询的单词*/
    if(msg->root_flag == 0)
    {
        sprintf(msg->data, "%s , %s",argv[1] , argv[2]);
        printf("type : %d  name : %s  data : %s" , msg->type , msg->name ,msg->data);
    }
    else
    {
        sprintf(msg->data, "%s , %s , %s",argv[0] ,argv[1] , argv[2]);
        printf("type : %d  name : %s  data : %s" , msg->type , msg->name ,msg->data);        
    }



    /*发送*/
    if(send(msg->type , msg , sizeof(MSG) , 0) < 0)
    {
        perror("send ");
        return -1;
    }

    return 0;

}


/**
 * @brief 查看用户历史记录
 * 
 * @param acceptfd 网络通信接口
 * @param msg 数据结构体
 * @param db 数据库指针
 * @return int 0--正常 1--出错了
 */
int do_history(int acceptfd,MSG *msg,sqlite3* db)
{
    char sql[128];//sql数据库执行语句存放的字符串
    char *errmsg;
    msg->type = acceptfd;//将通信接口fd也传给回调函数

    printf("name = '%s' \n" , msg->name);
    if(msg->root_flag == 0)
    {
        /*普通用户*/
        sprintf(sql, "select  * from record where name='%s';" , msg->name);
    }
    else
    {
        /*root用户*/
        sprintf(sql, "select  * from record;");
    }

    printf("sql : %s \nroot_flag : %d\n", sql,msg->root_flag);
    


    printf("sqlite3_exec\n");
    /*执行查询函数,并且在函数中将数据全部传递给客户端*/
    if(sqlite3_exec(db,sql,history_callback, (void *)msg, &errmsg) < 0)
    {
        printf("%s \n", errmsg);
        return 1;
    }

    printf("sqlite3_exec end.\n");

    /*数据全部发送完成之后,要发送第一个字符为空的数据,告诉客户端传输已经完成*/
    msg->data[0] = '\0';
    if(send(acceptfd , msg , sizeof(MSG) , 0) < 0)
    {
        perror("send");
        return 1;
    }


    return 0;

}

        2.10服务器端完整代码

        .c

#include "exchange.h"
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <sqlite3.h>
#include <signal.h>
#include <time.h>

#define     DATABASE    "my.db"
#define     BufLen      1024
#define     fileName    "dict.txt"


int do_client(int acceptfd,sqlite3* db);

 
int do_register(int , MSG *,sqlite3*);
int do_login(int ,MSG *,sqlite3* );
int do_query(int ,MSG *, sqlite3*);
int do_history(int ,MSG *, sqlite3*);
int do_root(int ,MSG *,sqlite3 *);
int do_found(char *file,MSG *msg,char *word);
void do_gettime(char *);


int main(int argc, char **argv)
{
    int sockfd;
    struct sockaddr_in serveraddr;
    int acceptfd;//接受服务器的fd
    pid_t pid;
    sqlite3* db;

    /*打开数据库*/
    if(sqlite3_open(DATABASE, &db) != SQLITE_OK)
    {
        printf("%s\n" , sqlite3_errmsg(db));
    }
    else
    {
        printf("open %s success\n",DATABASE);
    }


    sockfd = socket(AF_INET,SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        perror("socket");
        return -1;
    }

    /*填写信息*/
    bzero(&serveraddr,sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    serveraddr.sin_port = htons(port);

    /*绑定*/
    if(bind(sockfd , (struct sockaddr *) &serveraddr, sizeof(serveraddr)) < 0)
    {
        perror("fail to bind.\n");
        return -1;
    }

    /*设置监听模式*/
    if(listen(sockfd , 5) < 0)
    {
        printf("Listen is fail.\n");
        return -1;

    }

    /*忽略子进程*/
    signal(SIGCHLD, SIG_IGN);

    while (1)
    {
        /*
        accept 第一个参数是服务器对外的fd接口
        第二个参数是存放客户端数据的地址
        第三个参数是用来存放客户端数据大小的一个地址
        */
        if((acceptfd = accept(sockfd, NULL, NULL)) < 0)
        {
            perror("fail to accept.\n");
            return -1;
        }

        printf("client connect.\n");
        if((pid = fork()) < 0)
        {
            perror("fork fail.\n");
            return -1;
        }
        else if(pid == 0)//开辟一个子进程来处理与客户端的通信
        {
            close(sockfd);
            printf("go to client.\n");
            do_client(acceptfd, db);//调用客户端处理函数
        }
        else
        {
            close(acceptfd);//主程序中关闭这个客户端的连接
        }
    }
    return 0;
}


/**
 * @brief @客户端服务函数
 * 
 * @param acceptfd  客户端fd
 * @param db        数据库操作指针
 */
int do_client(int acceptfd,sqlite3* db)
{
    MSG msg;
    printf("In client hanshu.\n");
    while (recv(acceptfd , &msg, sizeof(MSG) ,0) > 0)
    {
        printf("type: %d\n",msg.type);
        switch(msg.type)
        {
            /*
                #define     R       1    //User - register
                #define     L       2    //User - Login
                #define     Q       3    //User - query
                #define     H       4    //User - history
                #define     root    5    //root - login
            */
        
            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("client exit.\n");
    close(acceptfd);
    exit(0);
}

/**
 * @brief 用户注册函数
 * 
 * @param acceptfd 与客户端的连接接口
 * @param msg 
 * @param db 
 * @return int 
 */
int do_register(int acceptfd, MSG *msg,sqlite3* db)
{
    char sql[BufLen] = {};
    char *errmsg = NULL;
    
    sprintf(sql ,"insert into user values('%s' , '%s');",msg->name,msg->data);
    printf("%s\n" ,sql);
    /*执行插入命令*/
    if(sqlite3_exec(db, sql ,NULL ,NULL ,&errmsg) < 0)
    {
        printf("%s\n", errmsg);
        return -1;
    }
    else
    {
        printf("register succeeful.\n");
        bzero(msg->data,sizeof(msg->data));
        strcpy(msg->data,"OK");
    }

/*发送成功指令*/
    if(send(acceptfd, msg, sizeof(MSG), 0) < 0)
    {
        perror("send");
        return -1;
    }

    
    return 0;
}


/**
 * @brief 进行用户登录
 * 
 * @param acceptfd 客户端套接字接口
 * @param msg 数据结构体
 * @param db 数据库指针
 * @return int succeed 0 ,err -1
 */
int do_login(int acceptfd,MSG *msg,sqlite3* db)
{
    char sql[BufLen] = {};
    int nrow;
    int ncolumn;
    char *errmsg;
    char **resultp;
    sprintf(sql ,"select * from user where name = '%s' and passwd = '%s';", msg->name, msg->data);
    printf("%s\n", sql);

    /*resultp是一个三维数组,nrow是行 相当于查询出多少条结果,ncolum是列 相当于有多少个字段*/
    sqlite3_get_table(db, sql, &resultp ,&nrow , &ncolumn , &errmsg);


    if(nrow == 1)
    {
        bzero(msg->data,sizeof(msg->data));
        strcpy(msg->data,"OK");
        printf("msg->data: %s\n", msg->data);
        printf("succeeful login in.\n");
        if(send(acceptfd , msg, sizeof(MSG) , 0) < 0)
        {
            perror("send");
            return -1;
        }

    }
    else
    {
        strcpy(msg->data,"NOTOK");
        printf("not ok.\n");
        if(send(acceptfd , msg, sizeof(MSG) , 0) < 0)
        {
            perror("send");
            return -1;
        }        
    }
    return 0;
}

/**
 * @brief 查询单词
 * 
 * @param acceptfd 
 * @param msg 
 * @param db 
 * @return int 
 */
int do_query(int acceptfd,MSG *msg,sqlite3* db)
{
    /*
    1、给客户端发送查询的结果
    2、在record.db中记录查询的时间和信息
    3、客户端发送过来的data放单词,name里面放用户名
    */
    char word[64] = {};
    int ret;
    char date[128];
    char sql[1024];
    char *errmsg;
    

    /*data是放在要查询的单词,和查询到的结果 ,name放的是用户名*/
    /*将名字复制到另外的数组中,避免互相影响*/
    strcpy(word, msg->data);

    ret = do_found(fileName,msg,word);
    printf("查询完毕!\n");
    /*结果放在msg->data当中*/

    if(ret == 1)
    {
        /*找到了*/
        /*将结果复制给*/
        
        /*获取时间*/
        do_gettime(date);
        printf("%s 找到了对应的注释.\n",word);
        printf("%s\n",date);

        /*插入记录*/
        sprintf(sql,"Insert into record values('%s','%s','%s');" , msg->name , date , word);
        printf("%s \n ", sql);
        if(sqlite3_exec(db , sql , NULL , NULL ,&errmsg) != SQLITE_OK)
        {
            printf("sqlite3_exec: %s \n" , errmsg);
        }

        
    }
    else
    {
        /*没找到*/
        printf("没找到.\n");
        strcpy(msg->data , "not found");
    }

    /*发送数据*/
    //strcpy(msg->data, word);
    if(send(acceptfd, msg, sizeof(MSG) , 0) < 0)
    {
        perror("send");
        return -1;
    }
    return 0; 
}

/**
 * @brief 在文件中查询单词
 * 
 * @param file 文件名字
 * @param msg 数据结构体
 * @param word 单词
 * @return int 0——没找到   1——找到了
 */
int do_found(char *file,MSG *msg,char *word)
{
    /*打开文件进行比对*/
    FILE *fp;
    char buf[512] = {};
    int len = 0;
    int result;
    char *p;

    if((fp = fopen(file, "r")) == NULL)
    {
        printf("open fail.\n");
        strcpy(msg->data , "fail to open file.");
        return 0;
    }

    len = strlen(word);
    printf("word: %s\n", word);
    int i = 0;
    /*成功打开*/
    //fgets读到文件末尾会返回NULL
    while(fgets(buf , 512 , fp) != NULL)
    {
        /*字典中的单词是按照字符数值的大小,从大到小排列的*/
        i++;
        
        result = strncmp(buf ,word ,len);
        printf("第%d次查询\n" , i);
        printf("result : %d len: %d \n",result,len);
        printf("buf : %sword : %s\n" , buf , word);
        if(result < 0)
        {
            
            bzero(buf , sizeof(buf));
            continue;
        }

        /*遇到比自己大的还没找到,匹配到前面一部分   代表着后面也不会找到了*/
        //result > 0 || ((result == 0) && buf[len] != ' ')
        if(result > 0 || (result == 0) && buf[len] != ' ')
        {
            printf("found 寻找结束\n");
            break;
        }
        


        p = buf + len;

        /*跳过空格阶段*/
        while (*p == ' ')
        {
            p++;
        }

        //将查询结果放在data中
        strcpy(msg->data, p);
        printf("found : %s\n", msg->data);

        /*关闭文件*/
        fclose(fp);
        return 1;
    }


    /*没找到*/
    fclose(fp);
    return 0;
}



/**
 * @brief exec函数查询时的回调函数
 * 
 * @param arg 用户调用exec时要传给回调函数的参数
 * @param argc 数据一共有多少个字段
 * @param argv 保存查询结果的数组,每个元素对应一个字段的值
 * @param argv_name 保存字段名字的数组
 * @return int 
 */
static int history_callback(void *arg, int argc,char ** argv , char **argv_name)
{
    /*msg->type里面放了网络通信的fd*/
    MSG *msg = NULL;
    msg = (MSG *)arg;
    
    /*就打印时间和查询的单词*/
    if(msg->root_flag == 0)
    {
        sprintf(msg->data, "%s , %s",argv[1] , argv[2]);
        printf("type : %d  name : %s  data : %s" , msg->type , msg->name ,msg->data);
    }
    else
    {
        sprintf(msg->data, "%s , %s , %s",argv[0] ,argv[1] , argv[2]);
        printf("type : %d  name : %s  data : %s" , msg->type , msg->name ,msg->data);        
    }



    /*发送*/
    if(send(msg->type , msg , sizeof(MSG) , 0) < 0)
    {
        perror("send ");
        return -1;
    }

    return 0;

}


/**
 * @brief 查看用户历史记录
 * 
 * @param acceptfd 网络通信接口
 * @param msg 数据结构体
 * @param db 数据库指针
 * @return int 0--正常 1--出错了
 */
int do_history(int acceptfd,MSG *msg,sqlite3* db)
{
    char sql[128];//sql数据库执行语句存放的字符串
    char *errmsg;
    msg->type = acceptfd;//将通信接口fd也传给回调函数

    printf("name = '%s' \n" , msg->name);
    if(msg->root_flag == 0)
    {
        /*普通用户*/
        sprintf(sql, "select  * from record where name='%s';" , msg->name);
    }
    else
    {
        /*root用户*/
        sprintf(sql, "select  * from record;");
    }

    printf("sql : %s \nroot_flag : %d\n", sql,msg->root_flag);
    


    printf("sqlite3_exec\n");
    /*执行查询函数,并且在函数中将数据全部传递给客户端*/
    if(sqlite3_exec(db,sql,history_callback, (void *)msg, &errmsg) < 0)
    {
        printf("%s \n", errmsg);
        return 1;
    }

    printf("sqlite3_exec end.\n");

    /*数据全部发送完成之后,要发送第一个字符为空的数据,告诉客户端传输已经完成*/
    msg->data[0] = '\0';
    if(send(acceptfd , msg , sizeof(MSG) , 0) < 0)
    {
        perror("send");
        return 1;
    }


    return 0;

}

/**
 * @brief 查看所有用户的记录
 * 
 * @param acceptfd 通信接口fd
 * @param msg 数据结构体
 * @param db 数据库指针
 * @return int 0正常    -1--出错
 */
int do_root(int acceptfd,MSG *msg,sqlite3* db)
{
    char sql[256] = {};

    bzero(sql , sizeof(sql));

    sprintf(sql, "select * from record;");

    
}


/**
 * @brief 将时间记录在传进来的字符数组里面
 * 
 * @param data 
 */
void do_gettime(char data[])
{
    time_t t;
    struct tm *tp;

    time(&t);

    tp = localtime(&t);
    bzero(data , 0);


    /*将数据生成在刚刚的字符串里面*/
    sprintf(data,"%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);
}

        .h

#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>


#define     datanum       32
#define     R       1    //User - register
#define     L       2    //User - Login
#define     Q       3    //User - query
#define     H       4    //User - history
#define     root    5    //root - login

#define     port    5001


/*通信双方信息结构体
两者需要保持一致
*/
typedef struct 
{
    int type;//类型
    int root_flag;
    char name[datanum]; //单词名字
    char data[256]; //单词翻译
}MSG;

三、客户端代码及流程

3.1、代码总览

客户端代码主要涉及和服务器通信,处理字符串判断服务器回复的数据

#include "exchange.h"




/*root login*/
int do_rootLogin(int socketfd,MSG *msg)
{
    bzero(msg,sizeof(MSG));
    msg->type = root;

    printf("Input root passwd:\n");
    scanf("%s",msg->data);
    getchar();


    if(send(socketfd,msg,sizeof(MSG), 0) < 0)
    {
        printf("fail to send\n");
        return -1;
    }

    /*接受数据直接放在这个msg结构体里面*/
    if(recv(socketfd , msg , sizeof(MSG), 0) < 0)
    {
        printf("fail to recv\n");
        return -1;
    }

    if(strncmp(msg->data ,"OK" , 3) == 0)
    {
        printf("root login...\n");
        return 1;
    }
    

    printf("%s\n",msg->data);
    return -1;
    

}

/*注册账号*/
int do_register(int sockfd,MSG *msg)
{
    printf("register ...\n");
    msg->type = R;

    printf("Input name:");
    scanf("%s",msg->name);
    getchar();

    printf("Input passwd:");
    scanf("%s",msg->data);
    getchar();

    if(send(sockfd,msg,sizeof(MSG),0) < 0)
    {
        printf("fail to send.\n");
        return -1;
    }
    printf("send.\n");

    if(recv(sockfd, msg ,sizeof(MSG), 0) < 0)
    {
        printf("fail to recv");
        return -1;
    }
    printf("recv.\n");

    printf("%s\n",msg->data);
    return 1;
}


/**
 * @brief 用户登录函数
 * 
 * @param socketfd 
 * @param msg 
 * @return int 
 * 发送和接受消息验证最好都放在一个函数里面去实现,通过返回值来判断是否成功
 */
int do_login(int socketfd,MSG *msg)
{
    printf("login ...\n");

    /*登录标记位*/
    
    bzero(msg,sizeof(MSG));
    msg->type = L;
    /*用户输入信息*/
    printf("Input name:\n");
    scanf("%s",msg->name);
    getchar();

    printf("Input passwd:\n");
    scanf("%s",msg->data);
    getchar();

    if(send(socketfd, msg, sizeof(MSG), 0) < 0)
    {
        printf("send fail.\n");
        return -1;
    }
    /*不要sizeof指针*/
    if(recv(socketfd, msg ,sizeof(MSG), 0) < 0)
    {
        printf("recv fail.\n");
        return -1;
    }

    /*验证服务器返回的信息*/
    /*strncmp 对比前n的字符,如果一样的话就会返回0*/
    /*返回OK代表可以*/
    
    if(strncmp(msg->data , "OK" ,3) == 0)
    {
        /*验证成功*/
        printf("Login ok\n");
        /*登录成功之后判断用户是不是root,如果是,标志位置1*/
        int ret;
        if((ret = strncmp(msg->name,"root" , 5)) == 0)
        {
            msg->root_flag = 1;
            printf("是root用户\n");
        }
        printf("ret : %d\n", ret);
        printf("name %s   \n",msg->name);
        return 1;
        
    }
    else
    {/*登录失败*/
        printf("Login not ok\n");
        printf("%s\n",msg->data);
        return -1;
    }


    printf("msg->data :%s\n",msg->data);
    return -1;
}

/*
    查询单词函数
*/
int do_query(int scoketfd,MSG *msg)
{
    printf("query... \n");
    msg->type = Q;
    puts("--------------------");

    while (1)
    {
        printf("Input word\n");
        scanf("%s",msg->data);
        getchar();

        /*如果用户输入#代表退出*/
        if(strncmp(msg->data,"#",1) == 0)
        {
            break;
        }

        if(send(scoketfd, msg ,sizeof(MSG) ,0) < 0)
        {
            printf("send fail.\n");
            return -1;
        }
        /*等待服务器传递回来的数据,然后直接打印就好了*/
        if(recv(scoketfd , msg, sizeof(MSG), 0) < 0)
        {
            printf("recv fail.\n");
            return -1;
        }
        printf("%s\n",msg->data);
    }
    return 0;
}

int do_history(int scoketfd,MSG *msg)
{
    printf("history ...\n");

    /*1、先发送查询信息*/
    msg->type = H;
    printf("root_flag : %d\n", msg->root_flag);

    if(send(scoketfd, msg, sizeof(MSG), 0) < 0)
    {
        printf("send fail.\n");
        return -1;
    }

    /*将接收到的数据都显示出来 ,指导收到空的字符串,代表结束*/
    while (1)
    {
        recv(scoketfd, msg, sizeof(MSG), 0);

        /*收到\0 就代表着发送完成*/
        if(msg->data[0] == '\0')
        {
            break;
        }
        else
        {
            printf("%s\n", msg->data);

        }
        
    }
    return 0;
}

//   server 192.168.137.87
int main(int argc ,char **argv)
{
    if(argc != 3)
    {
        printf("Usage : %s server  port \n",argv[0]);
        return -1;
    }

    int socketfd;
    struct sockaddr_in serveraddr;
    int n;
    MSG msg;
    /*创建套接字,连接服务器*/
    if((socketfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)//第三个参数选0,类型会自动匹配第二个参数的类型
    {
        perror("fail to socket \n");
        return -1;
    }

    bzero(&serveraddr,sizeof(serveraddr));
    /*填充结构体信息*/
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[2]));//转换为网络字节
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);//将点分转换成网络字节序

    /*连接服务器*/
    if(connect(socketfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr)) == -1)
    {
        perror("fail to connect");
        return -1;
    }


    /*进入一级菜单*/
    while (1)
    {
        printf("************************************************\n");
        printf("1.register    2.login      3.root login  4.quit \n");
        printf("************************************************\n");
        printf("please choose:\n");

        scanf("%d",&n);
        getchar();

        switch (n)
        {
        case 1:
            do_register(socketfd,&msg);
            break;
        case 2:
            if(do_login(socketfd,&msg) == 1)
            {
                goto next;
            }
            break;
        case 3:
            if(do_rootLogin(socketfd,&msg) == 1)
            {
                goto ROOT;
            }
            break;
        case 4:
            close(socketfd);
            exit(0);
            break;
        
        default:
            printf("Invalid data cmd.\n");
            break;
        }
    }
    
next:
    while (1)
    {
        printf("************************************************\n");
        printf("1.query_word       2.history_record      3. quit\n");
        printf("************************************************\n");
        printf("please choose:\n");

        scanf("%d",&n);
        getchar();

        switch(n)
        {
            case 1:
                do_query(socketfd,&msg);
                break;
            case 2:
                do_history(socketfd, &msg);
                break;
            case 3:
                close(socketfd);
                exit(0);
                break;
            default:
                printf("Invalid data cmd.\n");
                break;

        }


        
    }


ROOT:
    while (1)
    {
        printf("************************************************\n");
        printf("1.query_word       2.history_record      3. quit\n");
        printf("************************************************\n");
        printf("please choose:\n");



    }
    
    



    return 0;
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

LINUX 下 用C语言编写 TCP/IP通信的 sqlite3数据库服务器 的相关文章

随机推荐

  • er图的主键外键_卓象科技:MYSQL外键的优缺点

    MYSQL外键是什么 定义 外键是相对于主键说的 是建立表之间联系的必须的前提 例如 这里有两张 user 用户 表和qx 权限 表 user中gid是用户权限id 而gid是依赖于qx中的id 那么qx中的id就是user的外键 也就是当
  • Java增强for循环(学习笔记)

    Java增强for循环 主要用于数组或者集合的增强型for循环 格式 for 声明语句 表达式 代码句子 声明语句 声明新的局部变量 该变量的类型必须和数组元素的类型匹配 其作用域限定在循环语句块 其值与此时数组元素的值相等 表达式 表达式
  • pycharm配置python路径_pycharm如何配置python环境

    pycharm配置python环境的方法是 1 依次点击 File Project Interpreter 2 点击 Show All 选择 Existing Environment 3 选择python的安装路径 点击OK即可 配置方法
  • 安装secureCRT提示sorry的解决办法

    摘自 你的secureCRT还在sorry吗 作者 丶PURSUING 发布时间 2021 03 12 08 21 37 网址 https blog csdn net weixin 44742824 article details 1146
  • WinCE5.0中文模拟器SDK(VS2005)的配置

    WinCE5 0中文模拟器SDK的安装过程不细说了 一路默认即可 下面主要介绍如何配置 使其能在VS2005中正常使用 安装完成后 打开VS2005 点击菜单 工具 选项 设备工具 设备 选择 Windows CE 5 0 ARMV4I E
  • Keil报错:Libraries\CMSIS\stm32f10x.h(298): error: #67: expected a "}"

    原因主要有三点 启动文件 头文件定义 驱动选择不一致 各项如下 1 启动文件 2 头文件定义 3 驱动选择 会导致报错的情况案例 1 启动文件为startup stm32f10x md s C C 的Define为 STM32F10X HD
  • moose安装过程中遇到问题及解决方案

    问题 curl 56 OpenSSL SSL read error 0A000126 SSL routines unexpected eof while reading errno 104 解决方案 未使用vpn 下载速度慢 可多次执行命令
  • [Unity3d]3D项目转换为VR项目(暴风魔镜SDK)

    使用暴风魔镜SDK来操作 将魔镜的摄像头拖放到项目中 将MoJingVrHead的Script剪切到CamRoot中 这个时候能看到显示2个物体了 不过使用的Canvas还是显示一个 调整Canvas的属性 使其显示2份 步骤一 将Rend
  • Linux下杀死指定命令进程

    ps grep cat awk F print 1 xargs kill 9 执行如下 在网上搜到其他不一样的方式 也在此贴一下 https www jianshu com p 80b141746fae
  • 深入理解JS闭包

    关于JS中闭包的理解 相信很多人都和笔者一样刚开始很是困惑 笔者也是在看了很多前辈的文章后 总结出一点自己的理解 记录与此 囿于笔者水平有限 若有错误之处 恳请不啬赐教 你可以在一个函数里面嵌套另外一个函数 嵌套 内部 函数对其容器 外部
  • Windows电脑怎么设置局域网内共享磁盘?

    前言 我有一台主机硬盘容量比较大 想做为一个共享硬盘 方便我其他笔记本能够往这台硬盘传输文件 想到的最好最快的方法就是通过局域网内部进行文件传输 通过局域网共享磁盘 这种方法也是非常便捷的 那如何设置操作呢 请详细看下文 局域网共享磁盘 共
  • 用QEMU虚拟国产飞腾+麒麟环境

    1 简述 由于调试 测试需要飞腾主机及麒麟的环境 但是飞腾主机资源有限 于是便尝试了下在Qemu下虚拟出来一个ARM主机用来作为测试环境 本文介绍如何在Qemu虚拟的ARM环境下安装麒麟操作系统 2 安装过程 2 1 准备 本次安装需要准备
  • 什么是分布式系统?

    分布式系统是由多个独立的计算机或计算节点组成的系统 这些节点通过消息传递或共享数据的方式进行协调和通信 以实现共同的目标 分布式系统的设计目标是提高系统的可靠性 可扩展性 性能和容错性 在一个分布式系统中 各个计算机节点之间相互合作 共同完
  • .NET Framework简介

    1 什么是 NET Framework NET Framework 是支持生成 运行下一代应用程序和XML Web Services的内部Windows组件 它简化了在高度分布式Internet环境中的应用程序开发 NET Framewor
  • python之数值类型数据及运算

    数据类型 数据类型分为 字符串 str 整型 int 浮点型 float 负数 complex 布尔型 bool 一 字符串 1双引号 单引号括起来的 2双引号开头 结尾 xxx 3单引号开通 结尾 xxx 4不能一边单一边双 5多行字符串
  • iOS 为app生成下载链接,并生成二维码

    1 打开这个网址 http aso100 com 在此处输入app名称 点搜索 2 看 第一个就是我们的app 下一步点击图标 3 点击app id 4 看连接出来了 5 最后到这个网站生成二维码 http 2bai com cn hao2
  • Vue2中使用高德地图(Loader )

    1 需求 根据输入的地址 地图显示地址的位置 2 准备工作 2 1 注册高德开放平台账户 并完成认证 根据具体实际情况 完成个人开发或者企业开发认证 高德开放平台https console amap com 2 2在应用管理 我的应用中添加
  • 斗地主老输?只能领低保?看我用Python写一个AI出牌器!现在一亿欢乐豆了!

    前言 最近在网上看到一个有意思的开源项目 快手团队开发的开源AI斗地主 DouZero 今天我们就一起来学习制作一个基于DouZero的欢乐斗地主出牌器 看看AI是如何来帮助我们斗地主 赢欢乐豆 实现财富自由的吧 首先一起来看看AI斗地主出
  • View那些事儿(1) -- View绘制的整体流程

    写在开头 Android的知识体系十分庞大 在Android的学习道路上难免会遇到学习了新东西就忘了旧东西的情况 本系列文章主要是对自己对View的学习过程进行一个深入的理解与总结 当然还结合自己在实际项目中的一些体会写了一些东西 当用户打
  • LINUX 下 用C语言编写 TCP/IP通信的 sqlite3数据库服务器

    一 功能需求 我们首先明确一下 我们要制作的这个小服务器 需要具备什么功能 1 1 用户的注册和登录 使用sqlite3数据库 插入新的用户和查询用户的名字和密码是否匹配 1 2 查询单词 单词及其解释中 保存在一个文本文件当中 需要打开文