云词典(基于TCP、IO多路复用、sqlite3数据库)

将单词文本文件导入数据库表中
https://editor.csdn.net/md/?articleId=131514974
该项目代码文件:
https://download.csdn.net/download/m0_52857070/87989479

一、项目运行效果展示

1、编译

1.1 服务器端:

在这里插入图片描述

1.2 客户端

在这里插入图片描述

2、客户端

2.1 注册register

在这里插入图片描述

2.2 登录login

在这里插入图片描述

2.3 单词查询word(输入wordend退出单词查询)

在这里插入图片描述

2.4 历史查询myhistory

在这里插入图片描述

2.5 退出查询界面

在这里插入图片描述

2.6 退出quit

在这里插入图片描述

3、服务器端

在这里插入图片描述

二、项目实现流程

1、服务器端

1.1 服务端整体流程图

在这里插入图片描述

1.2 服务器端注册流程图

在这里插入图片描述

1.3 服务器端登录流程图

在这里插入图片描述

2、客户端

2.1客户端流程图
在这里插入图片描述

三、代码分析

1、客户端

第一步:创建套接字
第二步:填充服务器端结构体
第三步:连接服务器
第四步:循环输入命令
如果是注册命令,将注册命令发送给服务器,调用注册函数
如果是登录命令,将登录命令发送给服务器,调用登录函数
如果是退出命令,则将退出命令发送给服务器,break跳出死循环,结束进程

1.1 main函数

第一步:

int main(int argc, char const *argv[])
{
    COMMUN_T com;
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket err");
        return -1;
    }
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[2]));
    saddr.sin_addr.s_addr = inet_addr(argv[1]);

    if (connect(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
    {
        perror("connect err");
        return -1;
    }
    printf("connect ok\n");

    while (1)
    {
        printf("\n请输入命令:注册(register),登录(login),退出(quit):");
        fgets(com.type, sizeof(com.type), stdin);
        if (com.type[strlen(com.type) - 1] == '\n')
            com.type[strlen(com.type) - 1] = '\0';
        if (strcmp(com.type, "register") == 0)
        {
            //printf("register\n");
            send(sockfd, &com, sizeof(com), 0);
            myregister(sockfd);
        }
        else if (strcmp(com.type, "login") == 0)
        {
            //printf("login\n");
            send(sockfd, &com, sizeof(com), 0);
            mylogin(sockfd);
        }
        else if (strcmp(com.type, "quit") == 0)
        {
            //printf("quit\n");
            send(sockfd, &com, sizeof(com), 0);

            break;
        }
        else
        {
            printf("输入的命令有误\n");
        }
    }
    close(sockfd);
    return 0;
}

1.2 注册函数

第一步:获取用户名与密码
第二步:将用户名与密码结构体发送给服务器端
第三步:等待接收服务器端发送过来的注册状态
如果收到registersuccess,则注册成功,打印“注册成功”,退出
如果收到registered,则证明已注册,打印“已经注册过”,退出

void myregister(int sockfd)
{
    //printf("myregister\n");
    char buf[320];
    USER_T myuser;
    while (1)
    {
        printf("请输入用户名: ");
        fgets(myuser.name, sizeof(myuser.name), stdin);
        if (myuser.name[strlen(myuser.name) - 1] == '\n')
            myuser.name[strlen(myuser.name) - 1] = '\0';
        
        printf("请输入密码: ");
        fgets(myuser.passwd, sizeof(myuser.passwd), stdin);
        if (myuser.passwd[strlen(myuser.passwd) - 1] == '\n')
            myuser.passwd[strlen(myuser.passwd) - 1] = '\0';
        send(sockfd, &myuser, sizeof(myuser), 0);
        recv(sockfd, buf, sizeof(buf), 0);
        //printf("buf:%s\n", buf);
        if (strcmp(buf, "registersuccess") == 0)
        {
            printf("注册成功\n");
            break;
        }
        else if (strcmp(buf, "registered") == 0)
        {
            printf("已经注册过\n");
            break;
        }
        else
        {
            printf("其他未知错误\n");
        }
    }
}

1.3 登录函数

第一步:获取用户名与密码
第二步:发送给服务器
第三步:等待接收服务器发送过来的登录状态
1)如果收到loginsuccess,说明登录成功,跳出本循环
2)如果收到noregister,说明该用户还没有注册,函数返回
3)如果收到passwderr,说明密码错误,继续输入用户名与密码
第四步:如果登录成功,进入查询界面循环
第五步:输入那种查询方式
1)单词查询word ,则进入单词查询界面,获取要查询的单词,并发送给服务器,等待接收服务器发送过来的单词解释
2)如果历史查询myhistory,进入历史查询界面,接收服务器端发送过来的该用户历史查询记录,直到收到historyend,结束循环接收,退出历史查询
第六步:如果输入#,退出查询界面

