我服務器放在景區鎮上租的住處裏,附近經常因爲施工或是亂開挖之類的導致斷電。我用的電信寬帶。每次斷電之後服務器自動重啓就得重新分配一個ip。因爲用了域名服務,重啓後能根據域名查詢到新的IP。
但是,我還是嚐到了幾次找不到服務器IP的苦頭。有次在實驗室通宵,準備連接上屋裏的服務器做測試,因爲以前覺得域名很方便就沒有做記錄IP的工作。結果就發現連不上了,打電話問住同一小區的朋友是不是停電了,結果根本就沒有停電。登陸到我所使用的域名服務器的官網,才發現域名服務器癱瘓了,要維修兩天。我特地買了大堆吃的到實驗室準備利用兩個不同的網絡環境通宵做測試的,結果因爲不知道服務器的IP,什麼事情都做不了。自己有嘗試用一些以前用過的IP去暴力連接,但都失敗了。深更半夜的,想回去也沒辦法。於是開擼...
這種情況我遇上了很多次,但每次都沒有辦法。因爲用的免費的域名服務,不可能給我多完善的服務。於是自己想辦法解決這個問題。目前想了以下這幾種辦法:
1、使用2種以上不同的域名服務器。(該方法可行,服務器主機支持,路由器只支持某一個域名服務器)
2、使用一些手段通知自己更新後的IP。(正好我之前寫過郵件服務器,可以通過這種方法,當我IP改變時或定時向我的一些郵箱發送服務器的當前IP,這方法不錯,但是有些擔心郵件發多了又要被那些小氣的郵件服務器當成垃圾郵件把我拉黑。當我域名出現問題時,也通不過一些服務器的域名驗證)
3、服務器上登個QQ。(哈哈,居然有這麼NB的辦法,而且真的很有用呢,IP是準的,QQ的服務器基本不會停機維護。但還是不完美,有時候登陸時間長了或是自動登陸失敗後需要手動輸入驗證碼,這就沒轍了,因爲PPPOE登陸實在路由器上,有時候服務器開機了路由器還沒有登陸,就會出現QQ登陸不上的問題,這種情況設置下登陸延時應該就沒問題了)
4、詢問服務器所屬網段類的所有IP地址。(因爲無論怎麼重新分配IP,他的大的網段都不會變的。我查了查,我那的地址始終是118.112-113.0.0的網段。)
對比之下,還是4這種自力更生的方法適合我,我直接做一個C/S結構的搜索程序就行了。2*256*256=131072。最多才十多萬個可能的IP,分分鐘就能找到我的服務器。設計的原理是:服務器這邊始終監聽着一個專用端口,用來接收客戶端發過來的詢問。如果詢問信息和服務器最初設置的口令一致,服務器便向該地址回覆同樣的信息。客戶端收到服務器相同的回覆後,便能認定該地址就是要找的服務器IP地址。
因爲改程序不需要佔用太多資源,且需要跟着系統一起運行。我就直接在控制檯實現,因爲客戶端需要像大量不同的IP發送信息,因此我使用非阻塞的UDP來進行通信。
以下是服務端的實現代碼:
#include<windows.h> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<winsock.h> struct findinfo//發送的口令結構 { char name[32]; }; findinfo minfo; SOCKET ssock; sockaddr_in saddr; unsigned short sport; HANDLE sthreadhandle; bool isendthread; UINT opthread(LPVOID Param); //#pragma comment(lib,"ws2_32.lib") using namespace std; bool init()//初始化口令信息與監聽端口 { FILE *f; if(!(f=fopen("findinfo","r")))//初始化信息儲存在文件findinfo中 { puts("not find init file"); return false; } fscanf(f,"%s%d",minfo.name,&sport); if(strlen(minfo.name)<1) { puts("init file error"); return false; } printf("token:%s\nsport:%d\n",minfo.name,sport); fclose(f); return true; } bool setreg()//設置開機啓動 { char procbuf[256]; GetModuleFileName(GetModuleHandle(NULL),procbuf,sizeof(procbuf)); printf("this proc at:%s\n",procbuf); HKEY key; int ret; ret=RegOpenKeyEx( HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", 0, KEY_SET_VALUE, &key ); if(ret!=0) { printf("regopenkey fail:%d\n",ret); return false; } ret=RegSetValueEx( key, "findipsever", 0, REG_SZ, (const unsigned char*)procbuf, sizeof(procbuf) ); if(ret!=0) { printf("set reginfo fail:%d\n",ret); ret=RegCloseKey(key); return false; } ret=RegCloseKey(key); return true; } bool clearreg()//清除開機啓動 { char procbuf[256]; GetModuleFileName(GetModuleHandle(NULL),procbuf,sizeof(procbuf)); printf("this proc at:%s\n",procbuf); HKEY key; int ret; ret=RegOpenKeyEx( HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", 0, KEY_SET_VALUE, &key ); if(ret!=0) { printf("regopenkey fail:%d\n",ret); return false; } RegDeleteValue( key, "findipsever" ); if(ret!=0) { printf("clear reginfo fail:%d\n",ret); ret=RegCloseKey(key); return false; } ret=RegCloseKey(key); return true; } int main(int argv,char* argc[]) { if(argv>1)//配置開機啓動 { if(argc[1][0]=='-'&&argc[1][1]=='s') { if(setreg()) { puts("set reg secc"); } else { puts("set reg fail"); } return 0; } if(argc[1][0]=='-'&&argc[1][1]=='c') { if(clearreg()) { puts("clear reg secc"); } else { puts("clear reg fail"); } return 0; } } if(argv>2)//配置口令和端口信息 { FILE *f; if(!(f=fopen("findinfo","w+"))) { puts("can't open init file"); return -1; } fprintf(f,"%s\n%s",argc[1],argc[2]); fclose(f); printf("edit:%s %s secc\n",argc[1],argc[2]); return 0; } if(!init())//初始化口令和端口信息 { puts("init error"); return -1; } WSADATA wsaData; if(WSAStartup(MAKEWORD(1,0),&wsaData)) { printf("start socket error\n"); return -1; } ssock=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);//套接字使用UDP if(ssock==INVALID_SOCKET) { printf("create sock error\n"); WSACleanup(); return -1; } saddr.sin_family = AF_INET; saddr.sin_port= htons(sport);//設置爲所配置的端口 saddr.sin_addr.S_un.S_addr = INADDR_ANY; if(bind(ssock,(sockaddr *)&saddr,sizeof(sockaddr))==SOCKET_ERROR) { printf("bind error\n"); WSACleanup(); return -1; } sockaddr_in newaddr; int addrsize=sizeof(sockaddr); char *buf=new char[256]; int buflen; findinfo *reinfo=new findinfo; //設置爲非阻塞模式 int iMode=1; ioctlsocket(ssock,FIONBIO,(u_long FAR*)&iMode); isendthread=false; puts("--------sever start---------"); //開啓控制線程 sthreadhandle=CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)opthread, NULL, 0, NULL ); while(!isendthread)//循環監聽 { buflen=recvfrom( ssock, buf, 200, 0, (sockaddr *)&newaddr, &addrsize ); if(buflen>0) { buf[buflen]='\0'; //puts(buf); reinfo=(findinfo *)buf; if(strcmp(minfo.name,reinfo->name)==0)//口令匹配成功後進行回覆 { printf("%s send an request\n", inet_ntoa(newaddr.sin_addr)); sendto( ssock, (char *)&minfo, sizeof(minfo), 0, (sockaddr *)&newaddr, sizeof(sockaddr) ); } } Sleep(100);//休眠節約資源 } puts("---------sever end----------"); Sleep(500); puts("---------main exit----------"); return 0; } UINT opthread(LPVOID Param)//控制線程 { puts("-----enter hide window------"); //getchar(); Sleep(20000); HWND dos=GetForegroundWindow(); ShowWindow(dos,SW_HIDE); puts("------ernter end sever------"); getchar(); isendthread=true; return 0; }
對我那臺服務器而言,客戶端最多只需要發送10萬條詢問即可完成查詢,就當客戶端的上行帶寬是1M,一條詢問最多0.1k,不計網卡的的處理速度的話,最多2分鐘就能完成詢問。考慮到網卡的處理速度以及上行帶寬。客戶端進行查詢就只使用一條線程進行發送詢問,一條接收回復。代碼如下:
#include<windows.h> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<winsock.h> using namespace std; struct findinfo//發送的口令結構 { char name[32]; }; char resultip[512][20];//儲存獲取到的服務器恢復結果(考慮廣播地址發送至目的地的回覆) int resultnum; findinfo minfo; SOCKET sock; sockaddr_in saddr; HANDLE sthreadhandle; bool isendthread; UINT opthread(LPVOID Param); bool isipend(unsigned short *s1,unsigned short *s2)//判斷查詢的IP是否越界 { for(int i=0;i<4;i++) { if(s1[i]<s2[i]) { return false; } } return true; } int main(int argv,char* argc[]) { if(argv<2) { puts("format error\nthe right format: token port? startip? endip?"); exit(-1); } strcpy(minfo.name,argc[1]); unsigned port=998; unsigned short sip[5]; unsigned short eip[5]; //查詢至少帶有口令參數,缺省:端口 開始ip 結束ip //默認:998端口 0.0.0.0-255.255.255.255 if(argv>2) { port=atoi(argc[2]); } if(argv>3) { sscanf(argc[3],"%u.%u.%u.%u", sip,sip+1,sip+2,sip+3); //printf("%s %u %u %u %u\n",argc[3],sip[0],sip[1],sip[2],sip[3]); } else { for(int i=0;i<4;i++) { sip[i]=0; } } if(argv>4) { sscanf(argc[4],"%u.%u.%u.%u", eip,eip+1,eip+2,eip+3); } else { for(int i=0;i<4;i++) { eip[i]=255; } } WSADATA wsaData; if(WSAStartup(MAKEWORD(1,0),&wsaData)) { printf("start socket error\n"); return -1; } sock=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);//使用UDP套接字 if(sock==INVALID_SOCKET) { printf("create sock error\n"); WSACleanup(); return -1; } //設置UDP非阻塞 int iMode=1; ioctlsocket(sock,FIONBIO,(u_long FAR*)&iMode); //開啓接收應答線程 sthreadhandle=CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)opthread, NULL, 0, NULL ); saddr.sin_family = AF_INET; saddr.sin_port= htons(port); char cryip[20]; int ret; /*saddr.sin_addr.S_un.S_addr = inet_addr("192.168.0.155"); sendto(sock,(char *)&minfo,sizeof(minfo),0,(sockaddr *)&saddr,sizeof(sockaddr));*/ puts("-----------start find-------------"); while(!isipend(sip,eip))//開始發送詢問信息 { sprintf(cryip,"%u.%u.%u.%u", sip[0],sip[1],sip[2],sip[3]); saddr.sin_addr.S_un.S_addr = inet_addr(cryip); ret=sendto(sock,(char *)&minfo,sizeof(minfo),0,(sockaddr *)&saddr,sizeof(sockaddr)); if(ret==SOCKET_ERROR) { Sleep(2000); continue; } //修改到下一個IP sip[3]++; for(int i=3;i>0;i--) { if(sip[i]>255) { sip[i]=0; sip[i-1]++; printf("now at: %s\n",cryip); } } } Sleep(2000); puts("--------send find info end---------"); puts("-------enter check result----------"); getchar(); isendthread=true; Sleep(500); closesocket(sock); puts("------------main exit--------------"); return 0; } UINT opthread(LPVOID Param) { char *buf=new char[256]; int buflen; findinfo *reinfo; sockaddr_in newaddr; int addrlen=sizeof(sockaddr); resultnum=0; isendthread=false; //printf("recvfrom start"); while(!isendthread)//接收服務器的回覆 { buflen=recvfrom( sock, buf, 200, 0, (sockaddr *)&newaddr, &addrlen ); if(buflen>0) { buf[buflen]='\0'; //puts(buf); reinfo=(findinfo *)buf; //如果接收到正確的回覆,就將回復的IP地址存入結果字符串數組 if(strcmp(minfo.name,reinfo->name)==0) { strcpy(resultip[resultnum++],inet_ntoa(newaddr.sin_addr)); printf("find an addr at: %s\n", resultip[resultnum-1]); } } //Sleep(100); } if(resultnum==0) { puts("not find any ip"); } else { for(int i=0;i<resultnum;i++) { printf("find an addr at: %s\n", resultip[i]); } } /*getchar(); isendthread=true;*/ puts("--------recvfrom thread end--------"); return 0; }
下面看下使用效果:
服務器首先要配置口令和端口,直接帶上口令和端口參數即可
服務器使用參數:-s 配置開機啓動 -c清除開機啓動
自己運行即可開始服務
客戶端查詢至少帶有口令參數,缺省:端口 開始ip 結束ip。默認:998端口 0.0.0.0-255.255.255.255
例如我要查詢口令上面的服務器,參數爲:wchrt 998 118.112.0.0 118.113.255.255
如圖:
程序開始逐個IP進行詢問,我這的運行速度是每秒查詢1000個左右
查詢完畢,發現了服務器所在的IP地址:118.112.50.14.查詢成功