基于TCP的在线词典

先创建一个名为 my.db 的数据库

然后运用SQL语句建立两张表

建表语句:
CREATE TABLE usr (name text PRIMARY KEY, pass TEXT);
CREATE TABLE record (name TEXT, date TEXT, word TEXT);

功能演示

1:注册用户选项

2:登录选项

3:退出选项

第一步先注册一个用户:

第二步使用刚注册用户名和密码登录:

 执行完登录操作后界面会跳转到查询单词的界面

1:查询单词的选项

2:查询历史记录的选项

3:退出选项

选择1选项,输入想要搜索的单词

选择2选项,则会出现历史的记录

 流程图:

客户端的流程图

客户端的流程图

代码实现:

客户端代码

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

#define N 16 // 用户名数组的大小
#define R 1 //  用户注册
#define L 2 //  用户登录
#define Q 3 //  查询单词
#define H 4 //  查询历史记录

#define DATABASE "my.db" //数据库名字

typedef struct
{
    int type; //标志位(协议)
    char name[N]; //用户名
    char data[256]; // 密码或者单词
} MSG;

void do_register(int socketfd, MSG* msg); //用户注册函数声明
int do_login(int socketfd, MSG* msg); //用户登录函数声明
void do_query(int socketfd, MSG* msg); //查询单词函数声明
void do_history(int socketfd, MSG* msg); //查询历史记录函数声明

int main(int argc, char* argv[])
{
    int socketfd; //定义套接字
    struct sockaddr_in server_addr; //定义填充服务器网络信息的结构体
    MSG msg; //定义结构体(数据包)
    if (argc < 3) { //判断终端输入参数是否合理
        printf("Usage : %s <serv_ip> <serv_port>\n", argv[0]); //打印错误信息
        exit(-1);
    }
    //创建流式套接字
    if (-1 == (socketfd = socket(PF_INET, SOCK_STREAM, 0))) {
        perror("fail to socket"); //打印错误信息
        exit(-1);
    }

    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = PF_INET; //填充服务器网络信息结构体
    server_addr.sin_addr.s_addr = inet_addr(argv[1]); // IP地址
    server_addr.sin_port = htons(atoi(argv[2])); //端口号
    //用connect和服务器创建连接
    if (-1 == connect(socketfd, (struct sockaddr*)&server_addr, sizeof(server_addr))) {
        perror("fail to connect"); //打印错误信息
        exit(-1);
    }
    int choose; //定义选择变量
    //在终端打印菜单
    while (1) {
        printf("************************************\n");
        printf("* 1: register   2: login   3: quit *\n");
        printf("************************************\n");
        printf("please choose : ");
        //用户输入选择
        if (scanf("%d", &choose) <= 0) {
            perror("scanf"); //打印错误信息
            exit(-1);
        }
        //根据选择判断执行那些操作
        switch (choose) {
        case 1:
            do_register(socketfd, &msg); //调用用户注册函数
            break;
        case 2:
            if (do_login(socketfd, &msg) == 1) { //调用用户登录函数
                goto NEXT; //登陆成功后跳转到查找单词的界面
            }
            break;
        case 3:
            close(socketfd); //关闭套接字
            exit(0);
        }
    }
NEXT:
    //查找单词的界面
    while (1) {
        printf("************************************\n");
        printf("* 1: query   2: history    3: quit *\n");
        printf("************************************\n");
        printf("please choose : ");
        //用户输入选择
        if (scanf("%d", &choose) <= 0) {
            perror("scanf");
            exit(-1);
        }
        //根据选择判断执行那些操作
        switch (choose) {
        case 1:
            do_query(socketfd, &msg); //调用查询单词函数
            break;
        case 2:
            do_history(socketfd, &msg); //调用查询历史记录函数
            break;
        case 3:
            close(socketfd); //关闭套接字
            exit(0);
        }
    }
    return 0;
}

//注册函数
void do_register(int socketfd, MSG* msg)
{

    msg->type = R; //将数据包的类型定义为R(注册)

    printf("input your name:");
    scanf("%s", msg->name); //从终端输入用户名

    printf("input your password:");
    scanf("%s", msg->data); //在终端输入密码

    //给服务器发送数据包
    if (-1 == send(socketfd, msg, sizeof(MSG), 0)) {
        perror("send error"); //打印错误信息
        return;
    }
    //接收服务器发来的数据包
    if (-1 == recv(socketfd, msg, sizeof(MSG), 0)) {
        perror("send error"); //打印错误信息
        return;
    }
    //打印提示信息
    printf("register : %s\n", msg->data);
    return;
}

