项目:TCP在线云词典

news/2024/7/7 21:25:34

在这里插入图片描述

一.要求

1.搭建的框架环境中实现并发,实现多个用户同时查询的功能。
2.服务器分别保存每个用户的使用记录,客户端可以查询日志的功能。
3.基本的查询单词的功能。
4.密码验证的功能,实现登录验证账号和密码是否正确。

二.流程和框架

框架
在这里插入图片描述

客户端
![2023-09-03T10:44:22.png][2]

服务器
在这里插入图片描述

三.思路

1.首先你要准备好单词文件,用于英语单词的查询。
dict.txt

2.该项目涉及多并发问题,可以使用多进程,多线程,IO多路复用中的一种,我这里采用IO多路复用的select实现。

3.实现日志功能,需要建立一个数据库,为每一个用户建立一个表,表中存储用户的各种记录。
4.密码验证,同样使用数据库完成,每次登录时,将和数据库中所有的用户信息比较,若账号密码正确,即可登录。

在这里插入图片描述
Linux的IO多路复用是一种高效的IO处理机制,通过允许一个线程同时监控多个文件描述符(包括套接字、管道等)的IO事件,从而避免了传统的多线程或多进程方式中的频繁的上下文切换和资源消耗。在Linux系统中,IO多路复用主要基于以下三种机制:select、poll和epoll。

  1. select:select是最早引入的IO多路复用机制之一。它通过select系统调用来监控多个文件描述符上的IO事件,一旦有IO事件发生,就会通知应用程序进行处理。然而,select的一个缺点是每次调用都需要将所有的文件描述符从应用程序空间复制到内核空间,造成资源浪费。

  2. poll:poll是对select的改进,它也能够监控多个文件描述符上的IO事件,并将有IO事件发生的文件描述符返回给应用程序。与select不同的是,poll使用了链表数据结构来存储文件描述符,减少了在内核空间和应用程序空间之间的数据复制。

  3. epoll:epoll是Linux特有的高性能IO多路复用机制。它通过epoll系统调用来注册、注销和监控文件描述符上的IO事件。epoll采用事件驱动的方式,只会返回有IO事件发生的文件描述符,避免了无效的遍历和资源浪费。此外,epoll还提供了三种工作模式:EPOLL_CTL_ADD(添加文件描述符)、EPOLL_CTL_MOD(修改文件描述符)和EPOLL_CTL_DEL(删除文件描述符),更加灵活和高效。

IO多路复用在网络编程中特别有用,可以用于实现高并发的服务器。通过IO多路复用,可以在单线程或少量线程的情况下同时处理多个连接的IO事件,提高服务器的并发性能和效率。

总结而言,Linux的IO多路复用是一种有效的IO处理机制,通过select、poll和epoll等机制,可以实现高效的监控和处理多个IO事件,提高系统的并发性能和效率。
四.具体实现代码

head.h

#ifndef __HEAD_H__
#define __HEAD_H__
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>


/* 消息对应的结构体(同一个协议) */
typedef struct msg_t
{
    int type;       
    char name[32];  //用户名
    char text[256]; //消息正文
    char password[32]; //密码
} MSG_t;


enum type_t
{
  my__register=1,  //注册
  login,         //连接  
  word,          //查询单词
  history,       //查询日志   
  quit,      
};
#endif

client.c

#include "head.h"
struct sockaddr_in saddr;
int socked;
MSG_t msg;
//注册帐号函数
void my_register()
{
    MSG_t msg;
    msg.type = my__register;
    printf("%d\n", msg.type);
    printf("请输入帐号昵称:\n");
    scanf("%s", msg.name);
    printf("请输入帐号密码:\n");
    scanf("%s", msg.password);
    send(socked, &msg, sizeof(msg), 0);
    printf("sendis ok\n");
    int flage = recv(socked, &msg, sizeof(msg), 0);
    if (flage < 0)
    {
        perror("recv is err");
        return;
    }
    else
    {
        printf("%s\n", msg.text);
    }
}

//连接帐号函数,判断是否连接成功
int my_login()
{
    msg.type = login;
    printf("请输入帐号昵称:\n");
    scanf("%s", msg.name);
    printf("请输入帐号密码:\n");
    scanf("%s", msg.password);
    send(socked, &msg, sizeof(msg), 0);
    printf("send id ok\n");
    int flage = recv(socked, &msg, sizeof(msg), 0);
    if (flage < 0)
    {
        perror("recv is err");
        return -1;
    }
    else
    {
        printf("%s\n",msg.text);
        if (strncmp(msg.text, "ok", 2) == 0)
        {
            printf("登录成功\n");
            return 1;
        }
        else
        {
            printf("登录失败\n");
            return 0;
        }
    }
}

