基于tcp协议及数据库sqlite3的云词典项目

这个小项目是一个网络编程学习过程中一个阶段性检测项目,过程中可以有效检测我们对于服务器客户端搭建的能力,以及一些bug查找能力。项目的一个简单讲解我发在了b站上,没啥心得,多练就好。
https://t.bilibili.com/865244702526406675?share_source=pc_native

数据库创建及导入单词表

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
int main(int argc, char const *argv[])
{
    // 1.打开或创建数据库
    sqlite3 *db = NULL;
    int rc;
    if (sqlite3_open("./word.db", &db) != SQLITE_OK)//打开或创建库
    {
        printf("sqlite3_open err:%s\n", sqlite3_errmsg(db));
        return -1;
    }
    printf("sqlite3_open success\n");
    // 2.创建表
    char *errmsg = NULL;//返回创建数据库表的错误
    //创建一个两列的单词表,用于存储单词及注释
    if (sqlite3_exec(db, "create table if not exists wd1 (word char, annotation char);", NULL, NULL, &errmsg) != SQLITE_OK)
    {
        printf("create err: %sn", errmsg);
        sqlite3_close(db);  
        return -1;
    }
    //创建一个两列的账户表,用于存储账户名及对应密码
    if (sqlite3_exec(db, "create table if not exists user (name char, password char);", NULL, NULL, &errmsg) != SQLITE_OK)
    {
        printf("create err: %sn", errmsg);
        sqlite3_close(db);
        return -1;
    }
    printf("create success\n");
    // 3.向表中插入数据
    FILE *fp = fopen(argv[1], "r");//打开要插入的文件流
    if (fp == NULL)
    {
        printf("failed to open file\n");
        sqlite3_close(db);
        return -1;
    }
    char buf[1024];  //读取的一行
    char word[32];   //单词
    char ant[1024];  //保存注释
    while (fgets(buf, sizeof(buf), fp) != NULL) //读一行
    {
        sscanf(buf, "%99[^ ] %256[^\n]", word, ant);  //将第一个单词放到单词数组中,后面的内容放到注释数组中
        char sql[1024];//存放命令内容
        sprintf(sql, "insert into wd1 values(\"%s\", \"%s\");", word, ant);  // 构造插入语句,将单词及注释插入到单词表中
        rc = sqlite3_exec(db, sql, NULL, NULL, &errmsg);
        if (rc != SQLITE_OK)
        {
            printf("insert err: %s\n", errmsg);
            return -1;
        }
    }
    fclose(fp);//关闭文件描述符
    // 5.关闭数据库连接
    sqlite3_close(db);
    return 0;
}

头函数及传输协议

#ifndef __HEAD_H__
#define __HEAD_H__//防止重包含
#include 
#include 
#include 
#define N 32
enum type_t //运行命令
{
    R=4, //register注册
    L, //login登录
    Q, //query搜索
    H, //history历史
};
typedef struct //数据包结构体
{
    int type;//执行命令类型
    char name[N];   //用户名
    char data[1024]; //密码或要查询的单词
} MSG_t;
typedef struct node_t
{
    struct sockaddr_in addr; //ip地址
    struct node_t *next;     //链表下一个地址
} list_t;
#endif

云词典服务器端