void mylogin(int sockfd)
{
    //printf("mylogin\n");
    char buf[320];
    USER_T myuser;
    COMMUN_T com;
    HISTORY_T his_list;
    while (1)
    {
        printf("请输入用户名: ");
        fgets(myuser.name, sizeof(myuser.name), stdin);
        if (myuser.name[strlen(myuser.name) - 1] == '\n')
            myuser.name[strlen(myuser.name) - 1] = '\0';
        
        printf("请输入密码: ");
        fgets(myuser.passwd, sizeof(myuser.passwd), stdin);
        if (myuser.passwd[strlen(myuser.passwd) - 1] == '\n')
            myuser.passwd[strlen(myuser.passwd) - 1] = '\0';
        send(sockfd, &myuser, sizeof(myuser), 0);
        recv(sockfd, buf, sizeof(buf), 0);
        // printf("buf:%s\n", buf);
        if (strcmp(buf, "loginsuccess") == 0)
        {
            printf("登录成功\n");
            break;
        }
        else if (strcmp(buf, "passwderr") == 0)
        {
            printf("密码错误\n");
        }
        else if (strcmp(buf, "noregister") == 0)
        {
            printf("没有注册\n");
            return;
        }
    }
    while (1)
    {
        printf("\n请输入采取那种查询方式:单词查询(word),历史查询(myhistory) 退出查询界面(#):");
        fgets(com.type, sizeof(com.type), stdin);
        if (com.type[strlen(com.type) - 1] == '\n')
            com.type[strlen(com.type) - 1] = '\0';
        // printf("请输入用户名:");
        // fgets(com.name, sizeof(com.name), stdin);
        // if (com.name[strlen(com.name) - 1] == '\n')
        //     com.name[strlen(com.name) - 1] = '\0';
        send(sockfd, &com, sizeof(com), 0);

        //单词查询
        if (strcmp(com.type, "word") == 0)
        {
            // printf("word\n");
            while (1)
            {
                printf("\n请输入要查询的单词\n");
                fgets(com.data, sizeof(com.data), stdin);
                if (com.data[strlen(com.data) - 1] == '\n')
                    com.data[strlen(com.data) - 1] = '\0';
                send(sockfd, &com, sizeof(com), 0);
                if (strcmp(com.data, "wordend") == 0)
                    break;
                recv(sockfd, buf, sizeof(buf), 0);
                printf("单词解释:%s\n", buf);
            }
        }
        //历史查询
        else if (strcmp(com.type, "myhistory") == 0)
        {
            // printf("history\n");
            while (1)
            {
                recv(sockfd, &his_list, sizeof(his_list), 0);

                if (strcmp(his_list.word, "historyend") == 0)
                    break;
                printf("username:%s time: %s word: %s \n", his_list.name, his_list.usertime, his_list.word);
            }
            // printf("historyok\n");
        }
       
        else if(strcmp(com.type,"#")==0)
            break;
        else
        {
            printf("输入的命令有误\n");
        }
        

    }

   
}

2、服务器端

2.1 main函数

第一步:创建数据库
第二步:创建用户注册表、历史查询记录表、单词表(采用另外的程序添加进入的)
第三步:创建套接字
第四步:填充服务器端结构体
第五步:绑定
第六步:监听
第七步:捕捉ctrl+c信号,关闭本进程打开的所有文件、数据库
第八步:创建IO多路复用表
1)如果有连接请求,则调用accept函数,与客户端建立连接,并将生成的通信文件描述符添加到IO多路服用表
2)如果有通信请求,接收客户端发送过来的命令,
如果是注册命令,则调用注册函数
如果是登录命令,则调用登录函数,
如果接收到的是退出命令,则关闭与该客户端同学的文件描述符,并把它从IO多路复用表中删除

