Linux基于Redis实现短地址服务(迭代版)

增加功能:

1.  浏览器根据短地址重定向原地址,基于http协议,从请求中解析短地址,再从数据库中根据短地址找到原地址,拼接发送301重定向响应

        301 是永久重定向,第一次访问短链接会通过短地址服务跳转到长链接后,游览器会将其缓存。再次访问短地址则会不经过短链接服务器直接跳转长链接地址。

        301 对搜索引擎更友好,同时对服务器压力也会有一定减少。可以通生成参数定义 302 重定向的短链接。

2.  基于epoll使用IO复用实现多个连接请求的数据分发,响应

        使用epoll进行IO复用是构建高性能网络应用的关键技术之一,尤其是在需要处理大量并发连接的场景中。epoll通过事件驱动的IO复用技术,可以在单个或少量线程中高效地处理大量并发连接,显著减少了线程创建和上下文切换的开销。

  epoll能够快速响应IO事件,减少了无谓的轮询,提高了应用程序的响应速度。同时,它具有良好的可扩展性,能够随着系统负载的增加而扩展处理能力。

附完整代码:

// 短地址后台管理头文件 url_manage.h
#include <hiredis/hiredis.h>
#include <time.h>

void show(int client_fd);
void createTinyURL(int client_fd);
redisContext *connectRedis();
void analyzeTinyURL(int client_fd);
void dataDisplay(int client_fd);
void statisticalInformation(int client_fd);
void deleteTinyURL(int client_fd);
void riseURLTime(int client_fd);
void operationTinyURL(int client_fd);
char * requireLongURL(char *tiny_url);
// 短地址后台管理 url_manage.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <hiredis/hiredis.h>
#include <time.h>
#include "url_manage.h"
// 面板展示
void show(int client_fd)
{
    char str[] = "=================短地址服务===================\n1. 生成短地址\n2. 解析短地址\n3. 数据显示\n4. 统计信息\n5. 退出程序\n=============================================\n请选择操作: ";
    write(client_fd, str, sizeof(str));
}

// 生成短地址:唯一ID、有效期、次数,并与长地址存入数据库
void createTinyURL(int client_fd)
{
    char str[] = ("-----------------生成短地址--------------------\n请输入需要缩短的长地址: \n");
    write(client_fd, str, sizeof(str));
    char url[64] = {0};
    read(client_fd, url, sizeof(url));

    char tiny_url[32] = {'0', '0', '0', '0', '0'};
    int i = 4;
    redisContext *conn = connectRedis();
    redisReply *reply = redisCommand(conn, "incr url_id");
    freeReplyObject(reply);

    reply = redisCommand(conn, "get url_id");
    int id = atoi(reply->str);
    freeReplyObject(reply);
    char c;
    while (id)
    {
        int tem = id % 62;
        if (tem > 35)
        {
            tem -= 36;
            c = tem + 'A';
        }
        else if (tem > 9)
        {
            tem -= 10;
            c = tem + 'a';
        }
        else
        {
            c = tem + '0';
        }
        tiny_url[i] = c;
        i--;
        id /= 62;
    }
    // printf("生成的短地址为:ak.cn/%s\n", tiny_url);
    char respond[64];
    sprintf(respond, "生成的短地址为:ak.cn/%s\n", tiny_url);
    write(client_fd, respond, sizeof(respond));

    // 获取当前时间,并转为字符串
    time_t now = time(NULL);
    char time_str[20];
    strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M", localtime(&now));

    // 存入短地址、长地址、创建时间、有效期、访问次数
    reply = redisCommand(conn, "hset ak.cn/%s url %s create_time %s days 7 num 0", tiny_url, url, time_str);
    freeReplyObject(reply);
    redisFree(conn);
}

// 创建数据库连接
redisContext *connectRedis()
{
    redisContext *conn = redisConnect("127.0.0.1", 6379);
    if (conn->err)
    {
        printf("connection error\n");
        redisFree(conn);
        return 0;
    }

    return conn;
}

