反向代理模块kd
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);
}
相关文章:

反向代理模块kd
1 概念 1.1 反向代理概念 反向代理是指以代理服务器来接收客户端的请求,然后将请求转发给内部网络上的服务器,将从服务器上得到的结果返回给客户端,此时代理服务器对外表现为一个反向代理服务器。 对于客户端来说,反向代理就相当于…...
leaflet前端初始化项目
1、通过npm安装leaflet包,或者直接在项目中引入leaflet.js库文件。 npm 安装:npm i leaflet 如果在index.html中引入leaflet.js,在项目中可以直接使用变量L. 注意:尽量要么使用npm包,要么使用leaflet.js库,两者一起使用容易发生…...
CMS DTcms 靶场(弱口令、文件上传、tasklist提权、开启远程桌面3389、gotohttp远程登录控制)
环境说明 攻击机kali:192.168.111.128 信息收集 主机发现 ┌──(root㉿kali-plus)-[~/Desktop] └─# nmap -sP 192.168.111.0/24 Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-11-23 14:57 CST Nmap scan report for 192.168.111.1 Host is up (0.00039s latenc…...

Docker 入门与实战:从安装到容器管理的完整指南
🚀 Docker 入门与实战:从安装到容器管理的完整指南 🌟 📖 简介 在现代软件开发中,容器化技术已经成为不可或缺的一部分。而 Docker 作为容器化领域的领头羊,以其轻量级、高效和跨平台的特性,深…...

git删除本地分支
一、命令方式 1、查看本地分支 git branch 2、切换到一个不删除的分支 git checkout branch_name 3、强制删除分支 git branch -D local_branch_name 二、工具方式 1、选择"Browse references",右键"Delete branch"...

spring cloud gateway限流常见算法
目录 一、网关限流 1、限流的作用 1. 保护后端服务 2. 保证服务质量 (QoS) 3. 避免滥用和恶意攻击 4. 减少资源浪费 5. 提高系统可扩展性和稳定性 6. 控制不同用户的访问频率 7. 提升用户体验 8. 避免API滥用和负载过高 9. 监控与分析 10. 避免系统崩溃 2、网关限…...

本地使用docker部署DeepSeek大模型
1、相关技术介绍 1.1、RAG RAG(Retrieval Augmented Generation),即“检索,增强,生成”,用于提升自然语言处理任务的性能。其核心思想是通过检索相关信息来增强生成模型的能力,具体步骤如下&am…...
C++ 设计模式-外观模式
外观模式的定义 外观模式是一种 结构型设计模式,它通过提供一个简化的接口来隐藏系统的复杂性。外观模式的核心思想是: 封装复杂子系统:将多个复杂的子系统或组件封装在一个统一的接口后面。提供简单接口:为客户端提供一个更简单、更易用的接口,而不需要客户端直接与复杂…...

【Linux网络编程】应用层协议HTTP(请求方法,状态码,重定向,cookie,session)
🎁个人主页:我们的五年 🔍系列专栏:Linux网络编程 🌷追光的人,终会万丈光芒 🎉欢迎大家点赞👍评论📝收藏⭐文章 Linux网络编程笔记: https://blog.cs…...
SQL进阶技巧:如何统计用户跨端消费行为?
目录 0 问题描述 2 问题剖析 技术难点解析 3 完整解决方案 步骤1:构造全量日期平台组合 步骤2:用户行为标记 步骤3:最终关联聚合 4 核心技巧总结 5 复杂度评估 往期精彩 0 问题描述 支出表: Spending +-------------+---------+ | Column Name | Type | +-----…...

Fiddler笔记
文章目录 一、与F12对比二、核心作用三、原理四、配置1.Rules:2.配置证书抓取https包3.设置过滤器4、抓取App包 五、模拟弱网测试六、调试1.线上调试2.断点调试 七、理论1.四要素2.如何定位前后端bug 注 一、与F12对比 相同点: 都可以对http和https请求进行抓包分析…...

基于SpringBoot+Vue的老年人体检管理系统的设计与实现(源码+SQL脚本+LW+部署讲解等)
专注于大学生项目实战开发,讲解,毕业答疑辅导,欢迎高校老师/同行前辈交流合作✌。 技术范围:SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:…...

51c自动驾驶~合集51
我自己的原文哦~ https://blog.51cto.com/whaosoft/13320191 #毫末最新OAD 轨迹偏移学习助力端到端新SOTA~ 端到端自动驾驶技术在近年来取得了显著进展。在本研究中,我们提出了轨迹偏移学习,将传统的直接预测自车轨迹,转换为预测相对于…...
Redis 监视器:深入解析与实战指南
Redis 监视器:深入解析与实战指南 引言 随着互联网技术的飞速发展,企业对实时数据处理和高并发场景的需求日益增长。Redis作为一款高性能的内存数据库,在各个领域中得到了广泛应用,包括缓存、消息队列、实时数据分析等。然而&am…...