//查询单词函数
void my_query_word()
{
    msg.type = word;
    printf("输入quit退出\n");
    getchar();
    while (1)
    {
        msg.type = word;
        fgets(msg.text, sizeof(msg.text), stdin);
        if (msg.text[strlen((msg.text)) - 1] == '\n')
            msg.text[strlen((msg.text)) - 1] = '\0';
        if (strcmp(msg.text, "quit") == 0)
            return;
        printf("要查询的单词:%s\n",msg.text);
        send(socked, &msg, sizeof(msg), 0);
        int flage = recv(socked, &msg, sizeof(msg), 0);
        if (flage < 0)
        {
            perror("recv is err");
            return;
        }
        else
        {
            printf("%s", msg.text);
            printf("\n");
        }
    }
}

//查询日志函数
void my_history_record()
{
    msg.type = history;
    printf("\n请再次帐号密码:");
    scanf("%s", msg.password);
    send(socked, &msg, sizeof(msg), 0);
    while (1)
    {
        int flage = recv(socked, &msg, sizeof(msg), 0);
        if (flage < 0)
        {
            perror("recv is err");
            return;
        }
        else
        {
            if (strcmp(msg.text, "quit") == 0)
            {
                break;
            }
            printf("%s", msg.text);
        }
    }
    printf("************************************\n");
}

//查询函数,查询单词或者日志
void my_send()
{
    int choose;
    while (1)
    {
        printf("************************************\n");
        printf("* 1: query_word 2: history_record 3: quit *\n");
        printf("************************************\n");
        printf("请输入你的选择:");
        scanf("%d", &choose);
        switch (choose)
        {
        case 1:
            my_query_word();
            break;
        case 2:
            my_history_record();
            break;
        case 3:
            return;
        }
    }
}

int main(int argc, char const *argv[])
{
    //1.创建套接字,IPv4
    socked = socket(AF_INET, SOCK_STREAM, 0);
    if (socked < 0)
    {
        perror("socket is err\n");
        return -1;
    }

    //2.connect服务器
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[2]));
    saddr.sin_addr.s_addr = inet_addr(argv[1]);
    if ((connect(socked, (struct sockaddr *)&saddr, sizeof(saddr))) < 0)
    {
        perror("connect is err");
        return -1;
    }

    //3.选择注册,登录,退出
    int choose;
    while (1)
    {
        printf("************************************\n");
        printf("* 1: register 2: login 3: quit *\n");
        printf("************************************\n");
        printf("请输入你的选择:");
        scanf("%d", &choose);
        switch (choose)
        {
        case 1:
            my_register();
            break;
        case 2:
            if (my_login())
                my_send();
            break;
        case 3:
            close(socked);
            exit(0);
        }
    }
    close(socked);
    return 0;
}

server.c

#include "head.h"
#include <sqlite3.h>
#include <sys/select.h>
#include <time.h>
int sockfp, acceptfp, flage, sockfd, flages;
MSG_t msg, msg1;
sqlite3 *db = NULL;

//读日志函数,发送给客户端
void my_history()
{
    char **result;
    int hang, lie;
    char *errmasg;
    char buf[1024];
    sprintf(buf, "select * from %s_%s;", msg.name, msg.password);
    sqlite3_get_table(db, buf, &result, &hang, &lie, &errmasg);
    printf("开始发送日志\n");
    for (int i = 0; i <= hang; i++)
    {
        for (int j = 0; j < lie; j++)
        {

            sprintf(msg.text, "%s\t", result[i * lie + j]);
            send(sockfd, &msg, sizeof(msg), 0);
        }
        strcpy(msg.text, "\n");
        send(sockfd, &msg, sizeof(msg), 0);
    }
    strcpy(msg.text, "quit");
    send(sockfd, &msg, sizeof(msg), 0);
}

//写日志
void write_history()
{
    time_t current_time;
    struct tm *local_time;
    char time_string[100];
    char perate[100];
    char buf[1024];
    char *errmsg;
    // 获取当前时间戳
    current_time = time(NULL);

    // 将时间戳转换为本地时间
    local_time = localtime(&current_time);

    // 格式化本地时间字符串
    strftime(time_string, sizeof(time_string), "%Y-%m-%d %H:%M:%S", local_time);
    switch (msg1.type)
    {
    case my__register:
        strcpy(perate, "register");
        break;
    case login:
        strcpy(perate, "login");
        break;
    case word:
        strcpy(perate, "word");
        break;
    default:
        strcpy(perate, "quit");
        break;
    }
    sprintf(buf, "insert into %s_%s values(\"%s\",\"%s\");", msg1.name, msg1.password, time_string, perate);
    if ((sqlite3_exec(db, buf, NULL, NULL, &errmsg)) != SQLITE_OK)
    {
        fprintf(stderr, "sqlite3_exec is err %s", errmsg);
        return;
    }
}

