反向代理模块jmh

news2025/2/1 8:30:26

 

1 概念

1.1 反向代理概念

  • 反向代理是指以代理服务器来接收客户端的请求,然后将请求转发给内部网络上的服务器,将从服务器上得到的结果返回给客户端,此时代理服务器对外表现为一个反向代理服务器。

  • 对于客户端来说,反向代理就相当于目标服务器,只需要将反向代理当作目标服务器一样发送请求就可以了,并且客户端不需要进行任何设置。

1.2 特点

  • 反向代理是代理服务器,为服务器收发请求,使真实服务器对客户端不可见。

原文链接:https://blog.csdn.net/Dax1_/article/details/124652162

1.3 反向代理场景

  • 正向代理:A可以访问B,B可以访问C。如果A要访问C,那么要在B上面部署正向代理模块。

  • 反向代理:A可以访问B,C可以访问B,B不能访问A和C;如果A要访问C,可以用反向代理。

完整的反向代理图如下所示:

不适用代理的情况下,外网不能访问企业内网的服务器;如果希望访问企业内网,必须要使用反向代理,如果要使用反向代理,企业必须要有一个公网的服务器,公网的IP。

  • 正向代理只有一个模块一个服务程序;
  • 而反向代理有两个模块两个服务程序:外网模块和内网模块。

2 反向代理实现

2.1 反向代理基本思路

  1. 服务程序启动的时候,内网模块向外网模块发起一个TCP连接,建立一条传输通道(称为命令通道);

  2. 之后外网模块读取路由参数配置文件,外网模块需要监听5122,5123,5128三个端口;

  3. 用户1连接了外网服务器的5122端口,外网模块通过命令通道告诉内网模块帮我连192.168.168.1.4的22端口;

  4. 内网模块向外网模块发起TCP连接,建立一条传输通道,同时内网模块连接192.168.1.4 22,整个链路就建立起来了。

外网模块外网程序:

  • 外网模块没有主动的向任何服务器发起连接。
  • 外网程序没有为命令通道的socket准备epoll事件(因为命令通道不需要监听,只有外网程序向内网发送命令,内网程序并不会向外网程序发送命令。)。

内网模块内网程序:

  • 内网程序非阻塞socket命令通道建立之后,再设置为非阻塞的,加入epoll事件。
  • 此时,内网模块与外网模块都进入事件循环,外网程序监听着路由配置文件的端口可以开始接受用户客户端的连接请求了;内网程序epoll中只有命令通道的socket,内网程序也做好了准备接收外网程序的命令。

2.2 框架流程图

2.3 反向代理框架实现

rinetd.cpp外网模块

