HTTP代理服务器

代理服务器实现流程
创建一个监听服务器,监听客户端的连接。
客户端连接上后,将客户端要上网的信息,发给网络web服务器,
然后从那里读取数据,再发给客户端。

注:客户端是通过浏览器的设置,来设置我的地址和端口,一旦访问某个域名,就会和代理服务器连接。

代理服务器本身既是服务器,又是客户端。对浏览器来说,是服务器,对网页服务器来说是客户端。例如:代理服务器放在国外的主机上,可实现翻墙,当访问敏感数据,经过防火墙易被查出来了。

浏览器设置:

代理服务器代码:
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//创建监听套接字
int create_listensocket(void)
{
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    int n = 1;
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(n)); //地址复用
    
    struct sockaddr_in sin;
    bzero(&sin, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_port = htons(2020);
    sin.sin_addr.s_addr = INADDR_ANY; //listen any add
    //bangding
    if(bind(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0)
    {
        perror("bind");
    }
    listen(fd, 50);
    return fd;
}
//替换字符串
void replace(char *in, char *out)
{
    //取出文件名
    char filename[30]={0};
    sscanf(in, "%*[^//]//%*[^/]%s", filename); //略过前面
    printf("文件名是%s\n",filename);
    
    //取出后面部分+前面的部分GET (HTTP/1.1...)
    char *tail = strstr(in, "HTTP/"); //搜索HTTP子字符串
    
    char method[10] = {0};
    sscanf(in, "%s", method); //s碰到空格就会停下来
    //再组合起来
    sprintf(out, "%s %s %s", method, filename, tail);
    //GET http://www.163.com/abc.txt HTTP/1.1
    //--->GET /abc.txt HTTP/1.1
}

//处理客户端的读写
void handleclient(int fd)
{
    //1 从客户端读取要上网的信息
    char buffer[1024*1024]={0};
    int nread = read(fd, buffer, sizeof(buffer));
    //打出来
    printf("%s\n",buffer);
//GET http://www.163.com/abc.txt HTTP/1.1
//Host: www.163.com
//User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:67.0) Gecko/20100101 Firefox/67.0
//Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
//Accept-Language: en-US,en;q=0.5
//Accept-Encoding: gzip, deflate
//Connection: keep-alive
//Upgrade-Insecure-Requests: 1
    //2 转发给真正服务器
    //需要将第一行域名变成文件名,如果单有域名没有文件名,则使用"/"
    char buffernew[1024*1024]={0};
    replace(buffer, buffernew);
    printf("经过替换后的字符串是%s\n", buffernew);
    //输出了一团为二进制(真正的请求里会有二进制,不能简单看做字符串,否则网页显示不完整)
    
    //3 连接真正的服务器,把替换后的东西发给它
    //解析域名
    char hostname[30]={0};
    sscanf(buffer, "%*[^//]//%[^/]",hostname);
    printf("域名是%s\n",hostname);
    //域名转ip
    struct hostent *ent = gethostbyname(hostname);
    //构建服务器的地址结构体
    struct sockaddr_in serversin;
    bzero(&serversin, sizeof(serversin));
    serversin.sin_family = AF_INET;
    serversin.sin_port = htons(80); //端口默认是80
    memcpy(&serversin.sin_addr, ent->h_addr, ent->h_length);
    
    //连接网页服务器
    int myfd = socket(AF_INET, SOCK_STREAM, 0);
    int res = connect(myfd, (struct sockaddr *)&serversin, sizeof(serversin));
    if(res < 0)
    {
        perror("connect");
        return;
    }
    printf("连接成功\n");
    //将替换后的头部发给服务器
    write(myfd, buffernew, strlen(buffernew));
    
    //从网页服务器读回结果
    int len;
    while(read(myfd, buffer, 1) > 0) //如果大于0,表示有数据
    {    //此处处理二进制
        write(fd, buffer, 1); //客户端fd
    }
}

int main()
{
    //1 创建监听套接字,等待客户端(浏览器)的连接
    int listenfd = create_listensocket();

    //2 接受客户端的连接,然后去处理连接
    while(1) //单线运行比较慢
    {
        //从监听套接字复制出新的fd描述符,去分别和客户端连接
        int clientfd = accept(listenfd, NULL, NULL);
        //处理
        if(fork()==0) //新进程
        {
            handleclient(clientfd);
            close(clientfd);
            exit(0);
        }
        
    }
}

运行结果:
在浏览器中输入www.163.com 然后回车