我服务器放在景区镇上租的住处里,附近经常因为施工或是乱开挖之类的导致断电。我用的电信宽带。每次断电之后服务器自动重启就得重新分配一个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清除开机启动

自己运行即可开始服务

wKioL1TE8kLDowFkAAFW6CfkdhM345.jpg


客户端查询至少带有口令参数,缺省:端口 开始ip 结束ip。默认:998端口 0.0.0.0-255.255.255.255

例如我要查询口令上面的服务器,参数为:wchrt 998 118.112.0.0 118.113.255.255

如图:

wKiom1TE9w3gwC5XAAKnW4LO6uo626.jpg

程序开始逐个IP进行询问,我这的运行速度是每秒查询1000个左右

wKioL1TE9-mzCnMTAAL0IpFdQI4396.jpg

查询完毕,发现了服务器所在的IP地址:118.112.50.14.查询成功

源码