int main(int argc, char const *argv[])
{

    COMMUN_T com;
    //创建库

    if (sqlite3_open("./CloudDictionary.db", &db) != 0)
    {
        fprintf(stderr, "sqlite3_open err:%s\n", sqlite3_errmsg(db));
    }
    //创建注册表\历史查询表
    char *errmsg = NULL;
    if (sqlite3_exec(db, "create table user(name char primary key,passwd char);", NULL, NULL, &errmsg) != 0)
    {
        fprintf(stderr, "sqlite3_exec err:%s\n", sqlite3_errmsg(db));
    }

    if (sqlite3_exec(db, "create table historySearch(name char,time char,word char);", NULL, NULL, &errmsg) != 0)
    {
        fprintf(stderr, "sqlite3_exec err:%s\n", sqlite3_errmsg(db));
    }
    //打开字典文件

    //tcp 通信
    sockfd = socket(AF_INET, SOCK_STREAM, 0); // 要放到创建数据库前面,数据库会导致acceptfd从6开始
    if (sockfd < 0)
    {
        perror("socket err");
        return -1;
    }
    printf("sockfd:%d\n", sockfd);
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[1]));
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0");

    if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
    {
        perror("bind err");
        return -1;
    }
    if (listen(sockfd, 8) < 0)
    {
        perror("listen err");
        return -1;
    }
    signal(SIGINT, hander); //捕捉ctrl+c信号

    FD_ZERO(&readfds);
    // FD_SET(0, &readfds);
    FD_SET(sockfd, &readfds);
    int maxfd = sockfd;
    while (1)
    {
        tempfds = readfds;
        int ret = select(maxfd + 1, &tempfds, NULL, NULL, NULL);
        if (ret < 0)
        {
            perror("select err");
            return -1;
        }
        if (FD_ISSET(sockfd, &tempfds))
        {
            int acceptfd = accept(sockfd, NULL, NULL);
            if (acceptfd < 0)
            {
                perror("acceptfd err");
                return -1;
            }
            printf("acceptfd:%d\n", acceptfd);
            FD_SET(acceptfd, &readfds);
            if (maxfd < acceptfd)
                maxfd = acceptfd;
        }
        for (int i = sockfd+1; i <= maxfd; i++) //acceptfd从6开始分配的,要是不合适的会报找不到传输节点错误
        {
            if (FD_ISSET(i, &tempfds))
            {
                int retebyte = recv(i, &com, sizeof(com), 0);
                if (retebyte < 0)
                {
                    perror("recv err");
                    return -1;
                }
                else if (retebyte == 0)
                {
                    close(i);
                    FD_CLR(i, &readfds);
                    if (maxfd == i)
                        maxfd--;
                }

                else
                {
                    if (strcmp(com.type, "register") == 0)
                    {
                        // printf("register\n");
                        myregister(i);
                    }
                    else if (strcmp(com.type, "login") == 0)
                    {
                        // printf("login\n");
                        mylogin(i);
                    }
                    else if (strcmp(com.type, "quit") == 0)
                    {
                        printf("%d 客户端退出\n", i);
                        close(i);
                        FD_CLR(i, &readfds);
                        if (maxfd == i)
                            maxfd--;
                        continue;
                    }
                }
            }
        }
    }
    close(sockfd);

    return 0;
}

2.2 注册函数

第一步:接收客户端发送过来的用户名和密码结构体
第二步:判断该用户名在数据库中是否存在
如果存在,给客户端发送registered,跳出循环
如果不存在,插入成功的话,会给客户端发送registersuccess,跳出循环
注意:
1)这里我在创建用户注册表时,把用户名当成了关键字,如果注册的用户名存在,则系统会报“UNIQUE constraint failed: user.name”,根据这个来判断是否已经注册
2)如果errmsg==NULL的话,说明成功插入,这个需要这样判断,因为NULL不能用strcmp与任何字符串判断,会报段错误

//1\注册
void myregister(int acceptfd)
{
    //printf("myregister\n");
    char buf[320];
    USER_T myuser;
    char sql[320];

    while (1)
    {
        int retebyte = recv(acceptfd, &myuser, sizeof(myuser), 0);
        if (retebyte < 0)
        {
            perror("recv err");
            return;
        }
        else if (retebyte > 0)
        {
            printf("用户%s注册\n", myuser.name);
            errmsg = NULL;
            sprintf(sql, "insert into user values(\"%s\",\"%s\");", myuser.name, myuser.passwd);
            //printf("sql:%s\n", sql);

            if (sqlite3_exec(db, sql, NULL, NULL, &errmsg) != 0)
            {
                fprintf(stderr, "sqlite3_exec err:%s\n", errmsg);

                //  return;
            }
            // printf("----------------errmsg:%s----------------\n", errmsg);
            if (NULL == errmsg)
            {
                strcpy(buf, "registersuccess");
                printf("%s  用户注册成功\n\n", myuser.name);
                send(acceptfd, buf, sizeof(buf), 0);
                // printf("----------------------bbbbbbb----------\n");
                break;
            }
            else
            {
                if (strcmp(errmsg, "UNIQUE constraint failed: user.name") == 0) //errmsg没有错误时为NULL,与字符串比较出现段错误
                {
                    //printf("rerf\n");
                    strcpy(buf, "registered");
                    send(acceptfd, buf, sizeof(buf), 0);
                    //  printf("--------------------------------aaaaa\n");
                    break;
                }
            }
        }
    }
}

