项目:TCP在线云词典

2023-09-14 23:43:57

在这里插入图片描述

一.要求

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;
}

更多推荐

安装ThinkPHP5.1并在框架中使用FFmpeg视频处理工具遇到的问题和解决办法

一:安装ThinkPHP5.1框架问题一:安装方法有很多,我这里使用composer安装的,但是遇到了问题,出现了报错安装方法可是查看https://www.kancloud.cn/manual/thinkphp5_1/353948composercreate-projecttopthink/thinkblog5.1.

objList=strList为什么报错

代码复现先看下面一段代码List<String>strList=newArrayList<>();List<Object>objList=strList;//报错乍一看好像没什么问题,但为什么追报错呢?先别急,看下面一段代码List<Integer>intList=newArrayList<>();List<Objec

负载均衡策略

一台机器不能满足,则增加两台或者多台机器,共同承担访问压力,这就是典型的集群和负载均衡架构。一、轮询(RoundRobin)按照顺序将请求依次分配给每个服务器,确保每个服务器都能平均分担负载。二、哈希(IPHash)根据客户端的IP地址将请求分配给服务器,这会通过哈希函数来分配应用服务器。相同IP的客户端将始终被分配到

【 2023华为杯C题】大规模创新类竞赛评审方案研究(思路、代码......)

目录1题目概述2问题3极差的定义及标准分的计算方法4题目及数据下载5思路、代码下载......1题目概述现在创新类竞赛很多,其中规模较大的竞赛,一般采用两阶段(网评、现场评审)或三阶段(网评、现场评审和答辩)评审。创新类竞赛的特点是没有标准答案,需要评审专家根据命题人(组)提出的评审框架(建议)独立评审。所以,对同一份

凹凸贴图和法线贴图的区别

1、什么是凹凸贴图凹凸贴图(bumpmapping)是一种计算机图形学中的渲染技术,用于在给定的表面上模拟微小的凹凸纹理。通过在表面法线方向上微调每个像素的光照值,可以给平滑的表面增加视觉上的凹凸感。在凹凸贴图中,每个像素点都包含了一个法线向量,表示该点表面的方向。这些法线向量通常以纹理的形式存储在一个称为凹凸贴图的二

Zabbix“专家坐诊”第204期问答汇总

问题一Q:请问自动发现如何配置?A:在Zabbix中配置自动发现,可以使用以下步骤:登录到Zabbix的Web界面。确保您具有管理员或具有适当权限的用户角色。导航到“配置”菜单,然后选择“自动发现”。点击“创建自动发现”按钮。在“名称”字段中,输入自动发现规则的名称。在“新发现设备”字段中,选择要在自动发现过程中创建设

【Python】PySpark 数据处理 ② ( 安装 PySpark | PySpark 数据处理步骤 | 构建 PySpark 执行环境入口对象 )

文章目录一、安装PySpark1、使用pip安装PySpark2、国内代理镜像3、PyCharm中安装PySpark二、PySpark数据处理步骤三、构建PySpark执行环境入口对象四、代码示例一、安装PySpark1、使用pip安装PySpark执行Windows+R,运行cmd命令行提示符,在命令行提示符终端中,

计算机网络篇之IPV4数据报格式

计算机网络篇之IPV4数据报格式structiphdr{uint8_tversion:4;//4位版本字段表示Internet标头的格式,ipv4值是4uint8_tihl:4;//4位因特网报头长度,ip报头中32位字的数量uint8_ttos;//服务类型字段,该字段传达IP数据报的服务质量,源于第一个IP规范,在

【从0学习Solidity】 3. 函数详解

【从0学习Solidity】3.函数详解博主简介:不写代码没饭吃,一名全栈领域的创作者,专注于研究互联网产品的解决方案和技术。熟悉云原生、微服务架构,分享一些项目实战经验以及前沿技术的见解。关注我们的主页,探索全栈开发,期待与您一起在移动开发的世界中,不断进步和创造!本文收录于不写代码没饭吃的学习汇报系列,大家有兴趣的

SpringBoot整合Activiti7——代理/候选人/候选组(四)

文章目录一、代理人二、候选人三、候选组四、组任务办理流程五、UEL表达式UEL-valueUEL-methodUELmethod结合value一、代理人一个用户任务只允许有一个代理人。为一个任务分配代理人后act_ru_task表的ASSIGNEE_字段会被设置为响应的值。审批任务设置assignee变量,表示是该任务

GaussDB技术解读系列:性能调优

近日,在第14届中国数据库技术大会(DTCC2023)的GaussDB“五高两易”核心技术,给世界一个更优选择专场,华为数据库技术专家李士福详细解读了GaussDB性能调优的相关技术和应用实践。本篇为大家分享GaussDB性能调优的实践。主要包括三个部分,分别是性能调优的整体介绍,性能调优的关键技术,性能调优的应用实践

热文推荐