// 解析短地址
void analyzeTinyURL(int client_fd)
{
    char str[] = "-----------------解析短地址--------------------\n请输入需要解析的短地址: ";
    write(client_fd, str, sizeof(str));
    char tiny_url[64];
    read(client_fd, tiny_url, sizeof(tiny_url));

    redisContext *conn = connectRedis();
    redisReply *reply = redisCommand(conn, "hget %s url", tiny_url);
    // printf("解析短地址成功,原地址为:%s\n", reply->str);
    char respond[1024];
    sprintf(respond, "解析短地址成功,原地址为:%s\n", reply->str);
    write(client_fd, respond, sizeof(respond));
    freeReplyObject(reply);

    // 每次查询访问,访问次数加1
    redisCommand(conn, "hincrby %s num 1", tiny_url);
    redisFree(conn);
}

// 短地址、长地址、有效期,数据显示
void dataDisplay(int client_fd)
{
    char str[] = "-----------------数据显示--------------------\n短地址\t\t原地址\t\t\t创建时间\t\t有效期\n";
    write(client_fd, str, sizeof(str));

    char respond[1024];
    redisContext *conn = connectRedis();
    redisReply *reply = redisCommand(conn, "keys *");
    redisReply *reply_row;

    for (int i = 0; i < reply->elements; i++)
    {
        if (reply->element[i]->len > 6)
        {
            reply_row = redisCommand(conn, "hvals %s", reply->element[i]->str);
            // reply->element[i]->str      短地址名
            // reply_row->element[0]->str  原地址名
            // reply_row->element[1]->str  创建时间
            // reply_row->element[2]->str  有效期
            sprintf(respond, "%s\t%s\t%s\t%5s\t\n", reply->element[i]->str, reply_row->element[0]->str,
                    reply_row->element[1]->str, reply_row->element[2]->str);
            write(client_fd, respond, sizeof(respond));
            // usleep(10000);
            memset(respond, 0, sizeof(respond));
        }
    }
    freeReplyObject(reply);
    redisFree(conn);
}

// 对显示数据进行操作,删除短地址、增长有效期
void operationTinyURL(int client_fd)
{
    int flag = 0;
    dataDisplay(client_fd);
    while (1)
    {
        if (flag == 1)
        {
            dataDisplay(client_fd);
        }
        char str[] = "可选择操作: \n\t1. 删除短地址\n\t2. 开通会员~增长有效期\n\t3. 返回\n请选择操作: ";
        write(client_fd, str, sizeof(str));

        char action[64] = {0};
        read(client_fd, action, sizeof(action));
        if (strlen(action) == 0)
        {
            read(client_fd, action, sizeof(action));
        }
        switch (action[0])
        {
        case '1':
            deleteTinyURL(client_fd);
            break;
        case '2':
            riseURLTime(client_fd);
            break;
        case '3':
            return;
        default:
            printf("请选择正确操作数(1-3)\n");
            break;
        }
        flag = 1;
    }
}

// 统计数据信息,短地址、被访问次数
void statisticalInformation(int client_fd)
{
    char str[] = "-----------------统计信息--------------------\n短地址\t\t访问次数\n";
    write(client_fd, str, sizeof(str));

    char respond[128];
    redisContext *conn = connectRedis();
    redisReply *reply = redisCommand(conn, "keys *");
    redisReply *reply_row;

    for (int i = 0; i < reply->elements; i++)
    {
        if (reply->element[i]->len > 6)
        {
            reply_row = redisCommand(conn, "hvals %s", reply->element[i]->str);
            // reply->element[i]->str      短地址名
            // reply_row->element[3]->str  访问次数
            sprintf(respond, "%s\t%5s\t\n", reply->element[i]->str, reply_row->element[3]->str);
            write(client_fd, respond, sizeof(respond));
            memset(respond, 0, sizeof(respond));
        }
    }
    freeReplyObject(reply);
    redisFree(conn);
}

// 删除短地址
void deleteTinyURL(int client_fd)
{
    char str[] = "请输入需要删除的短地址: \n";
    write(client_fd, str, sizeof(str));

    char tiny_url[64];
    read(client_fd, tiny_url, sizeof(tiny_url));

    redisContext *conn = connectRedis();
    redisReply *reply = redisCommand(conn, "del %s", tiny_url);
    // printf("删除成功~\n");
    char respond[] = "删除成功~\n";
    write(client_fd, respond, sizeof(respond));

    freeReplyObject(reply);
    redisFree(conn);
}

