昆明做網(wǎng)站最好的小說網(wǎng)站排名
?
1 概念
?
1.1 反向代理概念
-
反向代理是指以代理服務器來接收客戶端的請求,然后將請求轉(zhuǎn)發(fā)給內(nèi)部網(wǎng)絡(luò)上的服務器,將從服務器上得到的結(jié)果返回給客戶端,此時代理服務器對外表現(xiàn)為一個反向代理服務器。
- 對于客戶端來說,反向代理就相當于目標服務器,只需要將反向代理當作目標服務器一樣發(fā)送請求就可以了,并且客戶端不需要進行任何設(shè)置。
1.2 特點
-
反向代理是代理服務器,為服務器收發(fā)請求,使真實服務器對客戶端不可見。
原文鏈接: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,可以用反向代理。
完整的反向代理圖如下所示:
不適用代理的情況下,外網(wǎng)不能訪問企業(yè)內(nèi)網(wǎng)的服務器;如果希望訪問企業(yè)內(nèi)網(wǎng),必須要使用反向代理,如果要使用反向代理,企業(yè)必須要有一個公網(wǎng)的服務器,公網(wǎng)的IP。
- 正向代理只有一個模塊一個服務程序;
- 而反向代理有兩個模塊兩個服務程序:外網(wǎng)模塊和內(nèi)網(wǎng)模塊。
2 反向代理實現(xiàn)
2.1 反向代理基本思路
服務程序啟動的時候,內(nèi)網(wǎng)模塊向外網(wǎng)模塊發(fā)起一個TCP連接,建立一條傳輸通道(稱為命令通道);
之后外網(wǎng)模塊讀取路由參數(shù)配置文件,外網(wǎng)模塊需要監(jiān)聽5122,5123,5128三個端口;
用戶1連接了外網(wǎng)服務器的5122端口,外網(wǎng)模塊通過命令通道告訴內(nèi)網(wǎng)模塊幫我連192.168.168.1.4的22端口;
內(nèi)網(wǎng)模塊向外網(wǎng)模塊發(fā)起TCP連接,建立一條傳輸通道,同時內(nèi)網(wǎng)模塊連接192.168.1.4 22,整個鏈路就建立起來了。
外網(wǎng)模塊外網(wǎng)程序:
- 外網(wǎng)模塊沒有主動的向任何服務器發(fā)起連接。
- 外網(wǎng)程序沒有為命令通道的socket準備epoll事件(因為命令通道不需要監(jiān)聽,只有外網(wǎng)程序向內(nèi)網(wǎng)發(fā)送命令,內(nèi)網(wǎng)程序并不會向外網(wǎng)程序發(fā)送命令。)。
內(nèi)網(wǎng)模塊內(nèi)網(wǎng)程序:
- 內(nèi)網(wǎng)程序非阻塞socket命令通道建立之后,再設(shè)置為非阻塞的,加入epoll事件。
- 此時,內(nèi)網(wǎng)模塊與外網(wǎng)模塊都進入事件循環(huán),外網(wǎng)程序監(jiān)聽著路由配置文件的端口可以開始接受用戶客戶端的連接請求了;內(nèi)網(wǎng)程序epoll中只有命令通道的socket,內(nèi)網(wǎng)程序也做好了準備接收外網(wǎng)程序的命令。
2.2 框架流程圖
2.3 反向代理框架實現(xiàn)
rinetd.cpp外網(wǎng)模塊
/** 程序名:rinetd.cpp,反向網(wǎng)絡(luò)代理服務程序-外網(wǎng)端。* 作者:張咸武
*/
#include "_public.h"
using namespace idc;// 代理路由參數(shù)的結(jié)構(gòu)體。
struct st_route
{int srcport; // 源端口。char dstip[31]; // 目標主機的地址。int dstport; // 目標主機的端口。int listensock; // 監(jiān)聽源端口的socket。
}stroute;
vector<struct st_route> vroute; // 代理路由的容器。
bool loadroute(const char *inifile); // 把代理路由參數(shù)加載到vroute容器。int initserver(int port); // 初始化服務端的監(jiān)聽端口。int epollfd=0; // epoll的句柄。
int tfd=0; // 定時器的句柄。#define MAXSOCK 1024
int clientsocks[MAXSOCK]; // 存放每個socket連接對端的socket的值。
int clientatime[MAXSOCK]; // 存放每個socket連接最后一次收發(fā)報文的時間。
string clientbuffer[MAXSOCK]; // 存放每個socket發(fā)送內(nèi)容的buffer。int cmdlistensock=0; // 命令通道監(jiān)聽的socket。
int cmdconnsock=0; // 命令通道連接的socket。void EXIT(int sig); // 進程退出函數(shù)。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 代理路由參數(shù)配置文件。\n");printf("cmdport 與內(nèi)網(wǎng)代理程序的通訊端口。\n\n");return -1;}// 關(guān)閉全部的信號和輸入輸出。// 設(shè)置信號,在shell狀態(tài)下可用 "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"); // 設(shè)置進程的心跳超時間為30秒。// 把代理路由參數(shù)加載到vroute容器。if (loadroute(argv[2])==false) return -1;logfile.write("加載代理路由參數(shù)成功(%d)。\n",vroute.size());// 初始化命令通道的監(jiān)聽端口。if ( (cmdlistensock=initserver(atoi(argv[3]))) < 0 ){ logfile.write("initserver(%s) failed.\n",argv[3]); EXIT(-1);}// 等待內(nèi)網(wǎng)程序的連接請求,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("與內(nèi)部的命令通道已建立(cmdconnsock=%d)。\n",cmdconnsock);// 初始化服務端用于監(jiān)聽外網(wǎng)的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);}// 把監(jiān)聽socket設(shè)置成非阻塞。fcntl(vroute[ii].listensock,F_SETFL,fcntl(vroute[ii].listensock,F_GETFD,0)|O_NONBLOCK);}// 創(chuàng)建epoll句柄。epollfd=epoll_create(1);struct epoll_event ev; // 聲明事件的數(shù)據(jù)結(jié)構(gòu)。// 為監(jiān)聽外網(wǎng)的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); // 把監(jiān)聽外網(wǎng)的socket的讀事件加入epollfd中。}// 創(chuàng)建定時器。tfd=timerfd_create(CLOCK_MONOTONIC,TFD_NONBLOCK|TFD_CLOEXEC); // 創(chuàng)建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){// 等待監(jiān)視的socket有事件發(fā)生。int infds=epoll_wait(epollfd,evs,10,-1);// 返回失敗。if (infds < 0) { logfile.write("epoll() failed。"); EXIT(-1); }// 遍歷epoll返回的已發(fā)生事件的數(shù)組evs。for (int ii=0;ii<infds;ii++){// 如果定時器的時間已到,有三件事要做:1)更新進程的心跳;2)向命令通道發(fā)送心跳報文;3)清理空閑的客戶端socket。if (evs[ii].data.fd==tfd){// logfile.write("定時器時間已到。\n");timerfd_settime(tfd,0,&timeout,0); // 重新開始計時。// pactive.uptatime(); // 1)更新進程心跳;// 2)向命令通道發(fā)送心跳報文;char buffer[256];strcpy(buffer,"<activetest>");if (send(cmdconnsock,buffer,strlen(buffer),0)<=0){logfile.write("與內(nèi)網(wǎng)程序的命令通道已斷開。\n"); EXIT(-1);}// 3)清理空閑的客戶端socket。for (int jj=0;jj<MAXSOCK;jj++){// 如果客戶端socket空閑的時間超過80秒就關(guān)掉它。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]]);// 把數(shù)組中對端的socket置空,這一行代碼和下一行代碼的順序不能亂。clientsocks[clientsocks[jj]]=0;// 把數(shù)組中本端的socket置空,這一行代碼和上一行代碼的順序不能亂。clientsocks[jj]=0;}}continue;}// 如果發(fā)生事件的是監(jiān)聽的listensock,表示外網(wǎng)有新的客戶端連上來。int jj=0;for (jj=0;jj<vroute.size();jj++){if (evs[ii].data.fd==vroute[jj].listensock){// 從已連接隊列中獲取一個已準備好的外網(wǎng)客戶端的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("連接數(shù)已超過最大值%d。\n",MAXSOCK); close(srcsock); break;}// 通過命令通道向內(nèi)網(wǎng)程序發(fā)送命令,把路由參數(shù)傳給它。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èi)網(wǎng)的命令通道已斷開。\n"); EXIT(-1);}// 接受內(nèi)網(wǎng)程序的連接,這里的accept()是阻塞的。int dstsock=accept(cmdlistensock,(struct sockaddr*)&client,&len);if (dstsock<0) { close(srcsock); break; }if (dstsock>=MAXSOCK){logfile.write("連接數(shù)已超過最大值%d。\n",MAXSOCK); close(srcsock); close(dstsock); break;}// 把內(nèi)網(wǎng)和外網(wǎng)客戶端的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數(shù)組中兩端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循環(huán)中已被處理。if (jj<vroute.size()) continue;// 如果是客戶端連接的socke有事件,分三種情況:1)客戶端有報文發(fā)過來;2)客戶端連接已斷開;3)有數(shù)據(jù)要發(fā)給客戶端。// 如果從通道一端的socket讀取到了數(shù)據(jù),把數(shù)據(jù)存放在對端socket的緩沖區(qū)中。// if (evs[ii].events==EPOLLIN) // 不要這么寫,有讀事件是1,有寫事件是4,如果讀和寫都有,是5。if (evs[ii].events&EPOLLIN) // 判斷是否為讀事件。 {char buffer[5000]; // 存放從接收緩沖區(qū)中讀取的數(shù)據(jù)。int buflen=0; // 從接收緩沖區(qū)中讀取的數(shù)據(jù)的大小。// 從通道的一端讀取數(shù)據(jù)。if ( (buflen=recv(evs[ii].data.fd,buffer,sizeof(buffer),0)) <= 0 ){// 如果連接已斷開,需要關(guān)閉通道兩端的socket。logfile.write("client(%d,%d) disconnected。\n",evs[ii].data.fd,clientsocks[evs[ii].data.fd]);close(evs[ii].data.fd); // 關(guān)閉客戶端的連接。close(clientsocks[evs[ii].data.fd]); // 關(guān)閉客戶端對端的連接。clientsocks[clientsocks[evs[ii].data.fd]]=0; // 把數(shù)組中對端的socket置空,這一行代碼和下一行代碼的順序不能亂。clientsocks[evs[ii].data.fd]=0; // 把數(shù)組中本端的socket置空,這一行代碼和上一行代碼的順序不能亂。continue;}// 成功的讀取到了數(shù)據(jù),把接收到的報文內(nèi)容原封不動的發(fā)給通道的對端。// 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);// 把讀取到的數(shù)據(jù)追加到對端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是否有寫事件(發(fā)送緩沖區(qū)沒有滿)。if (evs[ii].events&EPOLLOUT){// 把socket緩沖區(qū)中的數(shù)據(jù)發(fā)送出去。int writen=send(evs[ii].data.fd,clientbuffer[evs[ii].data.fd].data(),clientbuffer[evs[ii].data.fd].length(),0);// 以下代碼模擬不能一次發(fā)完全部數(shù)據(jù)的場景。//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緩沖區(qū)中已成功發(fā)送的數(shù)據(jù)。clientbuffer[evs[ii].data.fd].erase(0,writen);// 如果socket緩沖區(qū)中沒有數(shù)據(jù)了,不再關(guān)心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;
}// 初始化服務端的監(jiān)聽端口。
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;
}// 把代理路由參數(shù)加載到vroute容器。
bool loadroute(const char *inifile)
{cifile ifile;if (ifile.open(inifile)==false){logfile.write("打開代理路由參數(shù)文件(%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); // 把兩個空格替換成一個空格,注意第四個參數(shù)。deletelrchr(strbuffer,' '); // 刪除兩邊的空格。// 拆分參數(shù)。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);// 關(guān)閉監(jiān)聽內(nèi)網(wǎng)程序的socket。close(cmdlistensock);// 關(guān)閉內(nèi)網(wǎng)程序與服務端的命令通道。close(cmdconnsock);// 關(guān)閉全部監(jiān)聽的socket。for (auto &aa:vroute)if (aa.listensock>0) close(aa.listensock);// 關(guān)閉全部客戶端的socket。for (auto aa:clientsocks)if (aa>0) close(aa);close(epollfd); // 關(guān)閉epoll。close(tfd); // 關(guān)閉定時器。exit(0);
}
rinetdin.cpp代碼:
/** 程序名:rinetdin.cpp,反向網(wǎng)絡(luò)代理服務程序-內(nèi)網(wǎng)端。* 作者:張咸武
*/
#include "_public.h"
using namespace idc;int cmdconnsock; // 內(nèi)網(wǎng)程序與外網(wǎng)程序的命令通道的socket。int epollfd=0; // epoll的句柄。
int tfd=0; // 定時器的句柄。#define MAXSOCK 1024
int clientsocks[MAXSOCK]; // 存放每個socket連接對端的socket的值。
int clientatime[MAXSOCK]; // 存放每個socket連接最后一次收發(fā)報文的時間。
string clientbuffer[MAXSOCK]; // 存放每個socket發(fā)送內(nèi)容的buffer。// 向目標ip和端口發(fā)起socket連接,bio取值:false-非阻塞io,true-阻塞io。
int conntodst(const char *ip,const int port,bool bio=false);void EXIT(int sig); // 進程退出函數(shù)。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 外網(wǎng)代理程序的ip地址。\n");printf("port 外網(wǎng)代理程序的通訊端口。\n\n\n");return -1;}// 關(guān)閉全部的信號和輸入輸出。// 設(shè)置信號,在shell狀態(tài)下可用 "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"); // 設(shè)置進程的心跳超時間為30秒。// 建立與外網(wǎng)程序的命令通道,采用阻塞的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);// 命令通道建立之后,再設(shè)置為非阻塞的。fcntl(cmdconnsock,F_SETFL,fcntl(cmdconnsock,F_GETFD,0)|O_NONBLOCK);// 創(chuàng)建epoll句柄。epollfd=epoll_create(1);struct epoll_event ev; // 聲明事件的數(shù)據(jù)結(jié)構(gòu)。// 為命令通道的socket準備可讀事件。ev.events=EPOLLIN;ev.data.fd=cmdconnsock;epoll_ctl(epollfd,EPOLL_CTL_ADD,cmdconnsock,&ev);// 創(chuàng)建定時器。tfd=timerfd_create(CLOCK_MONOTONIC,TFD_NONBLOCK|TFD_CLOEXEC); // 創(chuàng)建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"); // 設(shè)置進程的心跳超時間為30秒。struct epoll_event evs[10]; // 存放epoll返回的事件。while (true){// 等待監(jiān)視的socket有事件發(fā)生。int infds=epoll_wait(epollfd,evs,10,-1);// 返回失敗。if (infds < 0) { logfile.write("epoll() failed。\n"); EXIT(-1); }// 遍歷epoll返回的已發(fā)生事件的數(shù)組evs。for (int ii=0;ii<infds;ii++){// 如果定時器的時間已到,有兩件事要做:1)設(shè)置進程的心跳;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秒就關(guān)掉它。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; // 把數(shù)組中對端的socket置空,這一行代碼和下一行代碼的順序不能亂。clientsocks[jj]=0; // 把數(shù)組中本端的socket置空,這一行代碼和上一行代碼的順序不能亂。}}continue;}// 如果發(fā)生事件的是命令通道。if (evs[ii].data.fd==cmdconnsock){// 讀取命令通道socket報文內(nèi)容。char buffer[256];memset(buffer,0,sizeof(buffer));if (recv(cmdconnsock,buffer,sizeof(buffer),0)<=0){logfile.write("與外網(wǎng)的命令通道已斷開。\n"); EXIT(-1);}// 如果收到的是心跳報文。if (strcmp(buffer,"<activetest>")==0) continue;// 如果收到的是新建連接的命令。// 向外網(wǎng)服務端發(fā)起連接請求。int srcsock=conntodst(argv[2],atoi(argv[3]));if (srcsock<0) continue;if (srcsock>=MAXSOCK){logfile.write("連接數(shù)已超過最大值%d。\n",MAXSOCK); close(srcsock); continue;}// 從命令報文內(nèi)容中獲取目標服務器的地址和端口。char dstip[11];int dstport;getxmlbuffer(buffer,"dstip",dstip,30);getxmlbuffer(buffer,"dstport",dstport);// 向目標服務器的地址和端口發(fā)起socket連接。int dstsock=conntodst(dstip,dstport);if (dstsock<0) { close(srcsock); continue; }if (dstsock>=MAXSOCK){ logfile.write("連接數(shù)已超過最大值%d。\n",MAXSOCK); close(srcsock); close(dstsock); continue;} // 把內(nèi)網(wǎng)和外網(wǎng)的socket對接在一起。logfile.write("新建內(nèi)外網(wǎng)通道(%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數(shù)組中兩端soccket的值和活動時間。clientsocks[srcsock]=dstsock; clientsocks[dstsock]=srcsock;clientatime[srcsock]=time(0); clientatime[dstsock]=time(0);continue;}// 如果是客戶端連接的socke有事件,分三種情況:1)客戶端有報文發(fā)過來;2)客戶端連接已斷開;3)有數(shù)據(jù)要發(fā)給客戶端。// 如果從通道一端的socket讀取到了數(shù)據(jù),把數(shù)據(jù)存放在對端socket的緩沖區(qū)中。// if (evs[ii].events==EPOLLIN) // 不要這么寫,有讀事件是1,有寫事件是4,如果讀和寫都有,是5。if (evs[ii].events&EPOLLIN) // 判斷是否為讀事件。 {char buffer[5000]; // 存放從接收緩沖區(qū)中讀取的數(shù)據(jù)。int buflen=0; // 從接收緩沖區(qū)中讀取的數(shù)據(jù)的大小。// 從通道的一端讀取數(shù)據(jù)。if ( (buflen=recv(evs[ii].data.fd,buffer,sizeof(buffer),0)) <= 0 ){// 如果連接已斷開,需要關(guān)閉通道兩端的socket。logfile.write("client(%d,%d) disconnected。\n",evs[ii].data.fd,clientsocks[evs[ii].data.fd]);close(evs[ii].data.fd); // 關(guān)閉客戶端的連接。close(clientsocks[evs[ii].data.fd]); // 關(guān)閉客戶端對端的連接。clientsocks[clientsocks[evs[ii].data.fd]]=0; // 把數(shù)組中對端的socket置空,這一行代碼和下一行代碼的順序不能亂。clientsocks[evs[ii].data.fd]=0; // 把數(shù)組中本端的socket置空,這一行代碼和上一行代碼的順序不能亂。continue;}// 成功的讀取到了數(shù)據(jù),把接收到的報文內(nèi)容原封不動的發(fā)給通道的對端。// 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);// 把讀取到的數(shù)據(jù)追加到對端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是否有寫事件(發(fā)送緩沖區(qū)沒有滿)。if (evs[ii].events&EPOLLOUT){// 把socket緩沖區(qū)中的數(shù)據(jù)發(fā)送出去。int writen=send(evs[ii].data.fd,clientbuffer[evs[ii].data.fd].data(),clientbuffer[evs[ii].data.fd].length(),0);// 以下代碼模擬不能一次發(fā)完全部數(shù)據(jù)的場景。//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緩沖區(qū)中已成功發(fā)送的數(shù)據(jù)。clientbuffer[evs[ii].data.fd].erase(0,writen);// 如果socket緩沖區(qū)中沒有數(shù)據(jù)了,不再關(guān)心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;
}// 向目標地址和端口發(fā)起socket連接。
int conntodst(const char *ip,const int port,bool bio)
{// 第1步:創(chuàng)建客戶端的socket。int sockfd;if ( (sockfd = socket(AF_INET,SOCK_STREAM,0))==-1) return -1; // 第2步:向服務器發(fā)起連接請求。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設(shè)置為非阻塞。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);// 關(guān)閉內(nèi)網(wǎng)程序與外網(wǎng)程序的命令通道。close(cmdconnsock);// 關(guān)閉全部客戶端的socket。for (auto aa:clientsocks)if (aa>0) close(aa);close(epollfd); // 關(guān)閉epoll。close(tfd); // 關(guān)閉定時器。exit(0);
}