首页 > 其他分享 >【网络代理模块】正向代理(上)

【网络代理模块】正向代理(上)

时间:2024-10-20 16:22:15浏览次数:5  
标签:aa socket epoll int 端口 代理 模块 正向

1 概念

1.1 正向代理概念

正向代理是一个位于客户端和目标服务器之间的代理服务器(中间服务器)。为了从目标服务器取得内容,客户端向代理服务器发送一个请求,并且指定目标服务器,之后代理向目标服务器转发请求,将获得的内容返回给客户端。正向代理的情况下,客户端必须要进行一些特殊的设置才能使用。

1.2 示例

参考公安部门的网络关系,上下级直接联系,平级之间互不联系。如果A省A市公安局想访问B省B市服务器,那么就要通过多个节点进行中转。手工的多次执行SSH协议多跳几下也是可以,但是如果像访问B省B市内部的网站那就没办法了。这个问题可以用正向代理解决,正向代理就是报文转发。

2 特点

  • 正向代理需要主动设置代理服务器ip或者域名进行访问,由设置的服务器ip或者域名去访问内容并返回
  • 正向代理是代理客户端,为客户端收发请求,使真实客户端对服务器不可见。

3 正向代理功能实现

3.1 路由参数配置文件

正向代理服务器端维护一个路由参数配置文件,内容为:

每一列分别为:源端口,目标地址,目标端口;

每一行的意思是将5022端口转发到192.168.150.128地址的22端口。

正向代理需要实现的功能:根据路由配置文件,监听源端口,如果源端口有客户端链接上来,就按照路由配置文件查找目的ip和端口进行连接。然后A和C相互通信,就跟没有中间的B一样。

3.2 功能开发

正向代理的实现思路(水平触发+非阻塞):

//帮助文档
//关闭信号和IO,处理信号
//打开日志

//加载代理路由配置文件,加载到容器vroute中。
//初始化服务端用于监听源端口的socket:遍历vroute将源端口一个个监听,并设置非阻塞的。
//监听的socket全部加入epoll中。
while(true)    //事件循环
{
    int event = epoll_wait();
    //循环判断是否是监听的socket
    {
        //连上来的socket是7
        //向目标地址和端口发起tcp连接,socket是8
        //那么把7和8的读事件加入epoll
        //更新clientsocks数组中两端socket的值和活动时间
    }
    //如果客户端连接的socket有事件,表示有报文发过来或者连接已经断开。
    //如果断开,即读取数据<=0
    {
        //关闭两端的socket
        //清空clientsocks数组两边的值
    }
    //如果有报文传来,即成功读取了数据
    {
        //数据原封不动发送给对端
    } 
}

具体代码实现:

/*
 * 程序名:inetd.cpp,正向网络代理服务程序。
 * 作者:张咸武
*/
#include "_public.h"
using namespace idc;

// 代理路由参数的结构体。
struct st_route
{
    int    srcport;           // 源端口。
    char dstip[31];        // 目标主机的地址。
    int    dstport;          // 目标主机的端口。
    int    listensock;      // 源端口监听的socket。
}stroute;
vector<struct st_route> vroute;       // 代理路由的容器。
bool loadroute(const char *inifile);  // 把代理路由参数加载到vroute容器。

// 初始化服务端的监听端口。
int initserver(const int port);

int epollfd=0;      // epoll的句柄。
int tfd=0;             // 定时器的句柄。

#define MAXSOCK  1024          // 最大连接数。
int clientsocks[MAXSOCK];       // 存放每个socket连接对端的socket的值。
int clientatime[MAXSOCK];       // 存放每个socket连接最后一次收发报文的时间。

// 向目标地址和端口发起socket连接。
int conntodst(const char *ip,const int port);

void EXIT(int sig);     // 进程退出函数。

clogfile logfile;

