IO多路复用实现在字典文件中查询单词

前言

	在学习网络编程时,我们已了解到TCP并发服务器既可以保证多个客户端同时访问服务器,同时也保证了数据传输的完整和准确,大大提高了服务器实用性。而我作为初学网络编程的小白,和大家分享一个IO多路复用实现在字典文件中查询单词的程序。不足之处,欢迎各位指正!

题目要求:

IO多路复用实现在字典文件中查询单词

注意事项:

1、服务器程序中注意文件描述符集合的及时更新,包括新增文件描述符和剔除;
2、服务器程序中用到sqlite3数据库,操作数据库时注意sql语句的正确性;
3、服务器程序中执行单词查询操作时,注意光标的偏移位置,否则会造成单词查询不到的情况;
4、服务器程序中操作sqlite3数据库时,若调用sqlite3_get_table函数,需调用sqlite3_free_table进行释放,避免内存泄露。

流程图:

在这里插入图片描述

代码实现:

服务器:

server.h

#ifndef __SERVER_H__
#define __SERVER_H__
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <sqlite3.h>

#define ERRLOG(errmsg)                                      \
    do                                                      \
    {                                                       \
        printf("%s %s %d\n", __FILE__, __func__, __LINE__); \
        perror(errmsg);                                     \
        exit(-1);                                           \
    } while (0)

#define N_SQL 1024
#define N_name 32
#define N_txt 512

// 声明数据包类型:
typedef struct _MSG
{
    char type;     // 类型( R 注册 L 登录 Q 退出  W 单词查询 H 历史记录 )
    char name[N_name]; // 用户名
    char txt[N_txt]; // 密码、单词、单词解释
} msg_t;

// 函数声明:
void deal_register(sqlite3 *db, int acceptfd, msg_t msg);
void deal_login(sqlite3 *db, int acceptfd, msg_t msg);
void deal_query(FILE *fp, int acceptfd, msg_t msg, sqlite3 *db);
void deal_record(sqlite3 *db, int acceptfd, msg_t msg);
int callback_record(void *arg, int column, char **f_value, char **f_name);

#endif

server.c

#include "server.h"

// 标志位:用于标识客户端是否存在查询记录。if(count==0)说明无查询记录,else说明有查询记录:
int flag = 0; 