/*服务器创建代码 */
#include 
#include  /* See NOTES */
#include 
#include 
#include  /* superset of previous */
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "head.h"
MSG_t msg;
int n;
sqlite3 *db = NULL;  //命令输入
char *errmsg = NULL; //错误码
int hang, lie;       //数据库行和列
int k = 0;
int Register(sqlite3 *db, int sockfd) //注册函数
{
    char **result = NULL; //数据库返回内容
    //3.向表中插入数据
    //(1)执行函数
    char sq1[128];                                                     //保存命令
    sprintf(sq1, "select * from user where name = \"%s\";", msg.name); 
    sqlite3_get_table(db, sq1, &result, &hang, &lie, &errmsg);//判断数据库中是否已经存在该账户
    if (hang != 0)
    {
        sprintf(msg.data, "账户已存在;\n");
        send(sockfd, &msg, sizeof(msg), 0);
        return -1;
    }
    else //成功注册
    {
        sprintf(sq1, "insert into user values(\"%s\",\"%s\");", msg.name, msg.data);
        if (sqlite3_exec(db, sq1, NULL, NULL, &errmsg) == SQLITE_OK) //""需要用\转意,注册成功插入用户表内
        {
            sprintf(sq1, "create table if not exists \"%s\" (word char, time char);", msg.name); //每注册一个用户创建一个新表
            if (sqlite3_exec(db, sq1, NULL, NULL, &errmsg) != SQLITE_OK)                         //创建新表
            {
                printf("create err: %s", errmsg);
                sqlite3_close(db);
                return -1;
            }
            else
            {
                printf("creat %s success\n", msg.name);
            }
            sprintf(msg.data, "OK");
            send(sockfd, &msg, sizeof(msg), 0); //注册成功发送消息
            memset(msg.data, 0, sizeof(msg.data));
            return 0;
        }
        else //否则插入失败
        {
            printf("insert value err;%s\n", errmsg);
            return -1;
        }
    }
}
//用户登录
int loginclient(sqlite3 *db, int sockfd)
{
    char **result = NULL; //数据库返回内容
    char sq1[128];        //保存命令
    sprintf(sq1, "select * from user where name = \"%s\";", msg.name);
    sqlite3_get_table(db, sq1, &result, &hang, &lie, &errmsg); //判断数据库中是否已经存在该账户
    if (hang != 0)                                             //如果能读出内容则行数不为0
    {
        if (strcmp(result[3], msg.data) == 0) //判断密码是否正确
        {
            sprintf(msg.data, "OK");
            send(sockfd, &msg, sizeof(msg), 0);
            return 0;
        }
        else //密码错误
        {
            sprintf(msg.data, "password err\n");
            send(sockfd, &msg, sizeof(msg), 0);
            return -1;
        }
    }
    else //反之未注册
    {
        sprintf(msg.data, "no register\n"); //未注册
        send(sockfd, &msg, sizeof(msg), 0);
        return -1;
    }
}
//查询单词注释
int chatclient(sqlite3 *db, int sockfd) //查询单词函数
{
    char **result = NULL; //数据库返回内容
    char sq1[128];        //保存命令
    sprintf(sq1, "select * from wd1 where word = \"%s\";", msg.data);
    sqlite3_get_table(db, sq1, &result, &hang, &lie, &errmsg); //判断是否查到该单词
    if (hang != 0)
    {
        sprintf(msg.data, "%s", result[3]); //将注释内容发送到客户端
        send(sockfd, &msg, sizeof(msg), 0); //发送该单词注释
        time_t th;
        time(&th); //获取当前时间
        char times[128];
        struct tm *ts;
        ts = localtime(&th); //将当时间转化为标准时间
        sprintf(times, "%4d-%2d-%2d %2d:%2d:%2d", ts->tm_year + 1900, ts->tm_mon + 1, ts->tm_mday, ts->tm_hour, ts->tm_min, ts->tm_sec);
        //先将时间放到一个字符串中,再放到命令语句中
        sprintf(sq1, "insert into \"%s\" values(\"%s\", \"%s\");", msg.name, result[2], times); // 构造插入语句
        int rc = sqlite3_exec(db, sq1, NULL, NULL, &errmsg);                                    //将查询时间保存到数据库中
        if (rc != SQLITE_OK)
        {
            printf("insert err: %s\n", errmsg);
            return -1;
        }
        return 0;
    }
    else //未找到该单词
    {
        sprintf(msg.data, "word unfund\n");
        send(sockfd, &msg, sizeof(msg), 0);
        return -1;
    }
}
int history(sqlite3 *db, int sockfd) //查询历史记录
{
    char **result = NULL;                            //数据库返回内容
    char sq1[128];                                   //保存命令
    sprintf(sq1, "select * from \"%s\";", msg.name); //查询单词查询历史
    sqlite3_get_table(db, sq1, &result, &hang, &lie, &errmsg);
    if (hang != 0)
    {
        for (int j = 0; j < hang; j++) //拼接表内内容到字符数组中
        {
            strcat(msg.data, result[j * lie + 2]);
            strcat(msg.data, " "); //两个表内内容间添加空格间隔
            strcat(msg.data, result[j * lie + 3]);
            strcat(msg.data, "\n"); //两行间换行
        }
        send(sockfd, &msg, sizeof(msg), 0); //发送该单词查询结果
        return 0;
    }
    else if (hang == 0) //历史记录为空
    {
        sprintf(msg.data, "history is void");
        send(sockfd, &msg, sizeof(msg), 0); //发送该单词查询结果
        return 0;
    }
}
int main(int argc, char const *argv[])
{
    // 1.打开或创建数据库
    int rc;
    if (sqlite3_open("./word.db", &db) != SQLITE_OK)
    {
        printf("sqlite3_open err:%s\n", sqlite3_errmsg(db));
        return -1;
    }
    printf("sqlite3_open success\n"); //打开数据库成功
    if (argc < 2)                     //行传参正确
    {
        printf("plase input \n");
        return -1;
    }
    //1.创建套接字,用于链接
    int sockfd;
    int acceptfd; //接收套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) //容错
    {
        perror("socket err");
        return -1;
    }
    //2.绑定 ip+port 填充结构体
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;                   //协议族ipv4
    saddr.sin_port = htons(atoi(argv[1]));        //端口号,htons将无符号短整数hostshort从主机字节顺序到网络字节顺序。
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0"); //ip地址,转化为16进制表示
    socklen_t len = sizeof(saddr);                //结构体大小
    //bind绑定ip和端口
    if (bind(sockfd, (struct sockaddr *)&saddr, len) < 0)
    {
        perror("bind err");
        return -1;
    }
    //3.启动监听,把主动套接子变为被动套接字
    if (listen(sockfd, 6) < 0)
    {
        perror("listen err");
        return -1;
    }
    //4.创建表
    fd_set readfds;    //原表
    fd_set tempfds;    //创建一个临时表,用来保存新表
    FD_ZERO(&readfds); //原表置空
    //5.填表
    FD_SET(sockfd, &readfds); //将要监测的文件描述符插入到表中
    int maxfd = sockfd;       //表内最大描述符
    int ret;
    //6.循环监听 select
    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)) //监听是否有客户端链接
        {
            //阻塞等待客户端的链接请求
            acceptfd = accept(sockfd, (struct sockaddr *)&saddr, &len);
            //获取客户端的ip和端口,(struct sockaddr *)&saddr:用来存放返回的ip,和端口
            if (acceptfd < 0)
            {
                perror("accept err");
                return -1;
            }
            printf("client ip:%s ,port:%d:connect success\n", inet_ntoa(saddr.sin_addr), ntohs(saddr.sin_port));
            //打印已经接入的客户端IP
            FD_SET(acceptfd, &readfds); //将新接入的客户端文件描述符插入到原表中
            if (acceptfd > maxfd)       //如果新插入的文件描述符大于已知最大文件描述符,则更新表内最大文件描述符
            {
                maxfd = acceptfd;
            }
        }
        for (int i = 5; i <= maxfd; i++) //遍历判断是否有信号传输
        {
            if (FD_ISSET(i, &tempfds)) //监测客户端文件描述符
            {
                int ret = recv(i, &msg, sizeof(msg), 0); //接收的信号
                if (ret < 0)                             //接收错误
                {
                    perror("recv err.");
                    return -1;
                }
                else if (ret == 0)
                {
                    printf("%d client exit\n", i); //客户端退出
                    close(i);                      //关闭描述符
                    FD_CLR(i, &readfds);           //删除文件描述符
                }
                else
                {
                    switch (msg.type)
                    {
                    case R: //注册
                        Register(db, i);
                        break;
                    case L: //登录
                        loginclient(db, i);
                        break;
                    case Q: //搜索
                        chatclient(db, i);
                        break;
                    case H: //历史
                        history(db, i);
                        break;
                    default:
                        break;
                    }
                }
            }
        }
    }
    close(sockfd);
    close(acceptfd);
    return 0;
}

