反向代理模块
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 反向代理概念 反向代理是指以代理服务器来接收客户端的请求,然后将请求转发给内部网络上的服务器,将从服务器上得到的结果返回给客户端,此时代理服务器对外表现为一个反向代理服务器。 对于客户端来说,反向代理就相当于…...
风尚云网前端学习:一个简易前端新手友好的HTML5页面布局与样式设计
风尚云网前端学习:一个简易前端新手友好的HTML5页面布局与样式设计 简介 在前端开发的世界里,HTML5和CSS3是构建现代网页的基石。本文将通过一个简单的HTML5页面模板,展示如何使用HTML5的结构化元素和CSS3的样式特性,来创建一个…...
spacy 安装 en_core_web_sm
目录 spacy win11 成功 linux No matching distribution found for numpy<3.0.0,>2.0.0 解决方法: linux安装失败: linux安装成功 从GitHub上下载 spacy win11 成功 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple spacy linux N…...
SpringBoot(9)-Dubbo+Zookeeper
目录 一、了解分布式系统 二、RPC 三、Dubbo 四、SpringBootDubboZookeeper 4.1 框架搭建 4.2 实现RPC 一、了解分布式系统 分布式系统:由一组通过网络进行通信,为了完成共同的任务而协调工作的计算机节点组成的系统 二、RPC RPC:远程…...
嵌入式的C/C++:深入理解 static、const 与 volatile 的用法与特点
目录 一、static 1、static 修饰局部变量 2、 static 修饰全局变量 3、static 修饰函数 4、static 修饰类成员 5、小结 二、const 1、const 修饰普通变量 2、const 修饰指针 3、const 修饰函数参数 4. const 修饰函数返回值 5. const 修饰类成员 6. const 与 #defi…...
信创改造 - TongRDS 替换 Redis
记得开放 6379 端口哦 1)首先在服务器上安装好 TongRDS 2)替换 redis 的 host,post,passwd 3)TongRDS 兼容 jedis # 例如:更改原先 redis 中对应的 host,post,passwd 改成 TongRDS…...
周志华深度森林deep forest(deep-forest)最新可安装教程,仅需在pycharm中完成,超简单安装教程
1、打开pycharm 没有pycharm的,在站内搜索安装教程即可。 2、点击“文件”“新建项目” 3、创建项目,Python版本中选择Python39。如果没有该版本,选择下面的Python 3.9下载并安装。 4、打开软件包,搜索“deep-forest”软件包&am…...
python VS c++
一、语法特点 Python: 语法简洁、优雅,代码可读性极强,采用缩进来表示代码块,摒弃了像 C 那样使用大括号的传统方式,使得代码看上去十分清晰简洁。例如: if 5 > 3:print("5大于3") elif 5 …...
提升软件测试报告的质量:Allure2中添加用例失败截图、日志、HTML块和视频的方法
Allure2的用途 Allure2是一个用于生成测试报告的框架,广泛应用于自动化测试和手动测试中。它支持多种测试框架,如JUnit、TestNG、MSTest等,通过生动的图表和详细的日志,使得非技术人员也能轻松地理解测试结果。许多团队选用Allur…...
基于IPMI的服务器硬件监控指标解读
在现代化数据中心中,服务器的稳定运行对于保障业务连续性至关重要。为了实时掌握服务器的健康状况,运维团队需要借助高效的监控工具。监控易作为一款功能强大的监控软件,支持使用IPMI(Intelligent Platform Management Interface&…...
VUE字符串转日期加天数
文章为本新手菜鸡的问题记录,如有错误和不足还行大佬指正 文章目录 问题描述解决方法 问题描述 得到一串字符串的日期,因为不是规范的日期格式,无法使用moment().add()方法,那么如何实现增加天数的操作? 解决方法 1…...
Android12 mtk设置插充电器自动开机
Android12 mtk平台通常关机后,插上充电器是进入关机充电流程,显示关机充电动画。 那么根据用户需求,如果需要设置关机之后,实现插上充电器后,自动开机。 正常流程:机器关机 --> 插上充电器 --> 显示…...
JSON路径工具类`JsonPathUtil`的实现与应用
JSON路径工具类JsonPathUtil的实现与应用 作者:zibo 日期:2024/11/25 口号:慢慢学,不要停。 文章目录 JSON路径工具类JsonPathUtil的实现与应用〇、完整代码一、引言二、功能概述三、代码实现详解1. 工具类基础结构2. 核心方法get…...
人名分类器(nlp)
# coding: utf-8 import osos.environ[KMP_DUPLICATE_LIB_OK] True# 导入torch工具 import jsonimport torch # 导入nn准备构建模型 import torch.nn as nn import torch.nn.functional as F import torch.optim as optim # 导入torch的数据源 数据迭代器工具包 from torch.ut…...
斐波那契数列 相关问题 详解
斐波那契数列相关问题详解 斐波那契数列及其相关问题是算法学习中的经典主题,变形与应用非常广泛,涵盖了递推关系、动态规划、组合数学、数论等多个领域。以下是斐波那契数列的相关问题及其解法的详解。 1. 经典斐波那契数列 定义 初始条件࿱…...
Pytorch微调深度学习模型
在公开数据训练了模型,有时候需要拿到自己的数据上微调。今天正好做了一下微调,在此记录一下微调的方法。用Pytorch还是比较容易实现的。 网上找了很多方法,以及Chatgpt也给了很多方法,但是不够简洁和容易理解。 大体步骤是&…...
springboot 使用笔记
1.springboot 快速启动项目 注意:该启动只是临时启动,不能关闭终端面板 cd /www/wwwroot java -jar admin.jar2.脚本启动 linux shell脚本启动springboot服务 3.java一键部署springboot 第5条 https://blog.csdn.net/qq_30272167/article/details/1…...
网络安全基础——网络安全法
填空题 1.根据**《中华人民共和国网络安全法》**第二十条(第二款),任何组织和个人试用网路应当遵守宪法法律,遵守公共秩序,遵守社会公德,不危害网络安全,不得利用网络从事危害国家安全、荣誉和利益,煽动颠…...
SCAU软件体系结构实验四 组合模式
目录 一、题目 二、源码 一、题目 个人(Person)与团队(Team)可以形成一个组织(Organization):组织有两种:个人组织和团队组织,多个个人可以组合成一个团队,不同的个人与团队可以组合成一个更大的团队。 使用控制台或者JavaFx界面…...
Amazon商品详情API接口:电商创新与用户体验的驱动力
在电子商务蓬勃发展的今天,作为全球最大的电商平台之一,亚马逊(Amazon)凭借其强大的技术实力和丰富的商品资源,为全球用户提供了优质的购物体验。其中,Amazon商品详情API接口在电商创新与用户体验提升方面扮…...
手机无法连接服务器1302什么意思?
你有没有遇到过手机无法连接服务器,屏幕上显示“1302”这样的错误代码?尤其是在急需使用手机进行工作或联系朋友时,突然出现的连接问题无疑会带来不少麻烦。那么,什么是1302错误,它又意味着什么呢? 1302错…...
Android adb shell dumpsys audio 信息查看分析详解
Android adb shell dumpsys audio 信息查看分析详解 一、前言 Android 如果要分析当前设备的声音通道相关日志, 仅仅看AudioService的日志是看不到啥日志的,但是看整个audio关键字的日志又太多太乱了, 所以可以看一下系统提供的一个调试指令…...
Python 网络爬虫操作指南
网络爬虫是自动化获取互联网上信息的一种工具。它广泛应用于数据采集、分析以及实现信息聚合等众多领域。本文将为你提供一个完整的Python网络爬虫操作指南,帮助你从零开始学习并实现简单的网络爬虫。我们将涵盖基本的爬虫概念、Python环境配置、常用库介绍。 上传…...
基于FPGA的2FSK调制-串口收发-带tb仿真文件-实际上板验证成功
基于FPGA的2FSK调制 前言一、2FSK储备知识二、代码分析1.模块分析2.波形分析 总结 前言 设计实现连续相位 2FSK 调制器,2FSK 的两个频率为:fI15KHz,f23KHz,波特率为 1500 bps,比特0映射为f 载波,比特1映射为 载波。 1)…...
JavaScript的基础数据类型
一、JavaScript中的数组 定义 数组是一种特殊的对象,用于存储多个值。在JavaScript中,数组可以包含不同的数据类型,如数字、字符串、对象、甚至其他数组。数组的创建有两种常见方式: 字面量表示法:let fruits [apple…...
第三讲 架构详解:“隐语”可信隐私计算开源框架
目录 隐语架构 隐语架构拆解 产品层 算法层 计算层 资源层 互联互通 跨域管控 本文主要是记录参加隐语开源社区推出的第四期隐私计算实训营学习到的相关内容。 隐语架构 隐语架构拆解 产品层 产品定位: 通过可视化产品,降低终端用户的体验和演…...
JDBC编程---Java
目录 一、数据库编程的前置 二、Java的数据库编程----JDBC 1.概念 2.JDBC编程的优点 三.导入MySQL驱动包 四、JDBC编程的实战 1.创造数据源,并设置数据库所在的位置,三条固定写法 2.建立和数据库服务器之间的连接,连接好了后ÿ…...
Python绘制太极八卦
文章目录 系列目录写在前面技术需求1. 图形绘制库的支持2. 图形绘制功能3. 参数化设计4. 绘制控制5. 数据处理6. 用户界面 完整代码代码分析1. rset() 函数2. offset() 函数3. taiji() 函数4. bagua() 函数5. 绘制过程6. 技术亮点 写在后面 系列目录 序号直达链接爱心系列1Pyth…...
Spring框架特性及包下载(Java EE 学习笔记04)
1 Spring 5的新特性 Spring 5是Spring当前最新的版本,与历史版本对比,Spring 5对Spring核心框架进行了修订和更新,增加了很多新特性,如支持响应式编程等。 更新JDK基线 因为Spring 5代码库运行于JDK 8之上,所以Spri…...
Linux关于vim的笔记
Linux关于vim的笔记:(vimtutor打开vim 教程) --------------------------------------------------------------------------------------------------------------------------------- 1. 光标在屏幕文本中的移动既可以用箭头键,也可以使用 hjkl 字母键…...
怎样用js做网站轮播图/济南seo整站优化招商电话
软件测试自学报班都可以,但是不要报那种只学到功能测试的班。 无论是自学还是培训首先要了解自己的是否适合学习?其次要知道软件测试应该学什么?有没有一个标准?最后要开始做计划、找资料,了解一些别人的学习经验&…...
电商网站制作方案/站长统计app进入网址新版小猪
Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: In aggregated query without GROUP BY, expression #3 of SELECT list contains nonaggregated column...
新乡做网站的/网站收录查询爱站
npm 操作步骤 npm: 关闭ssl: npm set strict-ssl false 设置npm库: npm config set registry https://10.50.127.28:8443/repository/npm-group/ 网页进入测试 https://10.50.127.28:8443/#browse/browse:npm-group:ansi-styles 废弃 https://10.22.29.12…...
高品质的网站开发公/百度风云榜各年度小说排行榜
养成好习惯,点个赞再走 有问题,欢迎私信、评论,我看到都会回复的 文章目录打开注册表编辑器备份部分注册表部分注册表的恢复导出整个注册表整个注册表的恢复未将所有数据都成功写入到注册表中。某些项是由系统或其他进程打开的,或…...
高品质网站建设公司/怎么自己做网页
2019独角兽企业重金招聘Python工程师标准>>> 在Java的集合框架中,经常需要通过构造方法传入一个比较器Comparator,或者创建比较器传入Collections的静态方法中作为方法参数,进行比较排序等,使用的是策略模式。 策略模式…...
网站建设工作讲话/百度风云榜官网
在使用Maven的过程中,经常会遇到几个核心的概念,准确的理解这些概念将会有莫大的帮助。 1. POM(Project Object Model)项目对象模型 POM 与 Java 代码实现了解耦,当需要升级版本时,只需要修改POM,而不需要更改Java代码…...