反向代理模块

 

1 概念

 

1.1 反向代理概念

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

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

1.2 特点

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

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

1.3 反向代理场景

66efe5f8d1f5452e91b585a637f7f24b.png

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

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

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

9f3a3b0fe7484b03b908d8cf51f1db7b.png

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

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

2 反向代理实现

2.1 反向代理基本思路

598f3677060d4c90b86d26318bcdc535.png

  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 框架流程图

2fedc1b2e17646b3bd2436058a58c490.png

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.rhkb.cn/news/474659.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

STM32 独立看门狗(IWDG)详解

目录 一、引言 二、独立看门狗的作用 三、独立看门狗的工作原理 1.时钟源 2.计数器 3.喂狗操作 4.超时时间计算 5.复位机制 四、独立看门狗相关寄存器 1.键寄存器&#xff08;IWDG_KR&#xff09; 2.预分频寄存器&#xff08;IWDG_PR&#xff09; 3.重载寄存器&…

足球虚拟越位线技术FIFA OT(二)

足球虚拟越位线技术FIFA OT&#xff08;二&#xff09; 在FIFA认证测试过程中&#xff0c;留给VAR系统绘制越位线的时间只有90秒&#xff08;在比赛中时间可能更短&#xff09;&#xff0c;那么90秒内要做什么事呢&#xff0c;首先场地上球员做出踢球动作&#xff0c;然后VAR要…

MySQL数据库3——函数与约束

一.函数 1.字符串函数 MySQL中内置了很多字符串函数&#xff0c;常用的几个如下&#xff1a; 使用方法&#xff1a; SELECT 函数名(参数);注意&#xff1a;MySQL中的索引值即下标都是从1开始的。 2.数值函数 常见的数值函数如下&#xff1a; 使用方法&#xff1a; SELECT…

Proteus 8.17的详细安装教程

通过百度网盘分享的文件&#xff1a;Proteus8.17(64bit&#xff09;.zip 链接&#xff1a;https://pan.baidu.com/s/1zu8ts1Idhgg9DGUHpAve7Q 提取码&#xff1a;8q8v 1.右击【Proteus8.17(64bit&#xff09;.zip】&#xff0c;选择【全部解压缩......】。 &#xff0c; 2.…

MySQL安装及数据库基础

目录 一. MySQL下载安装 1.1 安装&#xff08;如果之前有安装过MySQL&#xff0c;先执行下面的卸载流程&#xff09; 1.1.1 更新系统的软件包列表 1.1.2 安装MySQL服务器 1.1.3 检查MySQL服务是否启动&#xff0c;若没有启动手动启动 1.1.4 登录MySQL&#x…

NavVis VLX3的精度怎么去进行验证?【上海沪敖3D】

01、精度评价现状 三维捕捉行业还没有建立一个用于估算或验证移动激光扫描系统精度的统一标准。因此&#xff0c;需要高精度交付成果的专业人士很难相信设备所标注的精度规格&#xff0c;也就很难知道基于SLAM的移动激光扫描系统是否适合当前的项目。 NavVis将通过展示一种严格…

css3新特性(二十六课)

1、css3盒子模型 box - sizing: content - box&#xff1b; 是 CSS 中用于定义盒模型宽度和高度计算方式的一个属性值。在这种盒模型下&#xff0c;元素的宽度和高度&#xff08;width和height属性&#xff09;仅包括内容区域&#xff08;content&#xff09;的大小&#xff…

macbook外接2k/1080p显示器调试经验

准备工具 电脑 满足电脑和显示器要求的hdmi线或者转接头或者扩展坞 betterdisplay软件 Dell P2419H的最佳显示信息如下 飞利浦 245Es 2K的最佳显示比例如下 首选1152

【Hadoop】【大数据技术基础】实践三 NoSQL数据库 大数据基础编程、实验和案例教程(第2版)

第6章 典型NoSQL数据库的安装和使用 Redis安装和使用MongoDB的安装和使用 6.1 Redis安装和使用 6.1.1 Redis简介 Redis是一个键值&#xff08;key-value&#xff09;存储系统&#xff0c;即键值对非关系型数据库&#xff0c;和Memcached类似&#xff0c;目前正在被越来越多的…

零基础Java第二十二期:异常(二)

