Tcp、select函数实现在线词典(C语言)

功能:
注册:添加用户名和密码到数据库。
登录:检索数据库,查询是否符合。
查询:检索字典文本文件,查询结果存入数据库中。
历史:检索数据库,将登录账户的查询历史打印。

TCP服务器:

声明与定义

#define ERRLOG(msg)                                         \
    do                                                      \
    {                                                       \
        printf("%s %s(%d):", __FILE__, __func__, __LINE__); \
        perror(msg);                                        \
        exit(-1);                                           \
    } while (0)
//消息结构体封装
typedef struct msg
{
    char code;
    char name[20];
    char txt[128];
} msg_t;

sqlite3 *sql_init();//数据库初始化
int log_sql(msg_t *clientmsg, sqlite3 *my_sql, int clientfd);//注册
int select_sql(msg_t *clientmsg, sqlite3 *my_sql, int clientfd);//登录
int printf_sql(msg_t *clientmsg, sqlite3 *my_sql, int clientfd);//检索单词
int history_sql(msg_t *clientmsg, sqlite3 *my_sql, int clientfd);//查看历史
int str_deal(char *buf);//字符串处理
int time_get(char *timestr);//获取时间

主函数体

//检查输入
	if (3 != argc)
    {
        printf("Usage: %s <IP> <port>\n", argv[0]);
        return -1;
    }
1、第一步配置服务器结构体和套接字
//套接字
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == sockfd)
    {
        ERRLOG("socket error");
    }
//配置服务器结构体
    struct sockaddr_in serveraddr;
    memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[2]));
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    socklen_t serveraddr_len = sizeof(serveraddr);

    if (-1 == bind(sockfd, (struct sockaddr *)&serveraddr, serveraddr_len))
    {
        ERRLOG("bind error");
    }

    if (-1 == listen(sockfd, 5))
    {
        ERRLOG("listen error");
    }
2、配置select函数的文件句柄
	int maxfd = 0;
    fd_set readfds;
    fd_set readfdstemp;
    FD_ZERO(&readfds);
    FD_ZERO(&readfdstemp);
	//将套接字描述符添加到句柄中
    FD_SET(sockfd, &readfds);
    maxfd = maxfd > sockfd ? maxfd : sockfd;
3、初始化数据库
sqlite3 *my_sql = sql_init();
4、通过select函数检索句柄实现的逻辑结构
	int ret = 0;//保存有几个文件描述符就绪
    int i = 0;//在循环中代表了文件描述符
    int clientfd;//客户端连接创建的套接字
    int nbyte = 0;//接受多少字节
    while (1)
    {
        FD_ZERO(&readfdstemp);
        readfdstemp = readfds;
        //阻塞等待有1个或者多个文件描述符就绪
        ret = select(maxfd + 1, &readfdstemp, NULL, NULL, NULL);
        if (ret == -1)
        {
            PRINT_ERR("select init error");
        }
        else
        {//遍历句柄,区分有客户端连接还是客户端发送消息
            for (i = 3; i < (maxfd + 1) && ret != 0; i++)
            {
                if (FD_ISSET(i, &readfdstemp))
                {
                    if (i == sockfd)
                    {
                        if ((clientfd = accept(i, NULL, NULL)) == -1)
                        {
                            PRINT_ERR("accept error");
                        }
                        printf("新用户已连接\n");
                        FD_SET(clientfd, &readfds);
                        maxfd = maxfd > clientfd ? maxfd : clientfd;
                    }
                    else
                    {
                        memset(&severmsg, 0, sizeof(severmsg));
                        if ((nbyte = recv(i, &severmsg, sizeof(severmsg), 0)) == -1)
                        {
                            PRINT_ERR("recv error");
                        }
                        else if (nbyte == 0)
                        {
                            printf("用户断开连接\n");
                            FD_CLR(i, &readfds);
                            close(i);
                            continue;
                        }
                        //根据结构体中的操作码.code判断客户端需求
                        switch (severmsg.code)
                        {
                        case '1':
                            log_sql(&severmsg, my_sql, i);
                            break;
                        case '2':
                            select_sql(&severmsg, my_sql, i);
                            break;
                        case '3':
                            printf_sql(&severmsg, my_sql, i);
                            break;
                        case '4':
                            history_sql(&severmsg, my_sql, i);
                            break;
                        }
                    }
                    //每次处理完一个就绪,已就绪的文件描述符就减少了一个
                    ret--;
                }
            }
        }
    }