// 处理用户注册
void deal_register(sqlite3 *db, int acceptfd, msg_t msg)
{
    // printf("msg.type = [%c]\n", msg.type);
    char sql[N_SQL] = {0};
    int ret = 0;
    char **result = NULL;
    int nrow = 0;
    int ncolumn = 0;

    // 组装sql语句:查询用户名是否存在
    sprintf(sql, "SELECT * FROM usermsg WHERE name='%s'", msg.name);
    // printf("sql = [%s]\n", sql);
    ret = sqlite3_get_table(db, sql, &result, &nrow, &ncolumn, NULL);
    if (SQLITE_OK != ret)
    {
        ERRLOG("sqlite3_get_table (SELECT *FROM usermsg) error\n");
    }
    // printf("nrow = %d\n",nrow);
    if (0 == nrow)
    {
        // 说明用户不存在,直接注册:
        // 组装注册的sql语句
        sprintf(sql, "INSERT INTO usermsg VALUES('%s','%s')", msg.name, msg.txt);
        // printf("sql = [%s]\n", sql);
        ret = sqlite3_exec(db, sql, NULL, NULL, NULL);
        if (SQLITE_OK != ret)
        {
            ERRLOG("sqlite3_exec (INSERT INTO usermsg VALUES) error\n");
        }

        strcpy(msg.txt, "Register Success!,Please Login...");
        if (-1 == send(acceptfd, &msg, sizeof(msg_t), 0))
        {
            ERRLOG("Send error\n");
        }
        printf("[%s]\n", msg.txt);
    }
    else
    {
        // 说明用户存在,提示用户登录
        strcpy(msg.txt, "User Exist,Please Login!...");
        if (-1 == send(acceptfd, &msg, sizeof(msg_t), 0))
        {
            ERRLOG("Send error\n");
        }
        printf("[%s]\n", msg.txt);
    }
    // 使用sqlite3_get_table函数时需对资源进行释放
    sqlite3_free_table(result);
    return;
}
// 处理用户登录
void deal_login(sqlite3 *db, int acceptfd, msg_t msg)
{
    char sql[N_SQL] = {0};
    int ret = 0;
    char **result = NULL;
    int nrow = 0;
    int ncolumn = 0;

     组装sql语句:查询用户名是否存在
    sprintf(sql, "SELECT *FROM usermsg WHERE name='%s'", msg.name);
    // printf("sql = [%s]\n", sql);
    ret = sqlite3_get_table(db, sql, &result, &nrow, &ncolumn, NULL);
    if (SQLITE_OK != ret)
    {
        ERRLOG("sqlite3_get_table error\n");
    }

    if (0 == nrow)
    { // 说明用户不存在,需要注册
        strcpy(msg.txt, "User Not Exist,Please Register!...");
        if (-1 == send(acceptfd, &msg, sizeof(msg_t), 0))
        {
            ERRLOG("Send error\n");
        }
        printf("[%s]\n", msg.txt);
    }
    else
    {
        // 用户存在,检验用户名和密码是否一致
        // 组装sql语句:
        sprintf(sql, "SELECT *FROM usermsg WHERE name='%s' and passwd='%s'", msg.name, msg.txt);
        // printf("sql = [%s]\n", sql);
        ret = sqlite3_get_table(db, sql, &result, &nrow, &ncolumn, NULL);
        if (SQLITE_OK != ret)
        {
            ERRLOG("sqlite3_get_table error\n");
        }
        if (0 == nrow)
        { // 用户名和密码不一致
            strcpy(msg.txt, "Username or Passwd Error! Please Try Agin...");
            if (-1 == send(acceptfd, &msg, sizeof(msg_t), 0))
            {
                ERRLOG("Send error\n");
            }
            printf("[%s]\n", msg.txt);
        }
        else
        { // 登录成功
            strcpy(msg.txt, "Login Success!");
            if (-1 == send(acceptfd, &msg, sizeof(msg_t), 0))
            {
                ERRLOG("Send error\n");
            }
            printf("[%s]\n", msg.txt);
        }
    }
    // 调用sqlite3_get_table函数需进行资源释放
    sqlite3_free_table(result);

    return;
}

// 处理用户单词查询(文件中查询)
void deal_query(FILE *fp, int acceptfd, msg_t msg, sqlite3 *db)
{
    char buff[512] = {0};
    char sql[N_SQL] = {0};
    char sys_time[32] = {0};
    int i = 0;
    int j = 0;
    int ret = 0;
    // printf("recv:msg.txt = [%s]\n", msg.txt);
    while (NULL != fgets(buff, sizeof(buff), fp))
    {
        //查到该单词
        if ((!strncmp(buff, msg.txt, strlen(msg.txt))) && (buff[strlen(msg.txt)] == ' '))
        {
            // 查询到单词的同时 先将用户名、查询时间和单词插入到数据库record表中
            time_t tm = 0;
            struct tm *nowtm = NULL;
            if (-1 == (tm = time(NULL)))
            {
                ERRLOG("time error\n");
            }
            if (NULL == (nowtm = localtime(&tm)))
            {
                ERRLOG("localtime error\n");
            }
            // printf("%4d-%02d-%02d %02d:%02d:%02d\n", nowtm->tm_year + 1900, nowtm->tm_mon + 1, nowtm->tm_mday,
            //    nowtm->tm_hour, nowtm->tm_min, nowtm->tm_sec);
            sprintf(sys_time, "%4d-%02d-%02d %02d:%02d:%02d", nowtm->tm_year + 1900, nowtm->tm_mon + 1, nowtm->tm_mday,
                    nowtm->tm_hour, nowtm->tm_min, nowtm->tm_sec);

            // 组装sql语句
            sprintf(sql, "INSERT INTO record VALUES('%s','%s','%s')", msg.name, sys_time, msg.txt);
            // printf("sql = %s\n", sql);
            // 执行sql语句
            ret = sqlite3_exec(db, sql, NULL, NULL, NULL);
            if (SQLITE_OK != ret)
            {
                ERRLOG("sqlite3_exec error\n");
            }

            while (buff[i] != ' ')
            {
                i++;
            }
            while (buff[i] == ' ')
            {
                i++;
            }
            while (buff[i] != '\0')
            {
                msg.txt[j++] = buff[i++];
            }
            if (-1 == send(acceptfd, &msg, sizeof(msg_t), 0))
            {
                ERRLOG("Send error\n");
            }
            printf("%s", msg.txt);
            fseek(fp, 0, SEEK_SET);
            return;
        }
    }
    // 未查到要查询的单词:直接提示单词未找到!
    strcpy(msg.txt, "Sorry,Word Not Exist!,Please Input Again...");
    fseek(fp, 0, SEEK_SET);
    if (-1 == send(acceptfd, &msg, sizeof(msg_t), 0))
    {
        ERRLOG("Send error\n");
    }
    printf("[%s]\n", msg.txt);
    return;
}

