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

反向代理模块。。
1 概念 1.1 反向代理概念 反向代理是指以代理服务器来接收客户端的请求,然后将请求转发给内部网络上的服务器,将从服务器上得到的结果返回给客户端,此时代理服务器对外表现为一个反向代理服务器。 对于客户端来说,反向代理就相当于…...

【Linux基础指令】第三期
近期更新的基础指令链接: 【Linux基础指令】第一期-CSDN博客 【Linux基础指令】第二期-CSDN博客 本期博客的主题依旧是 "基础指令" ;话不多说,正文开始。 一、Linux的指令 1.zip / unzip 功能:打包压缩 命令格式&…...

Jenkins安装部署(以及常见报错解决方案),jdk版本控制器sdkman
目录 零、环境介绍 一、Jenkins安装 1、插件安装以及更换插件源 2、修改jenkins时区 二、sdkman安装(可选) 1、sdkman常用方法 2、sdkman常用方法演示 2.1、查看可用的jdk 2.2、下载jdk并切换版本 三、jenkins报错解决 1、下载sdkman后systemc…...

利用JSON数据类型优化关系型数据库设计
利用JSON数据类型优化关系型数据库设计 前言 在关系型数据库中,传统的结构化存储方式要求预先定义好所有的列及其数据类型。 然而,随着业务的发展,这种设计可能会显得不够灵活,尤其是在需要扩展单个列的描述功能时。 JSON数据…...

Nxopen 直齿轮参数化设计
NXUG1953 Visualstudio 2019 参考论文: A Method for Determining the AGMA Tooth Form Factor from Equations for the Generated Tooth Root Fillet //FullGear// Mandatory UF Includes #include <uf.h> #include <uf_object_types.h>// Internal I…...

线程配置经验
工作时,时常会遇到,线程相关的问题与解法,本人会持续对开发过程中遇到的关于线程相关的问题及解决记录更新记录在此篇博客中。 目录 一、线程基本知识 1. 线程和进程 二、问题与解法 1. 避免乘法级别数量线程并行 1)使用线程池…...

火语言RPA--KimiAiFree服务
🚩【组件功能】:KimiAiFree服务支持联网搜索、支持智能体对话、支持长文档解读、支持图像OCR。 配置预览 配置说明 服务地址 支持T或# 自行搭建或第三方提供的KimiAiFree服务地址。 RefreshToken 支持T或# 与KimiAiFree服务交互时用到的token。从ki…...
P6120 [USACO17JAN] Hoof, Paper, Scissor S
难度:普及/提高−; 题意: 石头、剪刀、布游戏,先给出 n n n 轮已经知道的其中一人的对局情况,例如样例: 5 P - 布 P - 布 H - 石头 P - 布 S - 剪刀另外一人,只允许修改一次机会的情况下…...