// 增长短地址有效期
void riseURLTime(int client_fd)
{
    char str[] = "请输入需要增长有效期的短地址: ";
    write(client_fd, str, sizeof(str));

    char tiny_url[64];
    read(client_fd, tiny_url, sizeof(tiny_url));

    int days;
    char str2[] = "请输入需要增长天数: ";
    write(client_fd, str2, sizeof(str2));

    char s_day[32];
    read(client_fd, s_day, sizeof(s_day));
    days = atoi(s_day);

    redisContext *conn = connectRedis();
    redisReply *reply = redisCommand(conn, "hincrby %s days %d", tiny_url, days);

    freeReplyObject(reply);
    redisFree(conn);
}

// 根据短地址获得长地址
char * requireLongURL(char *tiny_url){
    redisContext *conn = connectRedis();
    redisReply *reply = redisCommand(conn, "hget %s url", tiny_url);
    
    // 每次查询访问,访问次数加1
    redisCommand(conn, "hincrby %s num 1", tiny_url);
    redisFree(conn);
    return reply->str;
}
// 短地址服务器,监听多个端口,处理不同请求(地址管理、寻址重定向)TinyURL_server.c
#include <sys/epoll.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "url_manage.h"

#define MAX_EVENTS 10
#define PORTS_COUNT 2 // 假设监听两个端口
#define url_manage_port 9020
#define addressing_skip_port 8020

// 创建套接字
int createSocket(int port)
{
    int server_fd;
    struct sockaddr_in address;
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd < 0)
    {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(port);

    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0)
    {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    // 开始监听
    if (listen(server_fd, 3) < 0)
    {
        perror("listen");
        exit(EXIT_FAILURE);
    }

    printf("Listen on port %d...\n", port);
    return server_fd;
}
// url后台管理
void url_manage(int server_fd, int client_fd, struct sockaddr_in client_addr);
// url地址寻址重定向
void addressing_skip(int server_fd, int client_fd);
// 发送301重定向的函数
void send_301_redirect(int socket_fd, const char *location);

int main()
{
    int epfd = epoll_create1(0);
    if (epfd == -1)
    {
        perror("epoll_create1");
        exit(EXIT_FAILURE);
    }

    struct epoll_event event, events[MAX_EVENTS];
    int listenfds[PORTS_COUNT] = {0}; // 存储监听套接字的数组
    int port[2] = {url_manage_port, addressing_skip_port};
    listenfds[0] = createSocket(url_manage_port);
    listenfds[1] = createSocket(addressing_skip_port);

    // 创建并设置每个端口的监听套接字
    for (int i = 0; i < PORTS_COUNT; ++i)
    {
        // 将监听套接字添加到epoll的监控列表中
        event.data.fd = listenfds[i];
        event.events = EPOLLIN | EPOLLET;
        if (epoll_ctl(epfd, EPOLL_CTL_ADD, listenfds[i], &event) == -1)
        {
            perror("epoll_ctl");
            exit(EXIT_FAILURE);
        }
        event.data.ptr = (void *)&port[i];
    }
    struct sockaddr_in client_addr;
    int client_addr_len = sizeof(client_addr);

    // epoll等待事件的发生
    while (1)
    {
        int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
        if (nfds == -1)
        {
            perror("epoll_wait");
            exit(EXIT_FAILURE);
        }

        for (int i = 0; i < nfds; ++i)
        {
            if (events[i].events & EPOLLIN)
            {
                // 接受连接
                int client_fd = accept(events[i].data.fd, (struct sockaddr *)&client_addr, &client_addr_len);
                // ... 处理连接 ...
                // 根据端口号,处理不同需求
                if (listenfds[0] == events[i].data.fd)
                {
                    char client_ip[128];
                    inet_ntop(AF_INET, &(client_addr.sin_addr), client_ip, sizeof(client_ip));
                    printf("客户端已连接,IP地址为: %s\n", client_ip);
                    url_manage(listenfds[0], client_fd, client_addr);
                }
                else
                {
                    char client_ip[128];
                    inet_ntop(AF_INET, &(client_addr.sin_addr), client_ip, sizeof(client_ip));
                    printf("客户端已连接,IP地址为: %s\n", client_ip);
                    addressing_skip(listenfds[1], client_fd);
                }
            }
        }
    }

    close(epfd);
    return 0;
}