//登录函数
int do_login(int socketfd, MSG* msg)
{
    msg->type = L; //将数据包的类型定义为L(登录)

    printf("input your name:");
    scanf("%s", msg->name); //从终端输入用户名

    printf("input your password:");
    scanf("%s", msg->data); //在终端输入密码

    //给服务器发送数据包
    if (-1 == send(socketfd, msg, sizeof(MSG), 0)) {
        perror("send error"); //打印错误信息
        return-1;
    }
    //接收服务器发来的数据包
    if (-1 == recv(socketfd, msg, sizeof(MSG), 0)) {
        perror("send error"); //打印错误信息
        return-1;
    }
    //比较发来的数据判断是否登陆成功
    if (strncmp(msg->data, "OK", 3) == 0) {
        printf("login : OK\n"); //打印msg中data的信息
        return 1;
    }
    //打印msg中data的信息
    printf("login : %s\n", msg->data);
    return 0;
}

//查找单词的函数
void do_query(int socketfd, MSG* msg)
{
    msg->type = Q; //将数据包的类型定义为Q(查找单词)
    //循环查找
    while (1) {
        printf("input your (if # is end):");
        scanf("%s", msg->data); //输入想要查找的单词
        //如果不想查找了可用“#”退出
        if (0 == strcmp(msg->data, "#")) {
            break;
        }
        //给服务器发送数据包
        if (-1 == send(socketfd, msg, sizeof(MSG), 0)) {
            perror("send error"); //打印错误信息
            return;
        }
        //接收服务器发来的数据包
        if (-1 == recv(socketfd, msg, sizeof(MSG), 0)) {
            perror("send error"); //打印错误信息
            return;
        }
        //打印msg中data的信息
        printf("query : %s\n", msg->data);
    }
    return;
}

//查询历史纪录的函数
void do_history(int socketfd, MSG* msg)
{
    msg->type = H; //将数据包的类型定义为H(查询历史记录)

    //给服务器发送数据包
    if (-1 == send(socketfd, msg, sizeof(MSG), 0)) {
        perror("send error"); //打印错误信息
        return;
    }

    while (1) {

        //接收服务器发来的数据包
        if (-1 == recv(socketfd, msg, sizeof(MSG), 0)) {
            perror("send error"); //打印错误信息
            return;
        }
        //判断 当接收到OVER时就表明服务器的数据已发完
        if (!strcmp(msg->data, "OVER")) {
            break;
        }
        //循环打印发送来的数据
        printf("%s\n", msg->data);
    }
    return;
}

 服务器代码

#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <signal.h>
#include <sqlite3.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>

#define N 16 // 用户名数组的大小
#define R 1 //  用户注册
#define L 2 //  用户登录
#define Q 3 //  查询单词
#define H 4 //  查询历史记录

#define DATABASE "my.db" //数据库名字

typedef struct
{
    int type; //标志位(协议)
    char name[N]; //用户名
    char data[256]; // 密码或者单词
} MSG;

void do_register(int connectfd, MSG* msg, sqlite3* db); //用户注册函数声明
void do_login(int connectfd, MSG* msg, sqlite3* db); //用户登录函数声明
void do_query(int connectfd, MSG* msg, sqlite3* db); //查询单词函数声明
void do_history(int connectfd, MSG* msg, sqlite3* db); //查询历史记录函数声明
void do_client(int connectfd, sqlite3* db); //解析协议函数声明
int do_searchword(int connectfd, MSG* msg); //在文件中查询单词的函数声明
void getdata(char data[]); //获取查询单词的时间的函数声明

int history_callback(void* arg, int f_num, char** f_value, char** f_name); //回调函数声明

void handler(int sig) //回收子进程资源函数
{
    wait(NULL);
}

