增加功能:
1. 浏览器根据短地址重定向原地址,基于http协议,从请求中解析短地址,再从数据库中根据短地址找到原地址,拼接发送301重定向响应
301 是永久重定向,第一次访问短链接会通过短地址服务跳转到长链接后,游览器会将其缓存。再次访问短地址则会不经过短链接服务器直接跳转长链接地址。
301 对搜索引擎更友好,同时对服务器压力也会有一定减少。可以通生成参数定义 302 重定向的短链接。
2. 基于epoll使用IO复用实现多个连接请求的数据分发,响应
使用epoll
进行IO复用是构建高性能网络应用的关键技术之一,尤其是在需要处理大量并发连接的场景中。epoll
通过事件驱动的IO复用技术,可以在单个或少量线程中高效地处理大量并发连接,显著减少了线程创建和上下文切换的开销。
epoll
能够快速响应IO事件,减少了无谓的轮询,提高了应用程序的响应速度。同时,它具有良好的可扩展性,能够随着系统负载的增加而扩展处理能力。
附完整代码:
// 短地址后台管理头文件 url_manage.h
#include
#include
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
#include
#include
#include
#include
#include
#include
#include
#include
#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
#include
#include
#include
#include
#include
#include
#include
#include
#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
#include
#include
#include
#include
#include
#include
#include
#include
#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;
}