目录 一、异常的捕获 1.1. 多个异常&#xff0c;一次捕获 1.2. 异常之间的父子关系 1.3. finally 二、自定义异常 一、异常的捕获 1.1. 多个异常&#xff0c;一次捕获 由于Exception类是所有异常类的父类&#xff0c;因此可以用这个类型表示捕捉所有异常。也就是说&am…

IO流(九):打印流-字节打印流PrintStream、字符打印流PrintWriter

目录 1、什么是打印流&#xff1f; 2、字节打印流-PrintStream 2.1 代码演示以及注释 2.2 演示几种字节打印流常用的创建方式 3、字符打印流-PrintWriter 3.1 代码演示以及注释 3.2 演示几种字符打印流常用的创建方式 4、案例-System.out.println();输出内容重定向到指…

如何将几个音频合成一个音频?非常简单的几种合成方法

如何将几个音频合成一个音频&#xff1f;音频合成不仅仅是将不同的音频文件按顺序排列&#xff0c;它还可能涉及到音量调节、剪辑、淡入淡出、音效调整等多个方面。对于一些专业的音频制作人员来说&#xff0c;音频的每一部分细节都可能需要精心打磨&#xff0c;以确保最终合成…

JVM性能分析工具JProfiler的使用

一、基本概念 JProfiler&#xff1a;即“Java Profiler”&#xff0c;即“Java分析器”或“Java性能分析工具”。它是一款用于Java应用程序的性能分析和调试工具&#xff0c;主要帮助开发人员识别和解决性能瓶颈问题。 JVM&#xff1a;即“Java Virtual Machine”&#xff0c…

Flutter 扫描二维码

在今天的移动开发中&#xff0c;二维码扫描已经成为了常见的功能之一。Flutter作为一款跨平台的开发框架&#xff0c;提供了丰富的插件和功能&#xff0c;使得开发者可以轻松实现二维码扫描以及图像识别功能。本文将介绍如何在Flutter中通过结合 scan 插件、permission_handler…

金山云Q3调整后EBITDA率提升至9.8% 经营效率和盈利能力强劲增长

11月19日&#xff0c;金山云公布了2024年第三季度业绩。 季度内&#xff0c;公司在收入规模、盈利能力、经营现金流方面都取得了扎实的进展。财报显示&#xff0c;金山云Q3营收18.9亿元&#xff0c;同比回归两位数快速增长&#xff0c;达16.0%&#xff1b;公有云实现收入11.8亿…

AI模型新发现:精度的重要性超出预期

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

Pytest 学习 @allure.severity 标记用例级别的使用

一、前言 使用allure.serverity注解&#xff0c;可以在allure报告中清晰的看到不同级别用例情况 使用等级介绍 allure提供的枚举类 二、等级介绍 二、等级介绍 blocker&#xff1a;阻塞缺陷&#xff08;功能未实现&#xff0c;无法下一步&#xff09; critical&#xff1a;…

R语言 | 宽数据变成一列,保留对应的行名和列名

对应稀疏矩阵 转为 宽数据框&#xff0c;见 数据格式转换 | 稀疏矩阵3列还原为原始矩阵/数据框&#xff0c;自定义函数 df3toMatrix() 目的&#xff1a;比如查看鸢尾花整体的指标分布&#xff0c;4个指标分开&#xff0c;画到一个图中。每个品种画一个图。 1.数据整理&#…

Java——多线程

&#x1f3e1;个人主页&#xff1a;謬熙&#xff0c;欢迎各位大佬到访❤️❤️❤️~ &#x1f472;个人简介&#xff1a;本人编程小白&#xff0c;正在学习互联网开发求职知识…… 如果您觉得本文对您有帮助的话&#xff0c;记得点赞&#x1f44d;、收藏⭐️、评论&#x1f4ac…

HCIP --OSI七层参考模型回顾、TCP/UDP协议复习

目录 一、OSI 二、重要的三个协议报头格式 名词注解 MTU 封装 解封装 PDU ARP DNS TCP/IP与OSI的区别 三、数据包转发过程 四、获取目标ip地址方式 五、获取目标mac地址方式 六、交换机的工作原理 七、TCP/UDP TCP&#xff08;Transmission Control Protocol&a…