int main(int argc,char *argv[])
{
    if (argc != 3)
    {
        printf("\n");
        printf("Using :./inetd logfile inifile\n\n");
        printf("Sample:./inetd /tmp/inetd.log /etc/inetd.conf\n\n");
        printf("        /project/tools/bin/procctl 5 /project/tools/bin/inetd /tmp/inetd.log /etc/inetd.conf\n\n");
        printf("本程序的功能是正向代理,如果用到了1024以下的端口,则必须由root用户启动。\n");
        printf("logfile 本程序运行的日是志文件。\n");
        printf("inifile 路由参数配置文件。\n");

        return -1;
    }

    // 关闭全部的信号和输入输出。
    // 设置信号,在shell状态下可用 "kill + 进程号" 正常终止些进程。
    // 但请不要用 "kill -9 +进程号" 强行终止。
    closeioandsignal(true);  signal(SIGINT,EXIT); signal(SIGTERM,EXIT);

    // 打开日志文件。
    if (logfile.open(argv[1])==false)
    {
        printf("打开日志文件失败(%s)。\n",argv[1]); return -1;
    }

    // 把代理路由参数配置文件加载到vroute容器。
    if (loadroute(argv[2])==false) return -1;

    logfile.write("加载代理路由参数成功(%d)。\n",vroute.size());

    // 初始化服务端用于监听的socket。
    for (auto &aa:vroute)
    {
        if ( (aa.listensock=initserver(aa.srcport)) < 0 )
        {
            // 如果某一个socket初始化失败,忽略它。
            logfile.write("initserver(%d) failed.\n",aa.srcport);   continue;
        }

        // 把监听socket设置成非阻塞。
        fcntl(aa.listensock,F_SETFL,fcntl(aa.listensock,F_GETFD,0)|O_NONBLOCK);
    }

    // 创建epoll句柄。
    epollfd=epoll_create1(0);

    struct epoll_event ev;  // 声明事件的数据结构。

    // 为监听的socket准备读事件。
    for (auto aa:vroute)
    {
        if (aa.listensock<0) continue;

        ev.events=EPOLLIN;                     // 读事件。
        ev.data.fd=aa.listensock;              // 指定事件的自定义数据,会随着epoll_wait()返回的事件一并返回。
        epoll_ctl(epollfd,EPOLL_CTL_ADD,aa.listensock,&ev);  // 把监听的socket的事件加入epollfd中。
    }

    struct epoll_event evs[10];      // 存放epoll返回的事件。

    while (true)     // 进入事件循环。
    {
        // 等待监视的socket有事件发生。
        int infds=epoll_wait(epollfd,evs,10,-1);

        // 返回失败。
        if (infds < 0) { logfile.write("epoll() failed。\n"); EXIT(-1); }

        // 遍历epoll返回的已发生事件的数组evs。
        for (int ii=0;ii<infds;ii++)
        {
            logfile.write("已发生事件的socket=%d\n",evs[ii].data.fd);

            
            // 如果发生事件的是listensock,表示有新的客户端连上来。
            int jj=0;
            for (jj=0;jj<vroute.size();jj++)
            {
                if (evs[ii].data.fd==vroute[jj].listensock)     // 判断是哪个源端口有客户端连上来了。5058
                {
                    // 从已连接队列中获取客户端连上来的socket。 // socket是7
                    struct sockaddr_in client;
                    socklen_t len = sizeof(client);
                    int srcsock = accept(vroute[jj].listensock,(struct sockaddr*)&client,&len);
                    if (srcsock<0) break;
                    if (srcsock>=MAXSOCK) 
                    {
                        logfile.write("连接数已超过最大值%d。\n",MAXSOCK); close(srcsock); break;
                    }

                    // 向目标地址和端口发起连接,如果连接失败,会被epoll发现,将关闭通道。
                    int dstsock=conntodst(vroute[jj].dstip,vroute[jj].dstport);        // socket是8
                    if (dstsock<0) { close(srcsock); break; }
                    if (dstsock>=MAXSOCK)
                    {
                        logfile.write("连接数已超过最大值%d。\n",MAXSOCK); close(srcsock); close(dstsock); break;
                    }

                    logfile.write("accept on port %d,client(%d,%d) ok。\n",vroute[jj].srcport,srcsock,dstsock);

                    // 为新连接的两个socket准备读事件,并添加到epoll中。
                    ev.data.fd=srcsock; ev.events=EPOLLIN;
                    epoll_ctl(epollfd,EPOLL_CTL_ADD,srcsock,&ev);
                    ev.data.fd=dstsock; ev.events=EPOLLIN;
                    epoll_ctl(epollfd,EPOLL_CTL_ADD,dstsock,&ev);

                    // 更新clientsocks数组中两端soccket的值和活动时间。
                    clientsocks[srcsock]=dstsock;  clientatime[srcsock]=time(0); 
                    clientsocks[dstsock]=srcsock;  clientatime[dstsock]=time(0);

                    break;
                }
            }

            // 如果jj<vroute.size(),表示事件在上面的for循环中已被处理,流程不必往下。
            if (jj<vroute.size()) continue;
            

            
            // 如果是客户端连接的socke有事件,表示有报文发过来或者连接已断开。
      
            char buffer[5000];     // 存放从接收缓冲区中读取的数据。
            int    buflen=0;          // 从接收缓冲区中读取的数据的大小。

            // 从通道的一端读取数据。
            if ( (buflen=recv(evs[ii].data.fd,buffer,sizeof(buffer),0)) <= 0 )
            {
                // 如果连接已断开,需要关闭通道两端的socket。
                logfile.write("client(%d,%d) disconnected。\n",evs[ii].data.fd,clientsocks[evs[ii].data.fd]);
                close(evs[ii].data.fd);                                         // 关闭客户端的连接。
                close(clientsocks[evs[ii].data.fd]);                     // 关闭客户端对端的连接。
                clientsocks[clientsocks[evs[ii].data.fd]]=0;       // 把数组中对端的socket置空,这一行代码和下一行代码的顺序不能乱。
                clientsocks[evs[ii].data.fd]=0;                           // 把数组中本端的socket置空,这一行代码和上一行代码的顺序不能乱。

                continue;
            }
      
            // 成功的读取到了数据,把接收到的报文内容原封不动的发给通道的对端。
            logfile.write("from %d to %d,%d bytes。\n",evs[ii].data.fd,clientsocks[evs[ii].data.fd],buflen);
            send(clientsocks[evs[ii].data.fd],buffer,buflen,0);

            // 更新通道两端socket的活动时间。
            clientatime[evs[ii].data.fd]=time(0); 
            clientatime[clientsocks[evs[ii].data.fd]]=time(0);  
        }
    }

    return 0;
}