功能模块
数据库初始化:

sqlite3 *sql_init()
{
    sqlite3 *my_sql = NULL;
    int ret = sqlite3_open("dictionary.db", &my_sql);
    if (ret != SQLITE_OK)
    {
        printf("error:%d , errstr:%s", ret, sqlite3_errmsg(my_sql));
        exit(-1);
    }
    char *errstr = NULL;
    char user[128] = "CREATE TABLE IF NOT EXISTS usr (name txt primary key, pass txt);";
    char hissql[128] = "CREATE TABLE IF NOT EXISTS history (name txt, date txt, word txt,jieshi char);";

    if ((ret = sqlite3_exec(my_sql, user, NULL, NULL, &errstr)) != SQLITE_OK)
    {
        printf("errcode:[%d]   errstr:[%s]\n", ret, errstr);
        exit(-1);
    }
    if ((ret = sqlite3_exec(my_sql, hissql, NULL, NULL, &errstr)) != SQLITE_OK)
    {
        printf("errcode:[%d]   errstr:[%s]\n", ret, errstr);
        exit(-1);
    }
    sqlite3_free(errstr);
    return my_sql;
}
2、注册用户名
int log_sql(msg_t *clientmsg, sqlite3 *my_sql, int clientfd)
{
    int ret = 0;
    char sqlstr[200] = {0};
    sprintf(sqlstr, "INSERT INTO usr VALUES('%s' , '%s')", clientmsg->name, clientmsg->txt);
    if ((ret = sqlite3_exec(my_sql, sqlstr, NULL, NULL, NULL)) != SQLITE_OK)
    {
        printf("errcode:[%d]   errstr:[%s]\n", ret, sqlite3_errmsg(my_sql));
    }

    if (send(clientfd, clientmsg, sizeof(msg_t), 0) == -1)
    {
        PRINT_ERR("send error");
    }
    return 0;
}
3、登录用户名和密码
int select_sql(msg_t *clientmsg, sqlite3 *my_sql, int clientfd)
{
    int ret = 0;
    char sqlstr[200] = {0};
    char **result;
    int row;
    int col;
    sprintf(sqlstr, "SELECT * FROM usr WHERE name='%s' AND pass='%s'", clientmsg->name, clientmsg->txt);
    if ((ret = sqlite3_get_table(my_sql, sqlstr, &result, &row, &col, NULL)) != SQLITE_OK)
    {
        printf("errcode:[%d]   errstr:[%s]\n", ret, sqlite3_errmsg(my_sql));
    }
    if (row == 0)
    {
    	//若无符合的用户名和密码,操作码置5。告诉客户端用户名或密码错误
        clientmsg->code = '5';
    }

    sqlite3_free_table(result);

    if (send(clientfd, clientmsg, sizeof(msg_t), 0) == -1)
    {
        PRINT_ERR("send error");
    }
    printf("%c,%s,%s\n", clientmsg->code, clientmsg->name, clientmsg->txt);
    return 0;
}
4、检索词典
int printf_sql(msg_t *clientmsg, sqlite3 *my_sql, int clientfd)
{
    //打开文件
    FILE *fp;
    char buf[300] = {0};
    if ((fp = fopen("dict.txt", "r")) == NULL)
    {
        PRINT_ERR("open dictionary error");
    }
    int flag = 0;
    while (fgets(buf, sizeof(buf), fp) != NULL)
    {
        if (strncmp(buf, clientmsg->txt, strlen(clientmsg->txt)) == 0)
        {
            if (buf[strlen(clientmsg->txt)] == ' ')
            {
                str_deal(buf);
                flag = 1;
                break;
            }
        }
    }
    if (flag == 1)
    {
        //处理查询结果
        char word[30] = {0};
        strcpy(word, clientmsg->txt);
        char *p = buf + (strlen(clientmsg->txt) + 1);
        strcpy(clientmsg->txt, p);

        //获取时间
        char timestr[50] = {0};
        time_get(timestr);

        //保存结果到历史中
        int ret = 0;
        char sqlstr[200] = {0};
        sprintf(sqlstr, "INSERT INTO history VALUES('%s' ,'%s', '%s','%s')", clientmsg->name, timestr, word, p);
        if ((ret = sqlite3_exec(my_sql, sqlstr, NULL, NULL, NULL)) != SQLITE_OK)
        {
            printf("errcode:[%d]   errstr:[%s]\n", ret, sqlite3_errmsg(my_sql));
        }
    }
    else
    {
        strcpy(clientmsg->txt, "词库中无此单词,请检查");
    }
    if (send(clientfd, clientmsg, sizeof(msg_t), 0) == -1)
    {
        PRINT_ERR("send error");
    }
    fclose(fp);
    return 0;
}
5、历史记录查询
int history_sql(msg_t *clientmsg, sqlite3 *my_sql, int clientfd)
{
    int ret = 0;
    char sqlstr[200] = {0};
    char **result;
    int row;
    int col;
    sprintf(sqlstr, "SELECT * FROM history WHERE name='%s'", clientmsg->name);
    if ((ret = sqlite3_get_table(my_sql, sqlstr, &result, &row, &col, NULL)) != SQLITE_OK)
    {
        printf("errcode:[%d]   errstr:[%s]\n", ret, sqlite3_errmsg(my_sql));
    }
    //一行发送一次
    int i = 0;
    int j = 0;
    for (i = 0; i < col; i++);//函数结果的数组中前四个为表头
    int index = i;
    for (i = 0; i < row; i++)
    {
        for (j = 0; j < col;)
        {	//结果:用户名、时间、单词、单词解释……
        	//根据get_table函数的结果 +1为时间、+2为单词、+3为单词解释
            sprintf(clientmsg->txt, "%s   %s   :   %s", result[index + 1], result[index + 2], result[index+3]);
            if (send(clientfd, clientmsg, sizeof(msg_t), 0) == -1)
            {
                PRINT_ERR("send error");
            }
            index += 4;
            j += 4;
        }
    }
    //发送结束符
    strcpy(clientmsg->txt, "**over**");
    if (send(clientfd, clientmsg, sizeof(msg_t), 0) == -1)
    {
        PRINT_ERR("send error");
    }

    return 0;
}
6、字符串处理,删除多余的空格。每个空格处,只保留一个空格
int str_deal(char *buf)
{
    for (int i = 0; buf[i] != '\0';)
    {
        if (buf[i] == ' ' && buf[i + 1] == ' ')
        {
            for (int j = i + 1; buf[j] != '\0'; j++)
            {
                buf[j] = buf[j + 1];
            }
        }
        else
        {
            i++;
        }
    }
    return 0;
}
7、获取系统时间
int time_get(char *timestr)
{
    time_t time_history;
    struct tm *tm;
    if ((time_history = time(NULL)) == -1)
        PRINT_ERR("get time error");
    if ((tm = localtime(&time_history)) == NULL)
        PRINT_ERR("change time error");
    sprintf(timestr, "%d-%02d-%02d %02d:%02d:%02d", tm->tm_year + 1900,
            tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);

    return 0;
}

