正向代理服务器

embedded/2024/11/15 3:48:13/

1 概念

1.1 正向代理概念

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

1.2 示例

1af55e3df6c24332a0967c29c22a556d.png

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

2 特点

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

3 正向代理功能实现

3.1 路由参数配置文件

4ad1b621889d46238af100b7dc8629e1.png

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

ba270b2b49564bb08a81916cbf203a83.png

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

每一行的意思是将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是7struct 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是8if (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);
}

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


http://www.ppmy.cn/embedded/137667.html

相关文章

sql server 查看io资源使用

USE AdventureWorks2022; GO SET STATISTICS IO ON; GO SELECT * FROM Production.ProductCostHistory WHERE StandardCost < 500.00; GO SET STATISTICS IO OFF; GO 如果输出physical reads 或者 read-ahead reads 大于0 &#xff0c;则表示有物…

uni-app表格带分页,后端处理过每页显示多少条

uni-app表格带分页&#xff0c;后端处理过每页可以显示多少条&#xff0c;一句设置好了每页显示的数据量&#xff0c;不需要钱的在进行操作&#xff0c;在进行对数据的截取 <th-table :column"column" :listData"data" :checkSort"checkSort"…

spring gateway 动态路由

##yml配置 spring:application:name: public-gateway # cloud: # gateway: # routes: # - id: mybatis-plus-test # 路由的唯一标识 # uri: http://192.168.3.188:9898 # 目标服务的地址 # predicates: # - Path/test/** # 匹配…

假设检验——基于Python实现

第 7 章——假设检验 7.1 假设检验的原理 7.2 总体均值的检验 【例7-3】——一个 总体均值的检验——大样本 【代码框7-1】——一个 总体均值的检验——大样本 # 一个总体均值的检验(大样本) import pandas as pd from statsmodels.stats.weightstats import ztest exa…

LuaJIT源码分析(六)语法分析

LuaJIT源码分析&#xff08;六&#xff09;语法分析 lua虚拟机在编译lua脚本时&#xff0c;在对源代码进行词法分析之后&#xff0c;还需要进行语法分析&#xff0c;把源代码翻译成字节码。lua 5.1完整的语法描述如下&#xff1a; LuaJIT采用的是递归下降的解析方式&#xff0c…

前端呈现效果:鱼眼相机城市环境图像分割

鱼眼相机城市环境图像分割系统源码&#xff06;数据集分享 [yolov8-seg-SPDConv&#xff06;yolov8-seg-vanillanet等50全套改进创新点发刊_一键训练教程_Web前端展示] 1.研究背景与意义 项目参考ILSVRC ImageNet Large Scale Visual Recognition Challenge 项目来源AAAI G…

爬虫补环境案例---问财网(rpc,jsdom,代理,selenium)

目录 一.环境检测 1. 什么是环境检测 2.案例讲解 二 .吐环境脚本 1. 简介 2. 基础使用方法 3.数据返回 4. 完整代理使用 5. 代理封装 6. 封装所有使用方法 jsdom补环境 1. 环境安装 2. 基本使用 3. 添加参数形式 Selenium补环境 1. 简介 2.实战案例 1. 逆向目…

C哈的刷题计划之输出数字螺旋矩阵(1)

1、盲听C哈说 都说数据结构与算法是编程的核心&#xff0c;它们两个是内功与心法&#x1f600;&#xff0c;其它编程工具只是招式&#xff0c;学会了内功与心法&#xff0c;学习新事物&#xff08;这里特指层出不穷的IT技术&#xff09;就没有那么难了&#xff0c;实际上&#…