// 初始化服务端的监听端口。
int initserver(const int port)
{
    int sock = socket(AF_INET,SOCK_STREAM,0);
    if (sock < 0)
    {
        logfile.write("socket(%d) failed.\n",port); return -1;
    }

    int opt = 1; unsigned int len = sizeof(opt);
    setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,len);

    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(port);

    if (bind(sock,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0 )
    {
        logfile.write("bind(%d) failed.\n",port); close(sock); return -1;
    }

    if (listen(sock,5) != 0 )
    {
        logfile.write("listen(%d) failed.\n",port); close(sock); return -1;
    }

    return sock;
}

// 把代理路由参数加载到vroute容器。
bool loadroute(const char *inifile)
{
    cifile ifile;

    if (ifile.open(inifile)==false)
    {
        logfile.write("打开代理路由参数文件(%s)失败。\n",inifile); return false;
    }

    string strbuffer;
    ccmdstr cmdstr;

    while (true)
    {
        if (ifile.readline(strbuffer)==false) break;

        // 删除说明文字,#后面的部分。
        auto pos=strbuffer.find("#");
        if (pos!=string::npos) strbuffer.resize(pos);

        replacestr(strbuffer,"  "," ",true);    // 把两个空格替换成一个空格,注意第四个参数。
        deletelrchr(strbuffer,' ');                 // 删除两边的空格。

        // 拆分参数。
        cmdstr.splittocmd(strbuffer," ");
        if (cmdstr.size()!=3) continue;

        memset(&stroute,0,sizeof(struct st_route));
        cmdstr.getvalue(0,stroute.srcport);          // 源端口。
        cmdstr.getvalue(1,stroute.dstip);             // 目标地址。
        cmdstr.getvalue(2,stroute.dstport);         // 目标端口。

        vroute.push_back(stroute);
    }

    return true;
}