// 处理用户查询记录
void deal_record(sqlite3 *db, int acceptfd, msg_t msg)
{
    char buff[256] = {0};
    char sql[N_SQL] = {0};
    int i = 0;
    int ret = 0;
    // 组装sql语句
    sprintf(sql, "SELECT *FROM record WHERE name='%s'", msg.name);
    // printf("sql = [%s]\n", sql);
    
    // 执行sql语句
    ret = sqlite3_exec(db, sql, callback_record, (void *)&acceptfd, NULL);
    if (SQLITE_OK != ret)
    {
        ERRLOG("sqlite3_exec error\n");
    }
    if (flag == 0)
    {
        strcpy(msg.txt, "No record to query...");
        if (-1 == send(acceptfd, &msg, sizeof(msg_t), 0))
        {
            ERRLOG("Send error");
        }
    }
    flag = 0;
    strcpy(msg.txt, "---over---");
    if (-1 == send(acceptfd, &msg, sizeof(msg_t), 0))
    {
        ERRLOG("Send error");
    }
    // printf("msg.txt = %s\n",msg.txt);
    return;
}

// 查询历史记录回调函数
int callback_record(void *arg, int column, char **f_value, char **f_name)
{
    int acceptfd;
    msg_t msg;
    acceptfd = *(int *)arg;
    sprintf(msg.txt, "%s  %s", f_value[1], f_value[2]);
    flag++;
    // printf("flag = %d\n", flag);
    if (-1 == send(acceptfd, &msg, sizeof(msg_t), 0))
    {
        ERRLOG("Send error");
    }

    // printf("msg.txt = %s\n",msg.txt);
    return 0;
}

main.c

// 创建单词查询的“服务器”程序
#include "server.h"