Java8适配的markdown转换html工具(FlexMark)
坐标地址: <dependency><groupId>com.vladsch.flexmark</groupId><artifactId>flexmark-all</artifactId><version>0.60.0</version> </dependency> 工具类代码: import com.vladsch.flexmark.ext.tab…...

超全Deepseek资料包,deepseek下载安装部署提示词及本地部署指南介绍
该资料包涵盖了DeepSeek模型的下载、安装、部署以及本地运行的详细指南,适合希望在本地环境中高效运行DeepSeek模型的用户。资料包不仅包括基础的安装步骤,还提供了68G多套独立部署视频教程教程,针对不同硬件配置的模型选择建议,以…...

Postman - Postman 导入 JSON 文件(导入集合或环境变量)
一、Postman 中的 JSON 文件 在 Postman 中导入的 JSON 文件通常是指集合(Collection)或环境变量(Environments) 集合是 Postman 中用于管理 API 请求的一种方式,可以通过导入 JSON 文件来加载一个集合 环境变量是 P…...

傅里叶分析之掐死教程
https://zhuanlan.zhihu.com/p/19763358 要让读者在不看任何数学公式的情况下理解傅里叶分析。 傅里叶分析 不仅仅是一个数学工具,更是一种可以彻底颠覆一个人以前世界观的思维模式。但不幸的是,傅里叶分析的公式看起来太复杂了,所以很多…...

实在智能与宇树科技、云深科技一同获评浙江省“人工智能服务商”、 “数智优品”等荣誉
近日,浙江省经信厅正式公布《2024 年浙江省人工智能应用场景、应用标杆企业、人工智能服务商及 “数智优品” 名单》。 实在智能获评浙江省“人工智能服务商”,核心产品 “实在 Agent 智能体” 入选 “数智优品”。一同获此殊荣的还有宇树科技、云深处科…...

SpringAI系列 - RAG篇(三) - ETL
目录 一、引言二、组件说明三、集成示例一、引言 接下来我们介绍ETL框架,该框架对应我们之前提到的阶段1:ETL,主要负责知识的提取和管理。ETL 框架是检索增强生成(RAG)数据处理的核心,其将原始数据源转换为结构化向量并进行存储,确保数据以最佳格式供 AI 模型检索。 …...

手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...
FFmpeg 低延迟同屏方案
引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...

【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例
文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…...
Java入门学习详细版(一)
大家好,Java 学习是一个系统学习的过程,核心原则就是“理论 实践 坚持”,并且需循序渐进,不可过于着急,本篇文章推出的这份详细入门学习资料将带大家从零基础开始,逐步掌握 Java 的核心概念和编程技能。 …...
LRU 缓存机制详解与实现(Java版) + 力扣解决
📌 LRU 缓存机制详解与实现(Java版) 一、📖 问题背景 在日常开发中,我们经常会使用 缓存(Cache) 来提升性能。但由于内存有限,缓存不可能无限增长,于是需要策略决定&am…...
第7篇:中间件全链路监控与 SQL 性能分析实践
7.1 章节导读 在构建数据库中间件的过程中,可观测性 和 性能分析 是保障系统稳定性与可维护性的核心能力。 特别是在复杂分布式场景中,必须做到: 🔍 追踪每一条 SQL 的生命周期(从入口到数据库执行)&#…...
tomcat入门
1 tomcat 是什么 apache开发的web服务器可以为java web程序提供运行环境tomcat是一款高效,稳定,易于使用的web服务器tomcathttp服务器Servlet服务器 2 tomcat 目录介绍 -bin #存放tomcat的脚本 -conf #存放tomcat的配置文件 ---catalina.policy #to…...

热烈祝贺埃文科技正式加入可信数据空间发展联盟
2025年4月29日,在福州举办的第八届数字中国建设峰会“可信数据空间分论坛”上,可信数据空间发展联盟正式宣告成立。国家数据局党组书记、局长刘烈宏出席并致辞,强调该联盟是推进全国一体化数据市场建设的关键抓手。 郑州埃文科技有限公司&am…...

针对药品仓库的效期管理问题,如何利用WMS系统“破局”
案例: 某医药分销企业,主要经营各类药品的批发与零售。由于药品的特殊性,效期管理至关重要,但该企业一直面临效期问题的困扰。在未使用WMS系统之前,其药品入库、存储、出库等环节的效期管理主要依赖人工记录与检查。库…...