int main(int argc, char* argv[])
{
    int listenfd, connectfd; //定义套接字
    struct sockaddr_in server_addr; //定义填充服务器网络信息的结构体
    pid_t pid; //用于创建父子进程
    sqlite3* db; //数据库句柄

    if (argc < 3) { //判断终端输入参数是否合理
        printf("Usage : %s <ip> <port>\n", argv[0]);//打印错误信息
        exit(-1);
    }

    if (sqlite3_open(DATABASE, &db) != SQLITE_OK) { //有就打开数据库,没有就创建
        printf("error : %s\n", sqlite3_errmsg(db)); //打印错误信息
        exit(-1);
    }

    if ((listenfd = socket(PF_INET, SOCK_STREAM, 0)) < 0) { //创建流式套接字
        perror("fail to socket"); //打印错误信息
        exit(-1);
    }

    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = PF_INET; //填充服务器网络信息结构体
    server_addr.sin_addr.s_addr = inet_addr(argv[1]); // IP地址
    server_addr.sin_port = htons(atoi(argv[2])); //端口号

    int on = 1;
    //设置允许端口复用
    if (-1 == setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) {
        perror("setsockopt error"); //打印错误信息
        exit(-1);
    }
    //套接字和服务器网络信息结构体绑定
    if (bind(listenfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        perror("fail to bind"); //打印错误信息
        exit(-1);
    }

    if (listen(listenfd, 5) == -1) { //设置监听状态
        perror("fail to listen"); //打印错误信息
        exit(-1);
    }

    signal(SIGCHLD, handler); //处理僵尸进程

    while (1) {
        if ((connectfd = accept(listenfd, NULL, NULL)) < 0) { //创建对接客户的流式套接字
            perror("fail to accept"); //打印错误信息
            exit(-1);
        }

        if ((pid = fork()) < 0) {
            perror("fail to fork"); //打印错误信息
            exit(-1);
        } else if (pid == 0) //子进程执行处理代码
        {
            do_client(connectfd, db); //解析协议函数的调用
        } else //父进程负责连接
        {
            close(connectfd); //关闭对接客户的流式套接字
        }
    }
    return 0;
}

void do_client(int connectfd, sqlite3* db) //解析协议函数
{
    MSG msg; //定义结构体(数据包)
    while (recv(connectfd, &msg, sizeof(MSG), 0) > 0) // receive request
    {
        printf("type = %d\n", msg.type); //在服务器上显示包的类型(可不要)
        printf("type = %s\n", msg.data); //在服务器上显示输入的内容(可不要)
        switch (msg.type) { //判断包的类型并决定下一步操作
        case R:
            do_register(connectfd, &msg, db); //调用用户注册函数
            break;
        case L:
            do_login(connectfd, &msg, db); //调用用户登录函数
            break;
        case Q:
            do_query(connectfd, &msg, db); //调用查询单词函数
            break;
        case H:
            do_history(connectfd, &msg, db); //调用查询历史记录的函数
            break;
        }
    }
    printf("client quit\n"); //上面的类型都没有则打印client quit
    exit(0);
    return;
}

void do_register(int connectfd, MSG* msg, sqlite3* db)
{

    char sqlstr[521] = { 0 }; //作为容器承载字符串(尽量大一点不然会出现警告)
    //组装插入usr表的数据库的命令
    sprintf(sqlstr, "INSERT INTO usr VALUES('%s','%s')", msg->name, msg->data);
    //执行sql语句的函数
    if (SQLITE_OK != sqlite3_exec(db, sqlstr, NULL, NULL, NULL)) {
        //如果执行错误就先暂定为是用户已存在造成的错误
        //将“用户已存在”写入msg的data中
        strcpy(msg->data, "用户已存在");
        //给客户端发送数据包
        if (-1 == send(connectfd, msg, sizeof(MSG), 0)) {
            perror("send error"); //打印错误信息
            return;
        }
    } else {
        //将“OK”写入msg的data中
        strcpy(msg->data, "OK");
        //给客户端发送数据包
        if (-1 == send(connectfd, msg, sizeof(MSG), 0)) {
            perror("send error"); //打印错误信息
            return;
        }
    }
}

void do_login(int connectfd, MSG* msg, sqlite3* db)
{
    char sqlstr[521] = { 0 }; //作为容器承载字符串(尽量大一点不然会出现警告)
    char** result = NULL; //查询的结果
    int row = 0; //成功返回的行数
    int column = 0; //成功返回的列数
    //组装查询usr表的数据库的命令
    sprintf(sqlstr, "SELECT * FROM usr WHERE name='%s' AND pass='%s'", msg->name, msg->data);
    //执行sql语句的函数并可以将结果打印到终端
    if (SQLITE_OK != sqlite3_get_table(db, sqlstr, &result, &row, &column, NULL)) {
        printf("error : %s\n", sqlite3_errmsg(db)); //打印错误信息
        return;
    }
    //利用返回的行数判断是否有此用户并且密码正确
    if (row != 0) {
        strcpy(msg->data, "OK"); //如果有此用户并且密码正确则将“OK”写入msg的data中
    } else {
        //如果没有此用户或者密码不正确则将“name or password is wrony!!!”写入msg的data中
        strcpy(msg->data, "name or password is wrony!!!");
    }
    //给客户端发送数据包
    if (-1 == send(connectfd, msg, sizeof(MSG), 0)) {
        perror("send error"); //打印错误信息
    }
    return;
}

void do_query(int connectfd, MSG* msg, sqlite3* db)
{
    char sqlstr[256] = { 0 }; //作为容器承载字符串(尽量大一点不然会出现警告)
    char* errmsg = NULL; //定义一个指针指向错误信息
    int found = 0; //用于接收do_searchword函数的返回值
    char date[256] = { 0 }; //存放单词的解释
    char word[128] = { 0 }; //保存单词

    strcpy(word, msg->data); //将单词保存在word中,便于放入历史纪录的表中

    found = do_searchword(connectfd, msg); //调用在文件中查询单词的函数
    //执行成功,将保存历史记录
    if (found == 1) {
        getdata(date); //调用获取查询单词的时间的函数
        //组装插入到record表的数据库的命令
        sprintf(sqlstr, "INSERT INTO record VALUES('%s', '%s', '%s')", msg->name, date, word);
        //执行sql语句的函数
        if (sqlite3_exec(db, sqlstr, NULL, NULL, &errmsg) != SQLITE_OK) {
            printf("error : %s\n", errmsg); //打印错误信息
        }
    }
    //给客户端发送数据包
    if (-1 == send(connectfd, msg, sizeof(MSG), 0)) {
        perror("send error"); //打印错误信息
    }
    //释放errmsg
    sqlite3_free(errmsg);
    return;
}

int do_searchword(int connectfd, MSG* msg)
{
    FILE* fp; //创建标准IO文件描述符
    char temp[300] = { 0 }; //容纳一行的单词及单词的解释
    char* p = NULL; //定义一个一级指针用来截取单词的解释
    int len; //用来保存单词的长度
    int result; //用来接比较函数的返回值

    len = strlen(msg->data); //计算并保存单词的长度
    //打开保存单词及其解释的文件
    if ((fp = fopen("dict.txt", "r")) == NULL) {
        //将打开文件失败的信息写入到msg的data中
        strcpy(msg->data, "dict can not open");
        //给客户端发送数据包
        if (-1 == send(connectfd, msg, sizeof(MSG), 0)) {
            perror("send error"); //打印错误信息
            return -1;
        }
    }
    int flags = 0;
    //每次读取一行内容
    while (fgets(temp, 300, fp) != NULL) {
        //比较len个长度看是否相等,相等返回0
        result = strncmp(msg->data, temp, len);
        //当result等于0并且单词的后一位为空格时,则说明匹配到了
        if (result == 0 && temp[len] == ' ') {
            //将p定位到单词后一位的空格
            p = temp + len;
            //让p循环++定位到单词解释的第一位上
            while (*p == ' ') {
                p++;
            }
            //将单词的解释写入到msg的data中
            strcpy(msg->data, p);

            fclose(fp); //关闭标准IO的文件描述符
            return 1; //单词匹配成功返回1
        }
    }
    //将"not found"写入到msg的data中
    strcpy(msg->data, "not found");

    fclose(fp); //关闭标准IO的文件描述符
    return 0; //单词匹配失败返回
}

void getdata(char* data)
{
    //定义获取时间的变量
    time_t t;
    //定义结构体指针变量
    struct tm* tp;
    //调用获取秒数的函数
    time(&t);
    //调用转换函数,将秒数转换为年月日时分秒
    tp = localtime(&t);
    //将获取的时间写入到data中
    sprintf(data, "%4d-%02d-%02d %02d:%02d:%02d", 1900 + tp->tm_year, 1 + tp->tm_mon, tp->tm_mday,
        tp->tm_hour, tp->tm_min, tp->tm_sec);
}

void do_history(int connectfd, MSG* msg, sqlite3* db)
{
    //作为容器承载字符串(尽量大一点不然会出现警告)
    char sqlstr[256] = { 0 };
    //定义一个指针指向错误信息
    char* errmsg;
    //组装查询record表的数据库的命令
    sprintf(sqlstr, "SELECT * FROM record WHERE name='%s'", msg->name);
    //执行sql语句的函数
    if (SQLITE_OK != sqlite3_exec(db, sqlstr, history_callback, &connectfd, &errmsg)) {
        //打印错误信息
        printf("error : %s\n", errmsg);
        //释放errmsg
        sqlite3_free(errmsg);
        return;
    }
    //发送完成后将OVER写入到msg的data中,让客户端接收到发送完成的指令
    strcpy(msg->data, "OVER");
    //给客户端发送数据包
    if (-1 == send(connectfd, msg, sizeof(MSG), 0)) {
        //打印错误信息
        perror("send error");
    }
    return;
}

int history_callback(void* arg, int ncolumn, char** f_value, char** f_name)
{
    //将给回调函数传递的参数接一下并强转成int型
    int connectfd = *(int*)arg;
    //定义一个结构体变量(数据包)
    MSG msg;
    memset(&msg, 0, sizeof(MSG)); //清空结构体
    //有几行历史记录就调用几次回调函数
    //将时间和单词的字段写入到msg的data中
    sprintf(msg.data, "%s:%s", f_value[1], f_value[2]);
    //给客户端发送数据包
    if (-1 == send(connectfd, &msg, sizeof(MSG), 0)) {
        //打印错误信息
        perror("send error");
        return -1;
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值