extern int flag;

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

    //入参合理性检查
    if (3 != argc)
    {
        printf("Input error,Please try again!...\n");
        printf("Usage : %s <IP> <PORT>\n", argv[0]);
        exit(-1);
    }

    // 1.创建流式套接字
    if (-1 == (sockfd = socket(AF_INET, SOCK_STREAM, 0)))
    {
        ERRLOG("socket error");
    }

    // 2.填充服务器网络信息结构体
    struct sockaddr_in serveraddr = {
        .sin_family = AF_INET,
        .sin_port = htons(atoi(argv[2])),
        .sin_addr.s_addr = inet_addr(argv[1])};

    socklen_t serveraddr_len = sizeof(serveraddr);

    // 3.绑定网络信息结构体和套接字
    if (-1 == bind(sockfd, (const struct sockaddr *)&serveraddr, serveraddr_len))
    {
        ERRLOG("bind error");
    }

    // 4.设置流式套接字为被监听状态
    if (-1 == listen(sockfd, 10))
    {
        ERRLOG("listen error");
    }
    printf("服务器已启动\n");

    // 关注客户端网络信息结构体信息
    struct sockaddr_in clientaddr;
    socklen_t clientaddr_len = sizeof(clientaddr);

    int acceptfd;
    int ret = 0;
    int i = 0;
    int nbytes = 0;
    msg_t msg;
    memset(&msg, 0, sizeof(msg_t));

    // 创建文件描述符集合
    int max_fd = 0;
    fd_set readfds;
    FD_ZERO(&readfds);
    fd_set readfds_temp;
    FD_ZERO(&readfds_temp);

    // 将文件描述符添加到集合中,并更新最大的文件描述符
    FD_SET(sockfd, &readfds);
    max_fd = max_fd > sockfd ? max_fd : sockfd;

    // 打开数据库文件并创建usermsg表、record表
    sqlite3 *db = NULL;
    char sql[N_SQL] = {0};

    ret = sqlite3_open("./dict_db", &db);
    if (SQLITE_OK != ret)
    {
        ERRLOG("sqlite3_open error");
    }
    // printf("打开数据库文件成功...\n");

    // 设置主键 用户名是唯一的
    sprintf(sql, "CREATE TABLE if not exists usermsg(name text primary key,passwd text)");
    ret = sqlite3_exec(db, sql, NULL, NULL, NULL);
    if (SQLITE_OK != ret)
    {
        ERRLOG("sqlite3_exec (CREATE TABLE  usermsg) error");
    }
    // printf("创建数据库文件的usermsg表成功...\n");

    sprintf(sql, "CREATE TABLE if not exists record(name text,time text,word text)");
    ret = sqlite3_exec(db, sql, NULL, NULL, NULL);
    if (SQLITE_OK != ret)
    {
        ERRLOG("sqlite3_exec (CREATE TABLE record) error");
    }
    // printf("创建数据库文件的record表成功...\n");

    // 以 只读 的方式打开词典文件
    FILE *fp = NULL;
    if (NULL == (fp = fopen("./dict.txt", "r")))
    {
        ERRLOG("fopen error");
    }

    while (1)
    {
        readfds_temp = readfds;
        ret = select(max_fd + 1, &readfds_temp, NULL, NULL, NULL);
        if (-1 == ret)
        {
            ERRLOG("select error");
        }
        for (i = 3; i <= max_fd && ret != 0; i++)
        {
            if (FD_ISSET(i, &readfds_temp))
            {
                if (i == sockfd)
                {
                    // 新用户链接服务器
                    acceptfd = accept(i, (struct sockaddr *)&clientaddr, &clientaddr_len);
                    if (-1 == acceptfd)
                    {
                        ERRLOG("accept error");
                    }
                    printf("客户端[%d]已链接\n", acceptfd);

                    // 将新产生的文件描述符添加到集合中,并更新最大文件描述符
                    FD_SET(acceptfd, &readfds);
                    max_fd = max_fd > acceptfd ? max_fd : acceptfd;
                }
                else
                {
                    // 客户端发来数据
                    memset(&msg, 0, sizeof(msg_t));
                    nbytes = recv(i, &msg, sizeof(msg_t), 0);
                    if (-1 == nbytes)
                    {
                        ERRLOG("Recv error\n");
                    }
                    else if (0 == nbytes)
                    {
                        printf("客户端[%d]断开了链接...\n", i);
                        FD_CLR(i, &readfds);
                        close(i);
                        continue;
                    }

                    // 处理客户端的数据
                    printf("客户端[%d]发来请求...\n", acceptfd);
                    switch (msg.type)
                    {
                    case 'R': //处理注册
                        deal_register(db, i, msg);
                        break;
                    case 'L': //处理登录
                        deal_login(db, i, msg);
                        break;
                    case 'Q': //处理退出
                        printf("客户端[%d]已退出...\n", i);
                        FD_CLR(i, &readfds);
                        close(i);
                        continue;
                    case 'W': //处理单词查询
                        deal_query(fp, i, msg, db);
                        break;
                    case 'H': //处理历史查询
                        deal_record(db, i, msg);
                        break;
                    }
                }
                ret--;
            }
        }
    }
    // 关闭文件
    fclose(fp);

    // 关闭数据库文件
    ret = sqlite3_close(db);

    // 7.关闭套接字
    close(sockfd);

    return 0;
}