int callback(void *data, int argc, char **argv, char **azColName)
{
    printf("调用callback函数\n");
    printf("%d\n", argc);
    char *knownTableName = (char *)data;
    for (int i = 0; i < argc; i++)
    {

        printf("%s\n", argv[i]);
        if (strcmp(argv[i], knownTableName) == 0)
        {
            strcpy(msg.text, "ok");
            send(sockfd, &msg, sizeof(msg), 0);
            printf("send is ok ok\n");
            printf("%s已登录成功\n", argv[i]);
            flages = 1;
            return 0;
        }
    }
    // strcpy(msg.text, "no");
    // send(sockfd, &msg, sizeof(msg), 0);
    // printf("send is ok\n");
    return 0;
}

//注册函数
void my_register()
{
    char buf[512];
    char *errmsg;

    sprintf(buf, "create table %s_%s(time char,perate char);", msg.name, msg.password);
    printf("%s\n", buf);
    if ((sqlite3_exec(db, buf, NULL, NULL, &errmsg)) != SQLITE_OK)
    {
        fprintf(stderr, "sqlite3_exec is err %s", errmsg);
        strcpy(msg.text, "注册失败\n");
        send(sockfd, &msg, sizeof(msg), 0);
        return;
    }
    else
        strcpy(msg.text, "注册成功\n");
    send(sockfd, &msg, sizeof(msg), 0);
    printf("send is ok\n");
}

//连接函数只有昵称和密码都匹配才能连接
void my_login()
{
    printf("调用连接函数\n");
    char buf[512];
    char *errmsg;
    char knownTableName[512];
    int rc;
    sprintf(knownTableName, "%s_%s", msg.name, msg.password);
    strcpy(buf, "SELECT name FROM sqlite_master WHERE type='table';");
    printf("%s\n", buf);
    rc = sqlite3_exec(db, buf, callback, knownTableName, &errmsg);
    if (rc != SQLITE_OK)
    {
        fprintf(stderr, "SQL error: %s\n", errmsg);
    }
    if (!flages)
    {
        strcpy(msg.text, "no");
        send(sockfd, &msg, sizeof(msg), 0);
    }
    //sleep(1);
}

//查询单词函数
void my_word()
{
    char buf[2048] = {0};
    int len = strlen(msg.text), flage = 0;
    FILE *fp = fopen("dict.txt", "r");

    if (fp == NULL)
    {
        perror("fopen is err");
        strcpy(msg.text, "fopen is err");
        send(sockfd, &msg, sizeof(msg), 0);
        return;
    }

    while (fgets(buf, sizeof(buf), fp))
    {

        if ((strncmp(msg.text, buf, len)) == 0)
        {
            flage = 1;
            break;
        }
    }
    int i;
    printf("len=%d\n", len);
    for (i = len; i < 2048; ++i)
    {
        if (buf[i] != ' ')
            break;
    }
    //printf("%s\n",buf);
    printf("%s\n", msg.text);
    if (flage)
    {
        strcpy(msg.text, buf + i);
    }
    else
    {
        strcpy(msg.text, "没有这个单词");
    }
    send(sockfd, &msg, sizeof(msg), 0);
}