void url_manage(int server_fd, int client_fd, struct sockaddr_in client_addr)
{
    while (1)
    {
        // 接收请求,建立了 TCP 连接,获得了一个新的客户端套接字
        printf("等待客户端请求...\n");
        // 获取客户端IP地址
        char client_ip[128];
        inet_ntop(AF_INET, &(client_addr.sin_addr), client_ip, sizeof(client_ip));
        printf("客户端已连接,IP地址为: %s\n", client_ip);

        while (1)
        {
            show(client_fd);

            char action[64];
            read(client_fd, action, sizeof(action));

            switch (action[0])
            {
            case '1':
                createTinyURL(client_fd);
                break;
            case '2':
                analyzeTinyURL(client_fd);
                break;
            case '3':
                operationTinyURL(client_fd);
                break;
            case '4':
                statisticalInformation(client_fd);
                break;
            case '5':
                close(client_fd);
                close(server_fd);
                return;
            default:
                printf("请选择正确操作数(1-5)\n");
                break;
            }
        }
    }
}

void addressing_skip(int server_fd, int client_fd)
{
    char buffer[1024] = {0};
    read(client_fd, buffer, 1024);
    
    // 从http请求中解析短地址
    int count = 0;
    int sign = 0;
    char tem[8] = {0};
    char tiny_url[16] = {0};
    for (int i = 0; i < strlen(buffer); i++)
    {
        if (buffer[i] == ' ' || buffer[i] == '/')
        {
            sign++;
            continue;
        }
        if (sign == 3)
        {
            break;
        }
        if (sign == 2)
        {
            tem[count] = buffer[i];
            count++;
        }
    }
    sprintf(tiny_url, "ak.cn/%s", tem);
    
    // 获得长地址
    char *location = requireLongURL(tiny_url);
    printf("%s\n", location);
    
    const char *response_headers = "HTTP/1.1 301 Moved Permanently\r\n"
                                   "Location: ";
    char headers_with_location[1024];
    int headers_len = snprintf(headers_with_location, sizeof(headers_with_location),
                               "%s%s\r\n"
                               "Content-Length: 0\r\n"
                               "Connection: close\r\n\r\n",
                               response_headers, location);

    // 发送重定向响应
    send(client_fd, headers_with_location, headers_len, 0);

    // 关闭连接
    close(client_fd);
    close(server_fd);
    return;
}
// 短地址服务客户端 client.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/sendfile.h>

#define PORT 9020

int main(int argc, char const *argv[])
{
    int sockfd;

    // 创建套接字
    // 1. 域, 本地  2. 类型,字节流(TCP) 3. 协议
    sockfd = socket(AF_INET, SOCK_STREAM, 0);

    // 创建连接,连接服务器
    struct sockaddr_in addr;
    addr.sin_family = AF_INET; // 地址类型
    addr.sin_port = htons(PORT);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    // 1. 套接字 2.地址 3.地址长度
    int r = connect(sockfd, (struct sockaddr *)&addr, sizeof(addr));
    if (r == -1)
    {
        printf("连接失败\n");
        return EXIT_FAILURE;
    }

    printf("客户端已开启,已连接服务器~~\n");
    while (1)
    {
        char buf[1024] = {0};
        char input[64] = {0};

        read(sockfd, buf, sizeof(buf));
        printf("%s", buf);

        if (buf[strlen(buf) - 2] == ':' || buf[strlen(buf) - 3] == ':')
        {
            scanf("%s", input);
            write(sockfd, input, sizeof(input));
            // usleep(100000);
            if (input[0] == '5')
            {
                close(sockfd);
                return 0;
            }
            memset(input, 0, sizeof(input));
        }
    }

    // close(sockfd);
    return 0;
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值