/*
 * 程序名:rinetd.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(int port);     // 初始化服务端的监听端口。

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

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

int cmdlistensock=0;                // 命令通道监听的socket。
int cmdconnsock=0;                 // 命令通道连接的socket。

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

clogfile logfile;

// cpactive pactive;                        // 进程心跳。

int main(int argc,char *argv[])
{
    if (argc != 4)
    {
        printf("\n");
        printf("Using :./rinetd logfile inifile cmdport\n\n");
        printf("Sample:./rinetd /tmp/rinetd.log /etc/rinetd.conf 5001\n\n");
        printf("        /project/tools/bin/procctl 5 /project/tools/bin/rinetd /tmp/rinetd.log /etc/rinetd.conf 5001\n\n");
        printf("logfile 本程序运行的日志文件名。\n");
        printf("inifile 代理路由参数配置文件。\n");
        printf("cmdport 与内网代理程序的通讯端口。\n\n");
        return -1;
    }

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

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

    // pactive.addpinfo(30,"rinetd");       // 设置进程的心跳超时间为30秒。

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

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

    // 初始化命令通道的监听端口。
    if ( (cmdlistensock=initserver(atoi(argv[3]))) < 0 )
    {   
        logfile.write("initserver(%s) failed.\n",argv[3]); EXIT(-1);
    }

    // 等待内网程序的连接请求,cmdlistensock是阻塞的,并且没有交给epoll。
    struct sockaddr_in client;
    socklen_t len = sizeof(client);
    cmdconnsock = accept(cmdlistensock,(struct sockaddr*)&client,&len);
    if (cmdconnsock < 0)
    {
        logfile.write("accept() failed.\n"); EXIT(-1);
    }
    logfile.write("与内部的命令通道已建立(cmdconnsock=%d)。\n",cmdconnsock);

    // 初始化服务端用于监听外网的socket。
    for (int ii=0;ii<vroute.size();ii++)
    {
        if ( (vroute[ii].listensock=initserver(vroute[ii].srcport)) < 0 )
        {
            logfile.write("initserver(%d) failed.\n",vroute[ii].srcport); EXIT(-1);
        }

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

    // 创建epoll句柄。
    epollfd=epoll_create(1);

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

    // 为监听外网的socket准备可读事件。
    for (int ii=0;ii<vroute.size();ii++)
    {
        ev.events=EPOLLIN;                       // 读事件。
        ev.data.fd=vroute[ii].listensock;  
        epoll_ctl(epollfd,EPOLL_CTL_ADD,vroute[ii].listensock,&ev);   // 把监听外网的socket的读事件加入epollfd中。
    }

    // 创建定时器。
    tfd=timerfd_create(CLOCK_MONOTONIC,TFD_NONBLOCK|TFD_CLOEXEC);  // 创建timerfd。
    struct itimerspec timeout;
    memset(&timeout,0,sizeof(struct itimerspec));
    timeout.it_value.tv_sec = 20;                  // 超时时间为20秒。
    timeout.it_value.tv_nsec = 0;
    timerfd_settime(tfd,0,&timeout,NULL);  // 开始计时。
  
    // 为定时器准备事件。
    ev.events=EPOLLIN;   
    ev.data.fd=tfd;
    epoll_ctl(epollfd,EPOLL_CTL_ADD,tfd,&ev);       // 把定时器的读事件加入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。"); EXIT(-1); }

        // 遍历epoll返回的已发生事件的数组evs。
        for (int ii=0;ii<infds;ii++)
        {
            
            // 如果定时器的时间已到,有三件事要做:1)更新进程的心跳;2)向命令通道发送心跳报文;3)清理空闲的客户端socket。
            if (evs[ii].data.fd==tfd)
            {
                // logfile.write("定时器时间已到。\n");

                timerfd_settime(tfd,0,&timeout,0);  // 重新开始计时。

                // pactive.uptatime();        // 1)更新进程心跳;

                // 2)向命令通道发送心跳报文;
                char buffer[256];
                strcpy(buffer,"<activetest>");
                if (send(cmdconnsock,buffer,strlen(buffer),0)<=0)
                {
                    logfile.write("与内网程序的命令通道已断开。\n"); EXIT(-1);
                }

                // 3)清理空闲的客户端socket。
                for (int jj=0;jj<MAXSOCK;jj++)
                {
                    // 如果客户端socket空闲的时间超过80秒就关掉它。
                    if ( (clientsocks[jj]>0) && ((time(0)-clientatime[jj])>80) )
                    {
                        logfile.write("client(%d,%d) timeout。\n",clientsocks[jj],clientsocks[clientsocks[jj]]);
                        close(clientsocks[jj]);  close(clientsocks[clientsocks[jj]]);
                        // 把数组中对端的socket置空,这一行代码和下一行代码的顺序不能乱。
                        clientsocks[clientsocks[jj]]=0;
                        // 把数组中本端的socket置空,这一行代码和上一行代码的顺序不能乱。
                        clientsocks[jj]=0;
                    }
                }

                continue;
            }
            
      
            
            // 如果发生事件的是监听的listensock,表示外网有新的客户端连上来。
            int jj=0;
            for (jj=0;jj<vroute.size();jj++)
            {
                if (evs[ii].data.fd==vroute[jj].listensock)
                {
                    // 从已连接队列中获取一个已准备好的外网客户端的socket。
                    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;
                    }

                    // 通过命令通道向内网程序发送命令,把路由参数传给它。
                    char buffer[256];
                    memset(buffer,0,sizeof(buffer));
                    sprintf(buffer,"<dstip>%s</dstip><dstport>%d</dstport>",vroute[jj].dstip,vroute[jj].dstport);
                    if (send(cmdconnsock,buffer,strlen(buffer),0)<=0)
                    {
                        logfile.write("与内网的命令通道已断开。\n"); EXIT(-1);
                    }

                    // 接受内网程序的连接,这里的accept()是阻塞的。
                    int dstsock=accept(cmdlistensock,(struct sockaddr*)&client,&len);
                    if (dstsock<0) { close(srcsock); break; }
                    if (dstsock>=MAXSOCK)
                    {
                        logfile.write("连接数已超过最大值%d。\n",MAXSOCK); close(srcsock); close(dstsock); break;
                    }

                    // 把内网和外网客户端的socket对接在一起。

                    // 为新连接的两个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);

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

                    break;
                }
            }

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

            
            // 如果是客户端连接的socke有事件,分三种情况:1)客户端有报文发过来;2)客户端连接已断开;3)有数据要发给客户端。

            // 如果从通道一端的socket读取到了数据,把数据存放在对端socket的缓冲区中。
            //  if (evs[ii].events==EPOLLIN)   // 不要这么写,有读事件是1,有写事件是4,如果读和写都有,是5。
            if (evs[ii].events&EPOLLIN)     // 判断是否为读事件。 
            {
                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);

                logfile.write("from %d,%d bytes\n",evs[ii].data.fd,buflen);

                // 把读取到的数据追加到对端socket的buffer中。
                clientbuffer[clientsocks[evs[ii].data.fd]].append(buffer,buflen);

                // 修改对端socket的事件,增加写事件。
                ev.data.fd=clientsocks[evs[ii].data.fd];
                ev.events=EPOLLIN|EPOLLOUT;
                epoll_ctl(epollfd,EPOLL_CTL_MOD,ev.data.fd,&ev);

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

            // 判断客户端的socket是否有写事件(发送缓冲区没有满)。
            if (evs[ii].events&EPOLLOUT)
            {
                // 把socket缓冲区中的数据发送出去。
                int writen=send(evs[ii].data.fd,clientbuffer[evs[ii].data.fd].data(),clientbuffer[evs[ii].data.fd].length(),0);

                // 以下代码模拟不能一次发完全部数据的场景。
                //int ilen;
                //if (clientbuffer[evs[ii].data.fd].length()>10) ilen=10;
                //else ilen=clientbuffer[evs[ii].data.fd].length();
                //int writen=send(evs[ii].data.fd,clientbuffer[evs[ii].data.fd].data(),ilen,0);

                logfile.write("to %d,%d bytes\n",evs[ii].data.fd,writen);

                // 删除socket缓冲区中已成功发送的数据。
                clientbuffer[evs[ii].data.fd].erase(0,writen);

                // 如果socket缓冲区中没有数据了,不再关心socket的写件事。
                if (clientbuffer[evs[ii].data.fd].length()==0)
                {
                    ev.data.fd=evs[ii].data.fd;
                    ev.events=EPOLLIN;
                    epoll_ctl(epollfd,EPOLL_CTL_MOD,ev.data.fd,&ev);
                }
            }
        }
    }

    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;
}

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

    // 关闭监听内网程序的socket。
    close(cmdlistensock);

    // 关闭内网程序与服务端的命令通道。
    close(cmdconnsock);

    // 关闭全部监听的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);
}

rinetdin.cpp代码:

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

int  cmdconnsock;  // 内网程序与外网程序的命令通道的socket。

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

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

// 向目标ip和端口发起socket连接,bio取值:false-非阻塞io,true-阻塞io。
int conntodst(const char *ip,const int port,bool bio=false);

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

clogfile logfile;

// cpactive pactive;     // 进程心跳。

int main(int argc,char *argv[])
{
    if (argc != 4)
    {
        printf("\n");
        printf("Using :./rinetdin logfile ip port\n\n");
        printf("Sample:./rinetdin /tmp/rinetdin.log 192.168.192.136 5001\n\n");
        printf("        /project/tools/bin/procctl 5 /project/tools/bin/rinetdin /tmp/rinetdin.log 192.168.150.128 5001\n\n");
        printf("logfile 本程序运行的日志文件名。\n");
        printf("ip      外网代理程序的ip地址。\n");
        printf("port    外网代理程序的通讯端口。\n\n\n");
        return -1;
    }

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

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

    // pactive.addpInfo(30,"inetd");       // 设置进程的心跳超时间为30秒。

    // 建立与外网程序的命令通道,采用阻塞的socket。
    if ((cmdconnsock=conntodst(argv[2],atoi(argv[3]),true))<0)
    {
        logfile.write("tcpclient.connect(%s,%s) 失败。\n",argv[2],argv[3]); return -1;
    }

    logfile.write("与外部的命令通道已建立(cmdconnsock=%d)。\n",cmdconnsock);

    // 命令通道建立之后,再设置为非阻塞的。
    fcntl(cmdconnsock,F_SETFL,fcntl(cmdconnsock,F_GETFD,0)|O_NONBLOCK);

    // 创建epoll句柄。
    epollfd=epoll_create(1);

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

    // 为命令通道的socket准备可读事件。
    ev.events=EPOLLIN;
    ev.data.fd=cmdconnsock;
    epoll_ctl(epollfd,EPOLL_CTL_ADD,cmdconnsock,&ev);

    // 创建定时器。
    tfd=timerfd_create(CLOCK_MONOTONIC,TFD_NONBLOCK|TFD_CLOEXEC);  // 创建timerfd。
    struct itimerspec timeout;
    memset(&timeout,0,sizeof(struct itimerspec));
    timeout.it_value.tv_sec = 20;           // 超时时间为20秒。
    timeout.it_value.tv_nsec = 0;
    timerfd_settime(tfd,0,&timeout,0);  // 开始计时。
  
    // 为定时器准备事件。
    ev.events=EPOLLIN;               
    ev.data.fd=tfd;
    epoll_ctl(epollfd,EPOLL_CTL_ADD,tfd,&ev);       // 把定时器的读事件加入epollfd中。

    // pactive.addpinfo(30,"rinetdin");   // 设置进程的心跳超时间为30秒。

    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++)
        {
            
            // 如果定时器的时间已到,有两件事要做:1)设置进程的心跳;2)清理空闲的客户端socket。
            if (evs[ii].data.fd==tfd)
            {
                // logfile.write("定时器时间已到。\n");

                timerfd_settime(tfd,0,&timeout,NULL);  // 重新开始计时。

                // pactive.uptatime();        // 1)更新进程心跳。

                // 2)清理空闲的客户端socket。
                for (int jj=0;jj<MAXSOCK;jj++)
                {
                    // 如果客户端socket空闲的时间超过80秒就关掉它。
                    if ( (clientsocks[jj]>0) && ((time(0)-clientatime[jj])>80) )
                    {
                        logfile.write("client(%d,%d) timeout。\n",clientsocks[jj],clientsocks[clientsocks[jj]]);
                        close(clientsocks[jj]);  close(clientsocks[clientsocks[jj]]);
                        clientsocks[clientsocks[jj]]=0;     // 把数组中对端的socket置空,这一行代码和下一行代码的顺序不能乱。
                        clientsocks[jj]=0;                         // 把数组中本端的socket置空,这一行代码和上一行代码的顺序不能乱。
                    }
                }

                continue;
            }
            

            // 如果发生事件的是命令通道。
            if (evs[ii].data.fd==cmdconnsock)
            {
                // 读取命令通道socket报文内容。
                char buffer[256];
                memset(buffer,0,sizeof(buffer));
                if (recv(cmdconnsock,buffer,sizeof(buffer),0)<=0)
                {
                    logfile.write("与外网的命令通道已断开。\n"); EXIT(-1);
                }

                // 如果收到的是心跳报文。
                if (strcmp(buffer,"<activetest>")==0) continue;

                // 如果收到的是新建连接的命令。

                // 向外网服务端发起连接请求。
                int srcsock=conntodst(argv[2],atoi(argv[3]));
                if (srcsock<0) continue;
                if (srcsock>=MAXSOCK)
                {
                    logfile.write("连接数已超过最大值%d。\n",MAXSOCK); close(srcsock); continue;
                }

                // 从命令报文内容中获取目标服务器的地址和端口。
                char dstip[11];
                int  dstport;
                getxmlbuffer(buffer,"dstip",dstip,30);
                getxmlbuffer(buffer,"dstport",dstport);

                // 向目标服务器的地址和端口发起socket连接。
                int dstsock=conntodst(dstip,dstport);
                if (dstsock<0) { close(srcsock); continue; }
                if (dstsock>=MAXSOCK)
                { 
                    logfile.write("连接数已超过最大值%d。\n",MAXSOCK); close(srcsock); close(dstsock); continue;
                } 

                // 把内网和外网的socket对接在一起。
                logfile.write("新建内外网通道(%d,%d) ok。\n",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; clientsocks[dstsock]=srcsock;
                clientatime[srcsock]=time(0); clientatime[dstsock]=time(0);

                continue;
            }
            

            
            // 如果是客户端连接的socke有事件,分三种情况:1)客户端有报文发过来;2)客户端连接已断开;3)有数据要发给客户端。

            // 如果从通道一端的socket读取到了数据,把数据存放在对端socket的缓冲区中。
            //  if (evs[ii].events==EPOLLIN)   // 不要这么写,有读事件是1,有写事件是4,如果读和写都有,是5。
            if (evs[ii].events&EPOLLIN)     // 判断是否为读事件。 
            {
                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);

                logfile.write("from %d,%d bytes\n",evs[ii].data.fd,buflen);

                // 把读取到的数据追加到对端socket的buffer中。
                clientbuffer[clientsocks[evs[ii].data.fd]].append(buffer,buflen);

                // 修改对端socket的事件,增加写事件。
                ev.data.fd=clientsocks[evs[ii].data.fd];
                ev.events=EPOLLIN|EPOLLOUT;
                epoll_ctl(epollfd,EPOLL_CTL_MOD,ev.data.fd,&ev);

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

            // 判断客户端的socket是否有写事件(发送缓冲区没有满)。
            if (evs[ii].events&EPOLLOUT)
            {
                // 把socket缓冲区中的数据发送出去。
                int writen=send(evs[ii].data.fd,clientbuffer[evs[ii].data.fd].data(),clientbuffer[evs[ii].data.fd].length(),0);

                // 以下代码模拟不能一次发完全部数据的场景。
                //int ilen;
                //if (clientbuffer[evs[ii].data.fd].length()>10) ilen=10;
                //else ilen=clientbuffer[evs[ii].data.fd].length();
                //int writen=send(evs[ii].data.fd,clientbuffer[evs[ii].data.fd].data(),ilen,0);

                logfile.write("to %d,%d bytes\n",evs[ii].data.fd,writen);

                // 删除socket缓冲区中已成功发送的数据。
                clientbuffer[evs[ii].data.fd].erase(0,writen);

                // 如果socket缓冲区中没有数据了,不再关心socket的写件事。
                if (clientbuffer[evs[ii].data.fd].length()==0)
                {
                    ev.data.fd=evs[ii].data.fd;
                    ev.events=EPOLLIN;
                    epoll_ctl(epollfd,EPOLL_CTL_MOD,ev.data.fd,&ev);
                }
            }
        }
    }

    return 0;
}

// 向目标地址和端口发起socket连接。
int conntodst(const char *ip,const int port,bool bio)
{
    // 第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设置为非阻塞。
    if (bio==false) 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);

    // 关闭内网程序与外网程序的命令通道。
    close(cmdconnsock);

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

    close(epollfd);   // 关闭epoll。

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

    exit(0);
}

 

 

 

 

 

 

 

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2289235.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

全国31省空间权重矩阵(地理相邻空间、公路铁路地理距离空间、经济空间)权重矩阵数据-社科数据

中国31个省份空间权重矩阵-社科数据https://download.csdn.net/download/paofuluolijiang/90028597 https://download.csdn.net/download/paofuluolijiang/90028597 空间权重矩阵是反映个体在空间中依赖关系的矩阵&#xff0c;本数据计算全国31个省三种标准化处理的空间权重矩…

前端开发之jsencrypt加密解密的使用方法和使用示例

目录 RSA密钥生成选项简介 jsencrypt 使用教程 一、安装 jsencrypt 二、使用 jsencrypt 进行加密和解密 1. 创建密钥对 2. 加密数据 3. 解密数据 三、实际应用示例 加密数据并存储到 localStorage 中&#xff1a; 从 localStorage 中读取加密数据并解密&#xff1a; …

98.1 AI量化开发:长文本AI金融智能体(Qwen-Long)对金融研报大批量处理与智能分析的实战应用

目录 0. 承前1. 简介1.1 通义千问(Qwen-Long)的长文本处理能力 2. 基础功能实现2.1 文件上传2.2 单文件分析2.3 多文件分析 3. 汇总代码&运行3.1 封装的工具函数3.2 主要功能特点3.3 使用示例3.4 首次运行3.5 运行结果展示 4. 注意事项4.1 文件要求4.2 错误处理机制4.3 最佳…

PPT演示设置:插入音频同步切换播放时长计算

PPT中插入音频&同步切换&放时长计算 一、 插入音频及音频设置二、设置页面切换和音频同步三、播放时长计算 一、 插入音频及音频设置 1.插入音频&#xff1a;点击菜单栏插入-音频-选择PC上的音频&#xff08;已存在的音频&#xff09;或者录制音频&#xff08;现场录制…

链表的简单介绍

申明&#xff1a; 我们的链表可以写在类中或者接口中&#xff08;接口中更好&#xff09;&#xff0c;这里我们是写在类当中。 1.节点的构造是由当前数据和指向下一个结点的地址组成&#xff0c;那么我们在当前这个链表的类中需要实现一个节点那么此时就需要用到内部类(当一个…

基于聚类与相关性分析对马来西亚房价数据进行分析

碎碎念&#xff1a;由于最近太忙了&#xff0c;更新的比较慢&#xff0c;提前祝大家新春快乐&#xff0c;万事如意&#xff01;本数据集的下载地址&#xff0c;读者可以自行下载。 1.项目背景 本项目旨在对马来西亚房地产市场进行初步的数据分析&#xff0c;探索各州的房产市…

ARM嵌入式学习--第十一天(中断处理 , ADC)

--中断的概念 中断是指计算机运行过程中&#xff0c;出现某些意外情况需主机干预时&#xff0c;机器能自动停止正在运行的程序并转入处理新情况的程序&#xff0c;处理完毕后又返回被暂停的程序继续运行 --CPU处理事情的方式 -轮询方式 不断查询是否有事情需要处理&#xff0c…

消息队列篇--通信协议篇--网络通信模型(OSI7层参考模型,TCP/IP分层模型)

一、OSI参考模型&#xff08;Open Systems Interconnection Model&#xff09; OSI参考模型是一个用于描述和标准化网络通信功能的七层框架。它由国际标准化组织&#xff08;ISO&#xff09;提出&#xff0c;旨在为不同的网络设备和协议提供一个通用的语言和结构&#xff0c;以…

“新月之智”智能战术头盔系统(CITHS)

新月人物传记&#xff1a;人物传记之新月篇-CSDN博客 相关文章链接&#xff08;更新&#xff09;&#xff1a; 星际战争模拟系统&#xff1a;新月的编程之道-CSDN博客 新月智能护甲系统CMIA--未来战场的守护者-CSDN博客 目录 一、引言 二、智能头盔控制系统概述 三、系统架…

实验七 JSP内置对象II

实验七 JSP内置对象II 目的&#xff1a; 1、掌握JSP内置对象的使用。 2、理解JSP的作用域 3、掌握session&#xff0c;application对象的使用 实验要求&#xff1a; 1、完成实验题目 2、要求提交实验报告&#xff0c;将代码和实验结果页面截图放入报告中 实验过程&#xff1a…

OpenCV:Harris、Shi-Tomasi角点检测

简述 在计算机视觉和图像处理领域&#xff0c;角点是一种重要的特征点&#xff0c;通常是图像中梯度变化剧烈的区域&#xff0c;例如建筑物的拐角、棋盘的交点等。角点检测广泛应用于目标跟踪、运动检测、拼接全景图 等任务。 本文将介绍 Harris 角点检测 和 Shi-Tomasi 角点…

零代码搭建个人博客—Zblog结合内网穿透发布公网

目录 一、准备工作二、Z-blog 网站搭建1. XAMPP 环境设置2. Z-blog 安装3. Z-blog 网页测试 三、内网穿透工具 Cpolar 的安装和配置1. Cpolar 安装2. Cpolar 云端设置3. Cpolar 本地设置 四、本地网页发布五、注意六、本次经历总结 大家好&#xff0c;我是学问小小谢。 最近心血…

宏_wps_宏修改word中所有excel表格的格式_设置字体对齐格式_删除空行等

需求&#xff1a; 将word中所有excel表格的格式进行统一化&#xff0c;修改其中的数字类型为“宋体&#xff0c; 五号&#xff0c;右对齐&#xff0c; 不加粗&#xff0c;不倾斜”&#xff0c;其中的中文为“宋体&#xff0c; 五号&#xff0c; 不加粗&#xff0c;不倾斜” 数…

xss靶场

xss-labs下载地址&#xff1a;GitHub - do0dl3/xss-labs: xss 跨站漏洞平台 xss常见触发标签&#xff1a;XSS跨站脚本攻击实例与防御策略-CSDN博客 level-1 首先查看网页的源代码发现get传参的name的值test插入了html里头&#xff0c;还回显了payload的长度。 <!DOCTYPE …

线段树(Segment Tree)和树状数组

线段树&#xff08;Segment Tree&#xff09;和树状数组 线段树的实现链式&#xff1a;数组实现 解题思路树状数组 线段树是 二叉树结构 的衍生&#xff0c;用于高效解决区间查询和动态修改的问题&#xff0c;其中区间查询的时间复杂度为 O(logN)&#xff0c;动态修改单个元素的…

MySQL注入中load_file()函数的使用

前言 在Msql注入中&#xff0c;load_file()函数在获得webshell以及提权过程中起着十分重要的作用&#xff0c;常被用来读取各种配置文件 而load_file函数只有在满足两个条件的情况下才可以使用&#xff1a; 文件权限&#xff1a;chmod ax pathtofile 文件大小&#xff1a;必须…

DeepSeek-R1 论文解读 —— 强化学习大语言模型新时代来临?

近年来&#xff0c;人工智能&#xff08;AI&#xff09;领域发展迅猛&#xff0c;大语言模型&#xff08;LLMs&#xff09;为通用人工智能&#xff08;AGI&#xff09;的发展开辟了道路。OpenAI 的 o1 模型表现非凡&#xff0c;它引入的创新性推理时缩放技术显著提升了推理能力…

使用Pygame制作“贪吃蛇”游戏

贪吃蛇 是一款经典的休闲小游戏&#xff1a;玩家通过操控一条会不断变长的“蛇”在屏幕中移动&#xff0c;去吃随机出现的食物&#xff0c;同时要避免撞到墙壁或自己身体的其他部分。由于其逻辑相对简单&#xff0c;但可玩性和扩展性都不错&#xff0c;非常适合作为新手练习游戏…

海外问卷调查如何影响企业的经营?在品牌建设中有何指导意义?

市场调查的定义&#xff1a;通过科学的方法&#xff0c;有目的地、系统地搜集整理一些市场信息&#xff0c;其目的在于了解当下市场现状和发展前景&#xff0c;为企业生产和品牌打造提供一些科学的指导意见&#xff0c;这是任何大企业、中小企业、初创企业都必须重视的一个重要…

unity学习23:场景scene相关,场景信息,场景跳转

目录 1 默认场景和Assets里的场景 1.1 scene的作用 1.2 scene作为project的入口 1.3 默认场景 2 场景scene相关 2.1 创建scene 2.2 切换场景 2.3 build中的场景&#xff0c;在构建中包含的场景 &#xff08;否则会认为是失效的Scene&#xff09; 2.4 Scenes in Bui…