反向代理模块jmh
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 反向代理基本思路

服务程序启动的时候,内网模块向外网模块发起一个TCP连接,建立一条传输通道(称为命令通道);
之后外网模块读取路由参数配置文件,外网模块需要监听5122,5123,5128三个端口;
用户1连接了外网服务器的5122端口,外网模块通过命令通道告诉内网模块帮我连192.168.168.1.4的22端口;
内网模块向外网模块发起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);
}
相关文章:
反向代理模块jmh
1 概念 1.1 反向代理概念 反向代理是指以代理服务器来接收客户端的请求,然后将请求转发给内部网络上的服务器,将从服务器上得到的结果返回给客户端,此时代理服务器对外表现为一个反向代理服务器。 对于客户端来说,反向代理就相当…...
AI应用部署——streamlit
如何把项目部署到一个具有公网ip地址的服务器上,让他人看到? 可以利用 streamlit 的社区云免费部署 1、生成requirements.txt文件 终端输入pip freeze > requirements.txt即可 requirements.txt里既包括自己安装过的库,也包括这些库的…...
文明的基因:在传承中破茧重生
敦煌莫高窟的壁画历经千年风雨,至今仍在向世界讲述着东方美学的密码。那些斑驳的壁画上,既有北魏时期的天竺梵音,也留存着盛唐气象的长安余韵。文明的基因从未停止生长,就像莫高窟的壁画师们在临摹前朝壁画时,总会在衣…...
全国31省空间权重矩阵(地理相邻空间、公路铁路地理距离空间、经济空间)权重矩阵数据-社科数据
中国31个省份空间权重矩阵-社科数据https://download.csdn.net/download/paofuluolijiang/90028597 https://download.csdn.net/download/paofuluolijiang/90028597 空间权重矩阵是反映个体在空间中依赖关系的矩阵,本数据计算全国31个省三种标准化处理的空间权重矩…...
MySQL数据类型转换应注意什么?
文章目录 1. **隐式转换**2. **显式转换**3. **数据截断**4. **字符集与排序规则**5. **日期和时间转换**6. **数值转换**7. **NULL 处理**8. **性能影响**9. **错误处理**10. **函数选择**示例总结 在 MySQL 中进行数据类型转换时,需要注意以下几个关键点ÿ…...
前端开发之jsencrypt加密解密的使用方法和使用示例
目录 RSA密钥生成选项简介 jsencrypt 使用教程 一、安装 jsencrypt 二、使用 jsencrypt 进行加密和解密 1. 创建密钥对 2. 加密数据 3. 解密数据 三、实际应用示例 加密数据并存储到 localStorage 中: 从 localStorage 中读取加密数据并解密: …...
ESP32和STM32在处理中断方面的区别
为了通俗地讲解ESP32和STM32在处理中断方面的区别,我们可以把它们想象成两个不同的“智能管家”系统,各自负责管理一个家庭(即嵌入式项目)的各种任务。我们将重点放在如何处理突发事件(即中断)上。 ESP32 …...
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.插入音频:点击菜单栏插入-音频-选择PC上的音频(已存在的音频)或者录制音频(现场录制…...
链表的简单介绍
申明: 我们的链表可以写在类中或者接口中(接口中更好),这里我们是写在类当中。 1.节点的构造是由当前数据和指向下一个结点的地址组成,那么我们在当前这个链表的类中需要实现一个节点那么此时就需要用到内部类(当一个…...
Cocoa和Cocoa Touch是什么语言写成的?什么是Cocoa?编程语言中什么是框架?为什么苹果公司Cocoa类库有不少NS前缀?Swift编程语言?
Cocoa和Cocoa Touch是什么语言写成的? 二者主要都是用Objective-C语言编写而成的。 什么是Cocoa? Cocoa是苹果操作系统macOS和iOS上的应用程序开发框架集合,核心语言是Objective-C编程语言,在移动平台被称为Cocoa Touch,Cocoa包含多个子框架…...
AI-System 学习
《AI系统原理与架构》ZOMI https://github.com/chenzomi12/AISystem CPU、GPU、NPU 芯片基础 华为 Ascend 产品 NVLink的发展 & 结构 NVLink 拓扑、DGX 硬件渲染图...
基于聚类与相关性分析对马来西亚房价数据进行分析
碎碎念:由于最近太忙了,更新的比较慢,提前祝大家新春快乐,万事如意!本数据集的下载地址,读者可以自行下载。 1.项目背景 本项目旨在对马来西亚房地产市场进行初步的数据分析,探索各州的房产市…...
ARM嵌入式学习--第十一天(中断处理 , ADC)
--中断的概念 中断是指计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回被暂停的程序继续运行 --CPU处理事情的方式 -轮询方式 不断查询是否有事情需要处理,…...
消息队列篇--通信协议篇--网络通信模型(OSI7层参考模型,TCP/IP分层模型)
一、OSI参考模型(Open Systems Interconnection Model) OSI参考模型是一个用于描述和标准化网络通信功能的七层框架。它由国际标准化组织(ISO)提出,旨在为不同的网络设备和协议提供一个通用的语言和结构,以…...
“新月之智”智能战术头盔系统(CITHS)
新月人物传记:人物传记之新月篇-CSDN博客 相关文章链接(更新): 星际战争模拟系统:新月的编程之道-CSDN博客 新月智能护甲系统CMIA--未来战场的守护者-CSDN博客 目录 一、引言 二、智能头盔控制系统概述 三、系统架…...
Go Fx 框架使用指南:深入理解 Provide 和 Invoke 的区别
1. 什么是 Fx 框架? Fx 是一个基于 Go 语言的依赖注入框架,专注于简化应用程序的生命周期管理和依赖的构建。在复杂的应用程序中,Fx 通过模块化的设计方式将组件连接起来,使开发者能够更高效地管理依赖关系。 Fx 的核心理念是&a…...
实验七 JSP内置对象II
实验七 JSP内置对象II 目的: 1、掌握JSP内置对象的使用。 2、理解JSP的作用域 3、掌握session,application对象的使用 实验要求: 1、完成实验题目 2、要求提交实验报告,将代码和实验结果页面截图放入报告中 实验过程:…...
OpenCV:Harris、Shi-Tomasi角点检测
简述 在计算机视觉和图像处理领域,角点是一种重要的特征点,通常是图像中梯度变化剧烈的区域,例如建筑物的拐角、棋盘的交点等。角点检测广泛应用于目标跟踪、运动检测、拼接全景图 等任务。 本文将介绍 Harris 角点检测 和 Shi-Tomasi 角点…...
RK3568 opencv播放视频
文章目录 一、opencv相关视频播放类1. cv::VideoCapture 类主要构造方法:主要方法: 2. 视频播放基本流程代码示例: 3. 获取和设置视频属性4. 结合 FFmpeg 使用5. OpenCV 视频播放的局限性6. 结合 Qt 实现更高级的视频播放总结 二、QT中的代码…...
白话DeepSeek-R1论文(一)|AI的顿悟时刻:DeepSeek-R1-Zero 纯强化学习解锁推理新境界
最近有不少朋友来询问Deepseek的技术核心,今天开始陆续针对DeepSeek-R1论文中的核心内容进行解读,并且用大家都能听懂的方式来解读。 AI的顿悟时刻:DeepSeek-R1-Zero 纯强化学习解锁推理新境界 你有没有想过,人工智能是如何学会…...
青少年编程与数学 02-008 Pyhon语言编程基础 10课题、列表与循环语句
青少年编程与数学 02-008 Pyhon语言编程基础 10课题、列表与循环语句 一、列表二、定义与使用定义列表访问列表元素访问列表的切片修改列表元素列表的其他操作 三、运算1. 列表连接(Concatenation)2. 列表复制(Copying)3. 列表重复…...
caddy2配置http_basic用于验证用户名密码才允许访问页面
参考: basicauth (Caddyfile指令) — Caddy v2中文文档 1,查看caddy是否已经包含了Basic Auth插件 命令:caddy list-modules | grep http_basic 如果显示: http.authentication.providers.http_basic 则代表包含 Basic Auth 模…...
FOC核心原理的C语言实现
概述 应用FOC算法,比如无人机、电动汽车或工业电机控制。因此,除了理论,还需要提供实用的实现步骤、常见问题及解决方案,比如如何获取电机的位置信息(编码器或传感器),如何处理电流采样&#x…...
利用Manim库结合`matplotlib`、`numpy`和`scipy`来制作工作流程动画
以下是一个利用Manim库结合matplotlib、numpy和scipy来制作工作流程动画,展示流场速度分布计算模型,以及三流喷嘴的速度场和主要参数分布的可视化图形与动画的示例代码。这个示例将模拟一个简化的三流喷嘴速度场,通过计算速度分布并将其可视化…...
零代码搭建个人博客—Zblog结合内网穿透发布公网
目录 一、准备工作二、Z-blog 网站搭建1. XAMPP 环境设置2. Z-blog 安装3. Z-blog 网页测试 三、内网穿透工具 Cpolar 的安装和配置1. Cpolar 安装2. Cpolar 云端设置3. Cpolar 本地设置 四、本地网页发布五、注意六、本次经历总结 大家好,我是学问小小谢。 最近心血…...
宏_wps_宏修改word中所有excel表格的格式_设置字体对齐格式_删除空行等
需求: 将word中所有excel表格的格式进行统一化,修改其中的数字类型为“宋体, 五号,右对齐, 不加粗,不倾斜”,其中的中文为“宋体, 五号, 不加粗,不倾斜” 数…...
electron 应用开发实践
参考链接: https://blog.csdn.net/2401_83384536/article/details/140549279...
xss靶场
xss-labs下载地址:GitHub - do0dl3/xss-labs: xss 跨站漏洞平台 xss常见触发标签:XSS跨站脚本攻击实例与防御策略-CSDN博客 level-1 首先查看网页的源代码发现get传参的name的值test插入了html里头,还回显了payload的长度。 <!DOCTYPE …...
Koa 基础篇(二)—— 路由与中间件
let app new Koa() router.get(“/”,async ctx > { ctx.body “hello koa router” }) app.use(router.routes()) app.use(router.allowedMethods()) app.listen(3000) 运行项目,在浏览器访问本地3000端口,在页面上就会看到输出的语句。这就…...