云词典客户端

/*客户端创建代码 */
#include 
#include  /* See NOTES */
#include 
#include 
#include  /* superset of previous */
#include 
#include 
#include 
#include 
#include "head.h"
#define N 32
MSG_t msg;
int n; //命令输入
//注册操作
void do_register(int sockfd)
{
    while (1)
    {
        msg.type = R; //注册状态
        printf("请输入您的用户名:");
        scanf("%s", msg.name);
        getchar();                         //回收一个垃圾字符
        if (memcmp(msg.name, "#", 1) == 0) //注册过程中输入#退出注册页面
        {
            printf("退出注册\n");
            break;
        }
        printf("请输入您的密码:");
        scanf("%s", msg.data);
        getchar();
        send(sockfd, &msg, sizeof(msg), 0);
        recv(sockfd, &msg, sizeof(msg), 0); //接收客户端消息,判断是否注册成功
        printf("register:%s\n", msg.data);
        if (memcmp(msg.data, "OK", 2) == 0) //服务器返回的msg.data == OK注册成功
        {
            break;
        }
    }
}
//登录操作
int do_login(int sockfd)
{
    while (1)
    {
        msg.type = L; //登录状态
        printf("请输入您的用户名:");
        scanf("%s", msg.name);
        getchar();
         if (memcmp(msg.data, "#", 1) == 0) //退出查询页面,先判断退出后发送内容
        {
            printf("退出查询\n");
            msg.type = 0;//当发送给服务器为结束内容,让服务器端读出且无法识别type,继续等待事件
            memset(msg.data,0,sizeof(msg.data));//将msg.data中的#清空
            send(sockfd, &msg, sizeof(msg), 0);
            break;
        }
        printf("请输入您的密码:");
        scanf("%s", msg.data);
        getchar();
        send(sockfd, &msg, sizeof(msg), 0);
        recv(sockfd, &msg, sizeof(msg), 0);
        printf("login:%s\n", msg.data);
        if (memcmp(msg.data, "OK", 2) == 0) //服务器返回的msg.data=OK登录成功
        {
            return 1;
        }
    }
}
//查询操作
void do_query(int sockfd)
{
    msg.type = Q;
    while (1)
    {
        printf("请输入你要查询的单词内容:");
        scanf("%s", msg.data);
        getchar();
        if (memcmp(msg.data, "#", 1) == 0) //退出查询页面,先判断退出后发送内容
        {
            printf("退出查询\n");
            msg.type = 0;//当发送给服务器为结束内容,让服务器端读出且无法识别type,继续等待事件
            memset(msg.data,0,sizeof(msg.data));
            send(sockfd, &msg, sizeof(msg), 0);
            break;
        }
        send(sockfd, &msg, sizeof(msg), 0);
        recv(sockfd, &msg, sizeof(msg), 0);
        printf("annotation:%s\n", msg.data); //打印接收的信息/注释
    }
}
//查询历史记录
void do_history(int sockfd)
{
    // memset(msg.data, 0, sizeof(msg.data));
    msg.type = H;
    send(sockfd, &msg, sizeof(msg), 0);
    recv(sockfd, &msg, sizeof(msg), 0); //接收信息,接一次打一次.
    printf("%s\n", msg.data);
}
//主函数
int main(int argc, char const *argv[])
{
    if (argc < 3)
    {
        printf("plase input \n");
        return -1;
    }
    //1.创建套接字,用于链接
    int sockfd;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket err");
        return -1;
    }
    //文件描述符 0 -> 标准输入  1->标准输出  2->标准出错  3->socket
    printf("sockfd:%d\n", sockfd);
    //2.绑定 ip+port 填充结构体
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;                 //协议族ipv4
    saddr.sin_port = htons(atoi(argv[2]));      //端口号,htons将无符号短整数hostshort从主机字节顺序到网络字节顺序。
    saddr.sin_addr.s_addr = inet_addr(argv[1]); //ip地址,转化为16进制表示
    socklen_t len = sizeof(saddr);              //结构体大小
    //3用于连接服务器;
    if (connect(sockfd, (struct sockaddr *)&saddr, len) < 0)
    {
        perror("connect err");
        return -1;
    }
    int flag = 0;
    while (1)
    {
        printf("*******************************************************\n");
        printf("*                                             *\n");
        printf("*               1: 注册  2: 登录  3:  退出              *\n");
        printf("*******************************************************\n");
        printf("请输入命令:");
        scanf("%d", &n);
        getchar();
        switch (n)
        {
        case 1:
            do_register(sockfd);
            break;
        case 2:
            if (do_login(sockfd) == 1)
            {
                while (1)
                {
                    if (flag == 1)
                    {
                        flag = 0;
                        break;
                    }
                    printf("*******************************************************\n");
                    printf("*                                             *\n");
                    printf("*              1: 查询单词  2: 历史记录 3:  退出          *\n");
                    printf("*******************************************************\n");
                    printf("请输入命令:");
                    scanf("%d", &n);
                    getchar();
                    switch (n)
                    {
                    case 1:
                        do_query(sockfd);
                        break;
                    case 2:
                        printf("search history\n");
                        do_history(sockfd);
                        break;
                    case 3:
                        flag = 1;
                        break;
                    default:
                        break;
                    }
                }
            }
            break;
        case 3:
            return 0;
        default:
            break;
        }
    }
    close(sockfd);
    return 0;
}

你可能感兴趣的:(网络编程,数据库,tcp/ip,sqlite,linux,网络协议,开发语言)