2.3 登录函数

第一步:接收客户端发送过来的用户名与密码结构体
第二步:去数据库用户注册表中查找该用户名,
1)如果返回的行数=1的话,说明该用户已经注册过。再进一步判断密码是否正确。
如果正确的话,则登录成功,给客户端发送“loginsuccess”,跳出这个循环。
如果错误的话,会给客户端发送“passwderr”,继续接收客户端发送过来的用户名与密码
2)如果返回的行数!=1的话,行数返回只能是1或0(用户名是关键字,一个用户名最多一行),说明该用户还没有注册。给客户端发送“noregister”,函数返回。
第三步:如果登录成功,则进入查询界面
第四步:接收客户端发送过来的查询命令
1)如果是单词查询,则接收客户端发送过来的单词,利用函数sqlite3_get_table()去数据库单词表中查找该单词,找到后,把单词意思发送给客户端,并利用sqlite3_exec()把该用户查询记录(用户名、查询时间、查询单词)插入到历史查询记录表中。
如果接收到“wordend”,则结束单词查询
将单词文本文件导入数据库表中
https://editor.csdn.net/md/?articleId=131514974

2)如果是历史查询,则去查找该用户名的历史查询记录表,并把查询记录逐条发送给客户端。发送结束后,发送结束发送标志“historyend”给客户端。
3)如果接收到#,则break跳出这个死循环,退出查询界面