客户端:

client.h

#ifndef __CLIENT_H__
#define __CLIENT_H__

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

#define ERRLOG(errmsg)                                      \
    do                                                      \
    {                                                       \
        printf("%s %s %d\n", __FILE__, __func__, __LINE__); \
        perror(errmsg);                                     \
        exit(-1);                                           \
    } while (0)

#define N_name 32
#define N_txt 512

// 声明数据包类型:
typedef struct _MSG
{
    char type;     // 类型(1 注册/单词查询  2 登录/历史记录  3 退出 )
    char name[N_name]; // 用户名
    char txt[N_txt]; // 密码、单词、单词解释
} msg_t;

// 函数声明:
void print_men_one(void);
void print_men_two(void);
void deal_register(int sockfd, msg_t msg);
void deal_login(int sockfd, msg_t msg);
void deal_query(int sockfd, msg_t msg);
void deal_record(int sockfd, msg_t msg);

#endif

client.c

#include "client.h"

// 定义标志位:用来标志注册、登录的状态:
int flag = 0;

// 打印菜单(注册、登录或退出)
void print_men_one(void)
{
    printf("-------------------------------\n");
    printf("| 1.Register  2.Login  3.Quit |\n");
    printf("-------------------------------\n");
    return;
}

// 打印菜单(查询、历史记录或退出)
void print_men_two(void)
{
    printf("-----------------------------\n");
    printf("| 1.Query  2.Record  3.Quit |\n");
    printf("-----------------------------\n");
    return;
}

// 用户注册函数:
void deal_register(int sockfd, msg_t msg)
{
    msg.type = 'R';
    // printf("msg.type = %c\n", msg.type);

    printf("Please input username:");
    scanf("%s", msg.name);
    printf("Please input passwd:");
    scanf("%s", msg.txt);

    // 发送消息
    if (-1 == send(sockfd, &msg, sizeof(msg_t), 0))
    {
        ERRLOG("Send error");
    }
    // printf("Send register success!\n");

    // 接收消息
    int nbytes = recv(sockfd, &msg, sizeof(msg_t), 0);
    if (-1 == nbytes)
    {
        ERRLOG("Recv error");
    }
    printf("[%s]\n", msg.txt);

    return;
}

// 登录函数
void deal_login(int sockfd, msg_t msg)
{
    msg.type = 'L';
    // printf("msg.type = %c\n", msg.type);

    if (-1 == send(sockfd, &msg, sizeof(msg_t), 0))
    {
        ERRLOG("Send error\n");
    }
    // printf("Send dogin success!\n");

    // 接收消息
    int nbytes = recv(sockfd, &msg, sizeof(msg_t), 0);
    if (-1 == nbytes)
    {
        ERRLOG("Recv error");
    }
    printf("[%s]\n", msg.txt);

    if (!strcmp(msg.txt, "User Not Exist,Please Register!..."))
    {
        flag = 1;
    }

    if (!strcmp(msg.txt, "Username or Passwd Error! Please Try Agin..."))
    {
        flag = 2;
    }
    // printf("flag = %d\n",flag);
    
    return;
}

// 用户查询单词函数
void deal_query(int sockfd, msg_t msg)
{
    msg.type = 'W';
    // printf("msg.type = %c\n", msg.type);

    // 循环查询单词
    while (1)
    {
        printf("Please input word:");
        scanf("%s", msg.txt);

        // 从终端输入'#',跳出循环
        if (!strcmp(msg.txt, "#"))
        {
            break;
        }
        // 发送消息
        if (-1 == send(sockfd, &msg, sizeof(msg_t), 0))
        {
            ERRLOG("Send error");
        }
        // printf("%s\n", msg.txt);

        // 接收消息
        if (-1 == recv(sockfd, &msg, sizeof(msg_t), 0))
        {
            ERRLOG("Recv error");
        }
        printf("Result: %s", msg.txt);
    }
    return;
}

