Linux下C语言完成简单Web代理服务器

目录

总述

技术实现

运行效果

源码


总述

开发:WSL+vscode

本项目利用C语言套接字编程和多线程编程实现了Linux下一个简单web代理服务器,只支持HTTP 1.0协议的GET方法。

代理服务器端口号由用户自己手动输入,在终端输入 ./proxy <port>即可启动。

代理服务器的工作流程想必大家都清楚了

服务器创建套接字 ——> 服务器与客户端连接并分配线程 ——> 服务器接收客户端消息 ——> 服务器将客户端消息发向目标地址 ——> 服务器接收目标地址的响应 ——> 服务器将响应反馈给客户端

接下来只需要将各个步骤涉及的功能实现即可。


技术实现

首先验证用户是否输入端口号

    if (argc != 2) {
        printf("Usage: %s <port>\n", argv[0]);
        exit(1);
    }

如果输入端口号则验证输入端口号是否合法

    int port = atoi(argv[1]);

    if (port < 1 || port > 65535) {
        fprintf(stderr, "Error: Invalid port number. Port number should be between 1 and 65535.\n");
        return 1;
    }

接下来创建服务器端套接字

    int server_socket = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in server_address;
    bzero(&server_address, sizeof(server_address));
    server_address.sin_family = AF_INET;
    server_address.sin_port = htons(port);
    server_address.sin_addr.s_addr = INADDR_ANY;

    bind(server_socket, (struct sockaddr *)&server_address, sizeof(server_address));

    listen(server_socket, MAX_CONNECTIONS);
    printf("Listening on port %d\n", port);

bzero将套接字初始为0,AF_INET表示该套接字使用的是ipv4协议,htons是将端口号转换成网络字节序,INADDR_ANY表示接收来自任意地址的连接。

最后bind函数将服务器套接字绑定起来,也就是说服务器端开始监听端口了。

listen只是用来限制同时连接服务器的最大用户数。

服务器循环接收客户端的连接并为其分配线程处理

    while (1) {
        int client_socket = accept(server_socket, NULL, NULL);
        printf("Accepted connection\n");
        if (fork() == 0) {
            //创建新进程处理连接的客户端
        }

        close(client_socket);//关闭连接套接字
    }

每有一个新的连接服务器使用fork创建一个新进程处理,完成之后关闭客户端套接字释放资源。

接收客户端消息

    char buffer[MAX_BUFFER];
    read(client_socket, buffer, sizeof(buffer));
    char method[256], url[256], version[256];
    sscanf(buffer, "%s %s %s", method, url, version);
    if (strcasecmp(method, "GET") != 0) {
        char *response = "HTTP/1.0 501 Not Implemented\r\n\r\n";
        write(client_socket, response, strlen(response));
        close(client_socket);
        exit(1);
    }

设置缓冲区大小,将客户端消息读入缓冲区,然后使用sscanf将其以空格形式分割成方法、url、http版本。判断请求方法是不是get方法,如果不是则向客户端发送501错误

将客户端请求发送到目标地址

            int port=80; //default port
            char host[256], path[256], *port_ptr;
            sscanf(url, "http://%[^/]%s", host, path);

            // Check if the URL contains a port number
            port_ptr = strchr(host, ':');
            if (port_ptr) {
                *port_ptr = '\0'; // Null-terminate the host string
                port = atoi(port_ptr + 1); // Convert the port number to an integer
            }
            
            printf("Host: %s:%d\nPath: %s\n", host, port, path);

            struct hostent *server = gethostbyname(host);
            int target_socket = socket(AF_INET, SOCK_STREAM, 0);

            struct sockaddr_in target_address;
            bzero(&target_address, sizeof(target_address));
            target_address.sin_family = AF_INET;
            target_address.sin_port = htons(port);
            bcopy((char *)server->h_addr_list[0], (char *)&target_address.sin_addr.s_addr, server->h_length);
            connect(target_socket, (struct sockaddr *)&target_address, sizeof(target_address));

            char request[MAX_BUFFER];
            sprintf(request, "GET %s HTTP/1.0\r\nHost: %s\r\n\r\n", path, host);
            write(target_socket, request, strlen(request));