客户端

声明与定义

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

typedef struct msg
{
    char code;
    char name[20];
    char txt[128];
} msg_t;

int log_sql(msg_t *clientmsg, int sockfd);
int select_sql(msg_t *clientmsg, int sockfd);
int printf_sql(msg_t *clientmsg, int sockfd);
int history_sql(msg_t *clientmsg, int sockfd);

主函数体

	if (3 != argc)
    {
        printf("Usage: %s <IP> <port>\n", argv[0]);
        return -1;
    }
1、配置服务器和服务器结构体、连接服务器
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == sockfd)
    {
        ERRLOG("socket error");
    }

    struct sockaddr_in serveraddr;
    memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[2]));
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    socklen_t serveraddr_len = sizeof(serveraddr);
    if (-1 == connect(sockfd, (struct sockaddr *)&serveraddr, serveraddr_len))
    {
        ERRLOG("connect error");
    }

2、逻辑及实现
	msg_t clientmsg;
    memset(&clientmsg, 0, sizeof(clientmsg));
    while (1)
    {
        printf("************************************************\n");
        printf("**************1、注册 2、登录 *******************\n");
        printf("************************************************\n");

        scanf(" %c", &(clientmsg.code));
        printf("请输入用户名和密码:");
        scanf("%s %s", clientmsg.name, clientmsg.txt);

        switch (clientmsg.code)
        {
        case '1':
            log_sql(&clientmsg, sockfd);
            break;
        case '2':
            select_sql(&clientmsg, sockfd);
            if (clientmsg.code == '2')
            {	//如果登陆失败,服务器传回错误码5
                goto NEXT;
            }
            break;
        }
        memset(clientmsg.txt, 0, sizeof(clientmsg.txt));
        clientmsg.code = 0;
    }