Android Studio打包APK
1.导出APK安装包 如果是首次打包,Create new 单击蓝色对话框右边文件夹📂图标 ,选择密钥保存路径,然后在下方File name对话框中填写您想要名称,再点击OK回到密钥创建对话框。 在此对话框中填写密码(Passwo…...

08 比特币通用技术介绍
比特币分层 比特币区块结构 存储结构 区块是比特币存储交易的结构,一个区块总是指向其父节点。 一个区块包含三个字段:区块头、区块交易数量、交易列表。交易数量受到区块大小限制,输入、输出数量和脚本都会占用区块空间,矿工往…...

拟合损失函数
文章目录 拟合损失函数一、线性拟合1.1 介绍1.2 代码可视化1.2.1 生成示例数据1.2.2 损失函数1.2.3 绘制三维图像1.2.4 绘制等高线1.2.5 损失函数关于斜率的函数 二、 多变量拟合2.1 介绍2.2 代码可视化2.2.1 生成示例数据2.2.2 损失函数2.2.3 绘制等高线 三、 多项式拟合3.1 介…...

二进制安卓清单 binary AndroidManifest - XCTF apk 逆向-2
XCTF 的 apk 逆向-2 题目 wp,这是一道反编译对抗题。 题目背景 AndroidManifest.xml 在开发时是文本 xml,在编译时会被 aapt 编译打包成为 binary xml。具体的格式可以参考稀土掘金 MindMac 做的类图(2014),下面的博…...

在线免费快速无痕去除照片海报中的文字logo
上期和大家分享了用photoshop快速无痕去除照片海报中的文字logo的方法,有的同学觉得安装PS太麻烦,有那下载安装时间早都日落西山了,问有没有合适的在线方法可以快速去除;达芬奇上网也尝试了几个网站,今天分享一个对国人…...

引领未来科技潮流:Web3 前沿发展趋势
随着技术不断发展,我们正站在一个全新的互联网时代的门槛上,Web3的出现正在重新定义互联网的构架和运作方式。Web3,作为互联网的下一代发展趋势,其核心思想是去中心化、开放与用户主权。与现有的Web2.0相比,Web3更加注…...

【番外篇】鸿蒙扫雷天纪:运混沌灵智勘破雷劫天局
大家好啊,我是小象٩(๑ω๑)۶ 我的博客:Xiao Xiangζั͡ޓއއ 很高兴见到大家,希望能够和大家一起交流学习,共同进步。 这一节课我们不学习新的知识,我们来做一个扫雷小游戏 目录 扫雷小游戏概述一、扫雷游戏分析…...
08.OSPF 特殊区域及其他特性
OSPF 特殊区域及其他特性 一. 前言OSPF的四个特殊区域Stub末梢区域Totally Stub完全末梢区域NSSATotally NSSA完全的NSSA二.Stub 区域和 Totally Stub 区域(1)网络规模变大引发的问题(2)传输区域和末端区域(3)Stub 区域(4)Totally Stub 区域三.NSSA 区域和 Totally NSS…...
人工智能在医疗领域的应用有哪些?
人工智能在医疗领域的应用十分广泛,涵盖了诊断、治疗、药物研发等多个环节,以下是一些主要的应用: 医疗影像诊断 疾病识别:通过分析 X 光、CT、MRI 等影像,人工智能算法能够识别出肿瘤、结节、骨折等病变,…...
c#使用Confluent.Kafka实现生产者发送消息至kafka(远程连接kafka发送消息超时的解决 Local:Message timed out)
水一篇: 参考:c#使用Confluent.Kafka实现生产者发送消息至kafka(远程连接kafka发送消息超时的解决 Local:Message timed out) - 寒冰之光 - 博客园 该死的Kafka,远程连接Kafka超时以及解决办法 - 博客王大…...

【2025年数学建模美赛F题】(顶刊论文绘图)模型代码+论文
全球网络犯罪与网络安全政策的多维度分析及效能评估 摘要1 Introduction1.1 Problem Background1.2Restatement of the Problem1.3 Literature Review1.4 Our Work 2 Assumptions and Justifications数据完整性与可靠性假设:法律政策独立性假设:人口统计…...
DeepSeek 的背景介绍
在全球人工智能大模型蓬勃发展的浪潮中,DeepSeek 宛如一颗耀眼的新星,迅速崛起并吸引了众多关注的目光。它的出现不仅为人工智能领域注入了新的活力,也在一定程度上改变了行业的竞争格局。 一、创立背景与资金支持 DeepSeek,中文…...
系统设计 --- MongoDB亿级数据查询优化策略
系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log,共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题,不能使用ELK只能使用…...
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南 在数字化营销时代,邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天,我们将深入解析邮件打开率、网站可用性、页面参与时…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...
Mobile ALOHA全身模仿学习
一、题目 Mobile ALOHA:通过低成本全身远程操作学习双手移动操作 传统模仿学习(Imitation Learning)缺点:聚焦与桌面操作,缺乏通用任务所需的移动性和灵活性 本论文优点:(1)在ALOHA…...

算法笔记2
1.字符串拼接最好用StringBuilder,不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...

【7色560页】职场可视化逻辑图高级数据分析PPT模版
7种色调职场工作汇报PPT,橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版:职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...
【LeetCode】3309. 连接二进制表示可形成的最大数值(递归|回溯|位运算)
LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 题目描述解题思路Java代码 题目描述 题目链接:LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 给你一个长度为 3 的整数数组 nums。 现以某种顺序 连接…...

关于easyexcel动态下拉选问题处理
前些日子突然碰到一个问题,说是客户的导入文件模版想支持部分导入内容的下拉选,于是我就找了easyexcel官网寻找解决方案,并没有找到合适的方案,没办法只能自己动手并分享出来,针对Java生成Excel下拉菜单时因选项过多导…...
6️⃣Go 语言中的哈希、加密与序列化:通往区块链世界的钥匙
Go 语言中的哈希、加密与序列化:通往区块链世界的钥匙 一、前言:离区块链还有多远? 区块链听起来可能遥不可及,似乎是只有密码学专家和资深工程师才能涉足的领域。但事实上,构建一个区块链的核心并不复杂,尤其当你已经掌握了一门系统编程语言,比如 Go。 要真正理解区…...

链式法则中 复合函数的推导路径 多变量“信息传递路径”
非常好,我们将之前关于偏导数链式法则中不能“约掉”偏导符号的问题,统一使用 二重复合函数: z f ( u ( x , y ) , v ( x , y ) ) \boxed{z f(u(x,y),\ v(x,y))} zf(u(x,y), v(x,y)) 来全面说明。我们会展示其全微分形式(偏导…...