//2\登录
void mylogin(int acceptfd)
{
    //printf("mylogin\n");

    char buf[320];
    USER_T myuser;
    COMMUN_T com;

    char sql[320];
    char **resultp; //在get_table函数中是一维指针数组,数组首地址是二级指针
    int nrow;       //在get_table函数里自动获取到符合条件的行数
    int ncolumn;    // 在get_table函数里自动获取到符合条件的列数
    int k = 0;
    int retebyte;

    time_t ti;
    struct tm loc_time;
    struct tm *my_loctime = &loc_time;
    char mytime[256];
    HISTORY_T his_list;
    while (1)
    {
        retebyte = recv(acceptfd, &myuser, sizeof(myuser), 0);
        if (retebyte < 0)
        {
            perror("recv err");
            return;
        }
        else if (retebyte > 0)
        {
            
            printf("用户%s登录\n", myuser.name);
            k = 0; //k登录一次后k=3 ,在自加resultp数组越界,需要重新置0
            //1\登录
            sprintf(sql, "select * from user where name=\"%s\";", myuser.name);
            //printf("sql:%s\n", sql);
            if (sqlite3_get_table(db, sql, &resultp, &nrow, &ncolumn, &errmsg) != 0)
            {
                fprintf(stderr, "select err:%s", errmsg);
                return;
            }
            //printf("nrow:%d\n", nrow);
            // for (int i = 0; i < nrow + 1; i++)
            // {
            //     for (int j = 0; j < ncolumn; j++)
            //     {
            //         printf("%s ", resultp[k++]);
            //     }
            //     putchar(10);
            // }

            if (nrow == 1)
            {
                if (strcmp(resultp[3], myuser.passwd) == 0)
                {
                    //需要加\n刷新缓存区
                    printf("%s  用户登录成功\n\n", myuser.name);
                    strcpy(buf, "loginsuccess");
                    send(acceptfd, buf, sizeof(buf), 0);
                    break;
                }
                else
                {
                    //printf("user name:%s passwderr\n", myuser.name);
                    strcpy(buf, "passwderr");
                    send(acceptfd, buf, sizeof(buf), 0);
                }
            }
            else if (nrow == 0)
            {
                // printf("user name:%s noregister\n", myuser.name);
                strcpy(buf, "noregister");
                send(acceptfd, buf, sizeof(buf), 0);
                return;
            }
        }
    }
    while (1)
    {
        //2\查询方式
        retebyte = recv(acceptfd, &com, sizeof(com), 0);
        if (retebyte < 0)
        {
            perror("recv err");
            return;
        }
        else if (retebyte > 0)
        {
            if (strcmp(com.type, "word") == 0)
            {
                //printf("word\n");
                while (1)
                {
            //3\单词查询
                    retebyte = recv(acceptfd, &com, sizeof(com), 0);
                    if (retebyte < 0)
                    {
                        perror("recv err");
                        return;
                    }
                    else if (retebyte > 0)
                    {
                        printf("用户%s单词查询\n\n", myuser.name);
                        printf("单词查询中\n");
                        if (strcmp(com.data, "wordend") == 0)
                            break;
                        bzero(sql, sizeof(sql));
                        k = 0;
                        nrow = 0;
                        ncolumn = 0;
                        sprintf(sql, "select * from dictxt where a=\"%s\";", com.data);
                        //printf("sql:%s\n", sql);
                        if (sqlite3_get_table(db, sql, &resultp, &nrow, &ncolumn, &errmsg) != 0)
                        {
                            fprintf(stderr, "select err:%s", errmsg);
                            return;
                        }
                        // printf("nrow:%d\n", nrow);
                        // for (int i = 0; i < nrow + 1; i++)
                        // {
                        //     for (int j = 0; j < ncolumn; j++)
                        //     {
                        //         printf("%s ", resultp[k++]);
                        //     }
                        //     putchar(10);
                        // }
                        strcpy(buf, resultp[3]);
                        send(acceptfd, buf, sizeof(buf), 0);

                        //写历史查询表
                        bzero(sql, sizeof(sql));
                        time(&ti);
                        my_loctime = localtime(&ti);
                        sprintf(mytime, "%d-%d-%d %d:%d:%d", my_loctime->tm_year + 1900, my_loctime->tm_mon + 1, my_loctime->tm_mday, my_loctime->tm_hour, my_loctime->tm_min, my_loctime->tm_sec);
                        // printf("mytime:%s\n", mytime);

                        sprintf(sql, "insert into historySearch values(\"%s\",\"%s\",\"%s\");", myuser.name, mytime, com.data);
                        //printf("sql:%s\n", sql);

                        if (sqlite3_exec(db, sql, NULL, NULL, &errmsg) != 0)
                        {
                            fprintf(stderr, "sqlite3_exec err:%s\n", errmsg);
                            printf("sqlite3_exec\n");
                            //  return;
                        }
                        // printf("history ok\n");
                    }
                }
            }
            //4\历史查询
            else if (strcmp(com.type, "myhistory") == 0)
            {
                printf("用户%s历史记录查询\n\n", myuser.name);
                bzero(sql, sizeof(sql));
                k = 3;
                nrow = 0;
                ncolumn = 0;
                int j;
                sprintf(sql, "select * from historySearch where name=\"%s\";", myuser.name);
                // printf("sql:%s\n", sql);
                if (sqlite3_get_table(db, sql, &resultp, &nrow, &ncolumn, &errmsg) != 0)
                {
                    fprintf(stderr, "select err:%s", errmsg);
                    return;
                }
                //printf("nrow:%d\n", nrow);

                for (int i = 1; i < nrow + 1; i++)
                {
                    // if (i == 0)
                    //     continue; //把表头去掉,要是i=0,k=3开始,有段错误,不去表头,因为resultp把表头去了,所以多遍历一行,会导致段错误
                    for (j = 0; j < ncolumn; j++)
                    {
                        // printf("%s ", resultp[k++]);

                        if (j == 0)
                            strcpy(his_list.name, resultp[k++]); //k从3开始
                        else if (j == 1)
                            strcpy(his_list.usertime, resultp[k++]);
                        else if (j == 2)
                            strcpy(his_list.word, resultp[k++]);
                    }

                    //putchar(10);
                    send(acceptfd, &his_list, sizeof(his_list), 0);
                }
                strcpy(his_list.word, "historyend");
                send(acceptfd, &his_list, sizeof(his_list), 0);
                // printf("historyend\n");
                // break;
            }
            else if(strcmp(com.type,"#")==0)
            {
                printf("%s 用户退出查询界面\n",myuser.name);
                break;
            }
                
        }

        // printf("end\n");
    }
}

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值