NEXT:
    while (1)
    {
        printf("************************************************\n");
        printf("**********1、查询 2、历史 3、登出***************\n");
        printf("************************************************\n");
        scanf(" %c", &(clientmsg.code));

        switch (clientmsg.code)
        {
        case '1':
            clientmsg.code = '3';
            printf("输入单词:");
            scanf("%s", clientmsg.txt);
            printf_sql(&clientmsg, sockfd);
            break;
        case '2':
            clientmsg.code = '4';
            history_sql(&clientmsg, sockfd);
            break;
        }
        memset(clientmsg.txt, 0, sizeof(clientmsg.txt));
        clientmsg.code = 0;
    }


功能模块

1、注册模块
int log_sql(msg_t *clientmsg, int sockfd)
{
    int nbyte = 0;
    if (send(sockfd, clientmsg, sizeof(msg_t), 0) == -1)
    {
        PRINT_ERR("send error");
    }
    if ((nbyte = recv(sockfd, clientmsg, sizeof(msg_t), 0)) == -1)
    {
        PRINT_ERR("recv error");
    }
    if (clientmsg->code == '5')
    {
        printf("注册成功\n");
    }
    else
    {
        printf("用户名已被注册\n");
    }
    return 0;
}
2、登录
int select_sql(msg_t *clientmsg, int sockfd)
{
    int nbyte = 0;
    if (send(sockfd, clientmsg, sizeof(msg_t), 0) == -1)
    {
        PRINT_ERR("send error");
    }
    if ((nbyte = recv(sockfd, clientmsg, sizeof(msg_t), 0)) == -1)
    {
        PRINT_ERR("recv error");
    }
    if (clientmsg->code == '5')
    {
        printf("用户名或密码不正确,请确认用户名和密码或注册\n");
    }
    else
    {
        printf("登录成功\n");
    }
    return 0;
}
3、查询
int printf_sql(msg_t *clientmsg, int sockfd)
{
    int nbyte = 0;
    if (send(sockfd, clientmsg, sizeof(msg_t), 0) == -1)
    {
        PRINT_ERR("send error");
    }
    if ((nbyte = recv(sockfd, clientmsg, sizeof(msg_t), 0)) == -1)
    {
        PRINT_ERR("recv error");
    }
    printf("%s\n", clientmsg->txt);
    return 0;
}
4、历史查看
int history_sql(msg_t *clientmsg, int sockfd)
{
    int nbyte = 0;
    if (send(sockfd, clientmsg, sizeof(msg_t), 0) == -1)
    {
        PRINT_ERR("send error");
    }
    //判断是否接收完毕
    while (strcmp(clientmsg->txt,"**over**"))
    {
        if ((nbyte = recv(sockfd, clientmsg, sizeof(msg_t), 0)) == -1)
        {
            PRINT_ERR("recv error");
        }
        printf("%s\n", clientmsg->txt);
    }
    return 0;
}
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值