int main(int argc, char const *argv[])
{
    //1.创建或打开数据库
    if ((sqlite3_open("./dict.db", &db)) < 0)
    {
        fprintf(stderr, "sqlite3_open id err %s\n", sqlite3_errmsg(db));
        return -1;
    }

    //2.创建socket套接字
    sockfp = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfp < 0)
    {
        perror("socket is err");
        return -1;
    }

    //3.绑定服务器ip和端口号
    struct sockaddr_in saddr, caddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[1]));
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    socklen_t len = sizeof(struct sockaddr_in);

    if (bind(sockfp, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
    {
        perror("bind is err");
        return -1;
    }

    //4.listen监听
    if (listen(sockfp, 90))
    {
        perror("liste err");
        return -1;
    }

    //4. select多路复用
    //4.1 创建表
    fd_set readfds, tempfds;
    //4.2 清空表
    FD_ZERO(&readfds);
    FD_ZERO(&tempfds);
    //4.3 将关心的文件描述符添加表
    FD_SET(0, &readfds);
    FD_SET(sockfp, &readfds);

    int maxfd = sockfp;
    while (1)
    {
        tempfds = readfds;
        //4.4 select检测   阻塞
        select(maxfd + 1, &tempfds, NULL, NULL, NULL);
        //4.5 进行相应的逻辑处理

        //sockfp,监听套接字响应证明,有客户端要链接
        if (FD_ISSET(sockfp, &tempfds))
        {
            acceptfp = accept(sockfp, (struct sockaddr *)&caddr, &len);
            if (acceptfp < 0)
            {
                perror("acceptfp");
                exit(0);
            }
            printf("连接到%d端口号\n", acceptfp);
            printf("port:%d   ip:  %s\n", ntohs(caddr.sin_port), inet_ntoa(caddr.sin_addr));
            FD_SET(acceptfp, &readfds);
            if (acceptfp > maxfd)
                maxfd = acceptfp;
        }

        //检测客户端,检查是哪一个客户端发送的消息
        for (int i = 5; i <= maxfd; ++i)
        {
            if (FD_ISSET(i, &tempfds))
            {
                sockfd = i;
                printf("端口号%d相应\n", i);
                flage = i;
                int recvbyte = recv(i, &msg, sizeof(msg), 0);
                if (recvbyte < 0)
                {
                    perror("recv err");
                    return -1;
                }
                else if (recvbyte == 0)
                {
                    msg.type = quit;
                    write_history();
                    close(i);
                    FD_CLR(i, &readfds);
                    if (i == maxfd)
                        --maxfd;
                }
                else
                {
                    printf("recvbyte is ok\n");
                    msg1 = msg;

                    printf("******************\n");
                    switch (msg.type)
                    {
                    case my__register:
                        my_register();
                        write_history();
                        break;
                    case login:
                        my_login();
                        write_history();
                        break;
                    case word:
                        my_word();
                        write_history();
                        break;
                    case history:
                        my_history();
                        write_history();
                        break;
                    }
                }
            }
        }
    }

    return 0;
}


http://lihuaxi.xjx100.cn/news/1546327.html

相关文章

Java笔记:Greys-Java线上异常排查

1.软件安装与启动 Greys支持在线安装和本地安装两种安装方案&#xff0c;安装即可用&#xff0c;推荐使用在线安装。 1.1 在线安装&#xff08;推荐&#xff09; 请复制以下内容&#xff0c;并粘贴到命令行中。 curl -sLk http://ompc.oss.aliyuncs.com/greys/install.sh|b…

C语言中的宏定义和内联函数有什么区别?

宏定义&#xff08;Macro Definition&#xff09;和内联函数&#xff08;Inline Function&#xff09;是C语言中两种不同的代码优化和抽象技术&#xff0c;它们在功能和用法上有一些区别。本文将详细介绍宏定义和内联函数的特点、优缺点以及适用场景&#xff0c;以便初学者更好…

[CocosCreator]自定义事件(订阅/发布)管理器

欢迎喜欢或者从事CocosCreator开发的小伙伴请加入我的大家庭CocosCreator游戏开发Q群:26855530 首先,小伙伴应该知道CocosCreator本身已经实现了自己一套事件传播机制的,例如: this.node.on(foobar, this._sayHello, this); cc.game.on("foobar", this._sayHello, t…

指引型树型组件的封装

最近&#xff0c;由于业务的需要&#xff0c;需要做一个指向形树型组件。在寻找各种文章后&#xff0c;终于有了思路。&#x1f912;&#x1f912;&#x1f912; 树型组件的思路主要是递归。谈到递归&#xff0c;我们首先要有递归的出口。递归的出口就是没有孩子节点了。这个时…

免费音乐下载网站分享(MP3文件格式)

免费音乐下载网站分享&#xff08;MP3文件格式&#xff09; 最近需要下载一些歌曲&#xff0c;发现很多音乐app上下载文件都需要vip&#xff0c;再上网查询了一番&#xff0c;最后发现了一个宝藏网站&#xff0c;可以免费下载各种格式的MP3文件&#xff0c;在这里给大家分享一…

头条移动端项目Day08 —— 定时计算热点文章、XXL-JOB

❤ 作者主页&#xff1a;欢迎来到我的技术博客&#x1f60e; ❀ 个人介绍&#xff1a;大家好&#xff0c;本人热衷于Java后端开发&#xff0c;欢迎来交流学习哦&#xff01;(&#xffe3;▽&#xffe3;)~* &#x1f34a; 如果文章对您有帮助&#xff0c;记得关注、点赞、收藏、…

518抽奖软件,支持按人像照片抽奖

518抽奖软件简介 518抽奖软件&#xff0c;518我要发&#xff0c;超好用的年会抽奖软件&#xff0c;简约设计风格。 包含文字号码抽奖、照片抽奖两种模式&#xff0c;支持姓名抽奖、号码抽奖、数字抽奖、照片抽奖。(www.518cj.net) 照片抽奖模式 圆角边框 照片抽奖模式下&am…

目标检测YOLO实战应用案例100讲-基于YOLOv3多模块融合的遥感目标检测

目录 前言 国内外研究现状 基于手工特征的目标检测 基于深度学习的目标检测