默认网站端口是80,将客户端发来的url拆成主机地址和端口号,如果url不含有端口号则使用默认端口号80,否则使用url中给出的端口号。

创建与目标地址连接的套接字,gethostbyname是解析域名的库函数,bcopy将gethostbyname解析到的ip地址复制到target_address套接字中,然后connect连接,发送request请求,注意http报文格式。

接收目标地址响应并反馈给客户端

    while (1) {
        bzero(buffer, sizeof(buffer));
        int bytes = read(target_socket, buffer, sizeof(buffer) - 1);
        if (bytes <= 0) {
            break;
        }
        write(client_socket, buffer, bytes);
    }

    close(client_socket);
    close(target_socket);
    exit(0);

接收比较简单,直接把目标地址的响应全部发给客户端就行了。

由于是HTTP 1.0,最后不要忘记释放所有资源哦


运行效果

假设gcc编译生成proxy可执行文件,终端输入./proxy 8888运行程序

在火狐里面设置一下代理

输入http://baidu.com访问一下,成功访问到百度的html页面

也可以进行telnet测试

也是成功返回了百度的html


源码

源码放在下面了,大家慢慢享用 ^_^

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

#define MAX_BUFFER 4096
#define MAX_CONNECTIONS 100

int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("Usage: %s <port>\n", argv[0]);
        exit(1);
    }

    int port = atoi(argv[1]);

    if (port < 1 || port > 65535) {
        fprintf(stderr, "Error: Invalid port number. Port number should be between 1 and 65535.\n");
        return 1;
    }

    int server_socket = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in server_address;
    bzero(&server_address, sizeof(server_address));
    server_address.sin_family = AF_INET;
    server_address.sin_port = htons(port);
    server_address.sin_addr.s_addr = INADDR_ANY;

    bind(server_socket, (struct sockaddr *)&server_address, sizeof(server_address));

    listen(server_socket, MAX_CONNECTIONS);
    printf("Listening on port %d\n", port);
    while (1) {
        int client_socket = accept(server_socket, NULL, NULL);
        printf("Accepted connection\n");
        if (fork() == 0) {
            char buffer[MAX_BUFFER];
            read(client_socket, buffer, sizeof(buffer));

            char method[256], url[256], version[256];
            sscanf(buffer, "%s %s %s", method, url, version);

            if (strcasecmp(method, "GET") != 0) {
                char *response = "HTTP/1.0 501 Not Implemented\r\n\r\n";
                write(client_socket, response, strlen(response));
                close(client_socket);
                exit(1);
            }
            int port=80; //default port
            char host[256], path[256], *port_ptr;
            sscanf(url, "http://%[^/]%s", host, path);

            // Check if the URL contains a port number
            port_ptr = strchr(host, ':');
            if (port_ptr) {
                *port_ptr = '\0'; // Null-terminate the host string
                port = atoi(port_ptr + 1); // Convert the port number to an integer
            }
            
            printf("Host: %s:%d\nPath: %s\n", host, port, path);

            struct hostent *server = gethostbyname(host);
            int target_socket = socket(AF_INET, SOCK_STREAM, 0);

            struct sockaddr_in target_address;
            bzero(&target_address, sizeof(target_address));
            target_address.sin_family = AF_INET;
            target_address.sin_port = htons(port);
            bcopy((char *)server->h_addr_list[0], (char *)&target_address.sin_addr.s_addr, server->h_length);
            connect(target_socket, (struct sockaddr *)&target_address, sizeof(target_address));

            char request[MAX_BUFFER];
            sprintf(request, "GET %s HTTP/1.0\r\nHost: %s\r\n\r\n", path, host);
            write(target_socket, request, strlen(request));

            while (1) {
                bzero(buffer, sizeof(buffer));
                int bytes = read(target_socket, buffer, sizeof(buffer) - 1);
                if (bytes <= 0) {
                    break;
                }
                write(client_socket, buffer, bytes);
            }

            close(client_socket);
            close(target_socket);
            exit(0);
        }

        close(client_socket);
    }

    return 0;
}

  • 12
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值