// 向目标地址和端口发起socket连接。
int conntodst(const char *ip,const int port)
{
    // 第1步:创建客户端的socket。
    int sockfd;
    if ( (sockfd = socket(AF_INET,SOCK_STREAM,0))==-1) return -1; 

    // 第2步:向服务器发起连接请求。
    struct hostent* h;
    if ( (h = gethostbyname(ip)) == 0 ) { close(sockfd); return -1; }
  
    struct sockaddr_in servaddr;
    memset(&servaddr,0,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(port); // 指定服务端的通讯端口。
    memcpy(&servaddr.sin_addr,h->h_addr,h->h_length);

    // 把socket设置为非阻塞。
    fcntl(sockfd,F_SETFL,fcntl(sockfd,F_GETFD,0)|O_NONBLOCK);

    if (connect(sockfd, (struct sockaddr *)&servaddr,sizeof(servaddr))<0)
    {
        if (errno!=EINPROGRESS)
        {
            logfile.write("connect(%s,%d) failed.\n",ip,port); return -1;
        }
    }

    return sockfd;
}

void EXIT(int sig)
{
    logfile.write("程序退出,sig=%d。\n\n",sig);

    // 关闭全部监听的socket。
    for (auto &aa:vroute)
        if (aa.listensock>0) close(aa.listensock);

    // 关闭全部客户端的socket。
    for (auto aa:clientsocks)
        if (aa>0) close(aa);

    close(epollfd);   // 关闭epoll。

    close(tfd);       // 关闭定时器。

    exit(0);
}

至此,正向代理模块基本功能已经实现。

标签:aa,socket,epoll,int,端口,代理,模块,正向
From: https://blog.csdn.net/weixin_56520780/article/details/143093472

相关文章

  • 【网络代理模块】反向代理(上)
    1概念1.1反向代理概念反向代理是指以代理服务器来接收客户端的请求,然后将请求转发给内部网络上的服务器,将从服务器上得到的结果返回给客户端,此时代理服务器对外表现为一个反向代理服务器。对于客户端来说,反向代理就相当于目标服务器,只需要将反向代理当作目标服务器一样......
  • Java高级:动态代理
    前言:动态代理是一种设计模式。之所以学习动态代理这种设计模式,是因为后面学习一些技术、项目中,会用到动态代理。一、程序为什么需要代理?代理长什么样?1、为什么需要代理?拿现实举例:一个明星,一开始想唱歌就唱歌、想跳舞就跳舞。等到这个明星稍微有了点热度,就要开始收费表演......
  • 10-1.idea中的项目结构,辅助快捷键,模块的操作
    idea中的项目结构和辅助快捷键IDEA中项目结构首先是创建项目,新建的项目中有子项目,我们可以创建模块然后在模块中我们可以创建包,在包中的SRC中写我们的源代码,也就是类。VScode写Java项目如何你电脑比较卡的话,你可以使用VScode,请看我另一篇教程:VScode写Java项目的教程......
  • AOP - 自己写 JDK 动态代理增强 bean
    AOP的原理就是给目标对象创建代理对象,达到增强目标对象方法的目的如果目标对象实现了接口就是用JDK动态代理,如果没实现接口就是用三方的CGLIB代理如果不使用AOP想要增强一个bean可以这样做:@ComponentpublicclassTestimplementsBeanPostProcessor,ApplicationCon......
  • 简易CPU设计入门:验证取指令模块
    项目代码下载还是请大家首先准备好本项目所用的源代码。如果已经下载了,那就不用重复下载了。如果还没有下载,那么,请大家点击下方链接,来了解下载本项目的CPU源代码的方法。下载本项目代码准备好了项目源代码以后,我们接着去讲解。本节前言想要学习本节,前提是,你得是学习过我讲......
  • Charles证书安装与SSL代理设置(保姆级)
    Charles证书安装与SSL代理设置完全详解Charles安装证书为什么要安装证书前面也说过了Charles相当于一个中间代理,也就是说Charles作为一个中间代理在客户端和服务器之间进行通信,并且相互通信的数据可以被Charles拦截或者篡改,但是默认情况下我们的Charles是识别不了H......
  • python异常与模块
    1.了解异常什么是异常呢?当检测到一个错误时,python解释器就无法继续执行了,反而出现了一些错误的提示,这就是所谓的“异常”,也就是我们常说的bug像这样,计算机会告诉你出错的是哪一行代码以及出现错误的问题所在FileNotFoundError:文件未找到Nosuchfileordirectory:文......
  • 低代码平台中的功能驱动开发:模块化与领域设计
    在现代软件开发中,尤其是在低代码平台的背景下,清晰地定义功能和模块是成功的关键。功能驱动开发强调功能的优先性,模块化设计则确保系统的可维护性和可扩展性。本文将探讨如何在低代码平台中有效地将功能与模块结合起来,形成一个清晰的领域模型。功能与模块的统一在开发软件时,功......
  • Docker 配置代理服务
    如果Docker主机安装在内网,需要通过代理下载镜像,那可以为Docker服务(守护进程)配置代理服务器。本文是学习官方代理配置文档的笔记。Docker服务可以通过daemon.json文件或dockerd命令的--http-proxy或者--https-proxy的参数来配置。推荐使用配置文件来配置。配置......
  • 项目模块三:Socket模块
    一、模块设计1、套接字编程常用头文件展示#include<sys/types.h>#include<sys/socket.h>#include<netinet/in.h>#include<arpa/inet.h>#include<unistd.h>#include<fcntl.h>2、成员函数设计(1)创建套接字bool Create()intsocket(intdomain,inttype,......