// 查询历史记录
void deal_record(int sockfd, msg_t msg)
{
    msg.type = 'H';
    // printf("msg.type = %c\n", msg.type);

    if (-1 == send(sockfd, &msg, sizeof(msg_t), 0))
    {
        ERRLOG("Send error");
    }
    // printf("%s\n", msg.txt);

    // 循环接收消息
    while (1)
    {
        if (-1 == recv(sockfd, &msg, sizeof(msg_t), 0))
        {
            ERRLOG("Recv error");
        }

        // 判断是否接收数据完毕
        if (!strcmp(msg.txt, "---over---"))
        {
            break;
        }
        printf("[%s]\n", msg.txt);
    }
    return;
}

main.c

// 创建单词查询的“客户端”程序
#include "client.h"

extern int flag;

int main(int argc, const char *argv[])
{
    //入参合理性检查
    if (3 != argc)
    {
        printf("Usage : %s <IP> <PORT>\n", argv[0]);
        exit(-1);
    }

    // 创建流式套接字
    int sockfd = 0;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == sockfd)
    {
        ERRLOG("socket error");
    }

    // 填充服务器网络信息结构体
    struct sockaddr_in serveraddr = {
        .sin_family = AF_INET,
        .sin_port = htons(atoi(argv[2])),
        .sin_addr.s_addr = inet_addr(argv[1])};

    socklen_t serveraddr_len = sizeof(serveraddr);

    // 链接服务器
    if (-1 == connect(sockfd, (const struct sockaddr *)&serveraddr, serveraddr_len))
    {
        printf("connect error");
        exit(-1);
    }
    printf("链接[%s %d] 的服务器成功...\n", inet_ntoa(serveraddr.sin_addr), ntohs(serveraddr.sin_port));

    int choose_one = 0;
    int choose_two = 0;
    msg_t msg;
    memset(&msg, 0, sizeof(msg));

    while (1)
    {
        // 打印选项菜单(注册、登录或退出)
        print_men_one();
        printf("Please choose:");
        scanf("%d", &choose_one);

        switch (choose_one)
        {
        case 1: // 注册
        RE_REDISTER:
            deal_register(sockfd, msg);
            // break;
        case 2: // 登录
        RE_LOGIN:
            printf("Please input username:");
            scanf("%s", msg.name);
            printf("Please input passwd:");
            scanf("%s", msg.txt);

            deal_login(sockfd, msg);

            // printf("flag = %d\n",flag);
            // flag == 1 说明用户未注册,返回至“注册”界面,客户端直接输入用户名、密码进行注册
            if (flag == 1)
            {
                flag = 0;
                goto RE_REDISTER;
            }
            // flag == 2 说明用户名与密码不一致,返回至“登录”界面,客户端直接输入用户名、密码进行登录
            if (flag == 2)
            {
                flag = 0;
                goto RE_LOGIN;
            }
            while (1)
            {
                print_men_two();
                printf("Please choose:");
                scanf("%d", &choose_two);
                switch (choose_two)
                {
                case 1: // 查询单词
                    deal_query(sockfd, msg);
                    break;
                case 2: // 查询历史记录
                    deal_record(sockfd, msg);
                    break;
                }
                if (3 == choose_two)
                {
                    break;
                }
            }
            break;
        }
        if (3 == choose_one)
        {
            msg.type = 'Q';
            // printf("msg.type = %c\n", msg.type);
            if (-1 == send(sockfd, &msg, sizeof(msg_t), 0))
            {
                ERRLOG("Send error\n");
            }
            printf("Welcome back...\n");
            break;
        }
    }
    // 关闭
    close(sockfd);

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值