手把手教你写CS搜索程序

 

    首先,本人比较喜欢玩反恐,虽然很菜。玩反恐最郁闷的就是找不到人一起玩,杀机器人没意思,上浩方学校的网络又太卡,于是写了这个程序来搜索校园网内的玩家。下面只是自己一个周末研究的结果,边学习边改进,错误之处欢迎指正。

    先讲一下原理,跟所有的网络程序一样,反恐有自己的通信协议,可以参考下面这个链接:

Half-Life Protocol: http://www.int64.org/docs/gamestat-protocols/halflife.html

由上可见所有请求都是以四个连续的值为255(十六进制的FF)的字节开始的,后面的字符串根据要查询的内容确定,如果想查询细节则用details,查询规则就用rules, 查看玩家信息就用players. 服务器会根据你发送的请求反馈相应的信息。

建服务器的一端(比如IP地址为59.65.168.2)的CS程序会监听27015(默认)这个端口等待其他玩家的加入。这时候如果你向该服务器(59.65.168.2:27015)发送一个查询字符串内容如下"ÿÿÿÿdetails" (ÿ是十六进制的FF), 服务器会返回关于该服务器细节信息,比如正在玩的地图、人数、是否有密码等等。如果服务器没有返回,就说明服务器不在线。

    实际应用中一般要搜索一个IP段,看看在这个IP段上有没有人在玩反恐,方法就是向每个IP的27015端口发送请求,有回应的就说明在线,然后解析一下回应的信息以便显示给用户。默认情况下,反恐联网对战限制在一个子网内,只有IP的前两部分相同的电脑才可以联机对战,比如我的IP地址为59.65.168.129,就只能跟ip为59.65.*.*的电脑联机。一般要搜索的IP数目会相当巨大,比如我们学校59.65.*.*这个段,从59.65.160.*到59.65.190.*有7000多个ip,要在极短的时间扫描这么多主机,就需要一个好的设计。下面给出两个方案,第一个是我最初的方案,在我的机器上耗时半分钟,第二个是改进之后的,耗时不到一秒,通过比较就可以看出良好的设计对程序性能的重要性。

方案一的源码(结合下面的讲解阅读):

/*
 * For Yuki, without whom nothing would be much worth doing.
 * Copyright(c) 2007. BJTU_KAY All rights reserved. 
 * The Program wil only output the running CS 1.5 Servers,(NOT for CS 1.6,CS Source or any higher version)
 * The Program will modify the file named "favsvrs.dat" which is under the root directory of your CS installation
 * The Program rewrites this file everytime it runs and updates the server list in this file.
 * This will save you from the trouble of adding servers by yourself. 
 * And when you start the game and click "Internet Game", the running servers list will be right there,updated.
 * Any bug report or suggestion, please send email to: linuxyuking@gamil.com
 * Enjoy~!
 *
 * References:
 * Half-Life Protocol: 
http://www.int64.org/docs/gamestat-protocols/halflife.html
 * For the format the file "favsvrs.dat", find one under the root directory of your CS installation
 * A C# version which inspired my work can be found at: 
http://www.codeproject.com/csharp/gameserverinfo.asp
 
*/


#include
< winsock2.h >
#include
< string .h >
#include
< stdio.h >
#include
< process.h >   
#include
< time.h >

#pragma  comment(lib, "wsock32.lib")

#define  BufLength 256

// address sturcture
typedef  struct  tagServer {
    
char address[16];
    
int port;
}
Server, * PServer;

// server info structure
typedef  struct  tagGameServerInfo {
  
char* serveraddress;
  
char* hostname;
  
char* mapname;
  
int playernum;
  
int maxplayers;
  
int protocolver;
  
char servertype;
  
char serveros;
  
int passworded;
}
GameServerInfo;

// Global variables
int  threadNum;  // the number of running threads
int  liveServNum;  // the number of running CS server found
GameServerInfo ResultSet[ 30 ]; // store the searching results for the purpose of outputing in console

int  Query(Server *  server, char *  readbuf);
char *  ReadNextParam( char *  readbuf, int *  offset);
char *  FetchAddress( char *  readbuf, int *  offset);
void  ParseDetails( char *  readbuf, GameServerInfo *  gsinfo);
void  ThreadProc ( void *  pserv);
void  GenServer(PServer pserv, const   char *  prefix,  int  third,  int  fourth);
void  SearchLoop( char   * prefix,  int  first,  int  last, int  finished,  int  total);
void  DisplayResult();
void  DisplayChoice();
void  reverse( char *  s);
char *  int2str( int  n);
void  SetColor(unsigned  short  ForeColor);

/// Query a specific server
/// <param name="server">The address structure of the gameserver</param>
/// <param name="readbuf">store the responsed data if succeed</param>

int  Query(Server *  server, char *  readbuf)
{
    WSADATA                wsaData;
    SOCKET                fd;
    SOCKADDR_IN            ServerAddr;
    
const  char*        querystring="details"// query string
    int                    iResult;
    fd_set                readfds;   
    fd_set                exceptfds;   
    TIMEVAL                timeOut;   
 

    
//// Initialize Winsock
    iResult = WSAStartup(MAKEWORD(2,2), &wsaData); 
    
if (iResult != NO_ERROR) {
        printf(
"WSAStartup failed with error: %d ", iResult);
        
return 1;
    }


    
// Create a SOCKET for connecting to server
    fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    
if (fd == INVALID_SOCKET) {
        printf(
"socket failed with error: %ld ", WSAGetLastError());
        WSACleanup();
        
return 1;
    }


    
//Fill in the sockaddr_in structure
    ServerAddr.sin_family = AF_INET;
    ServerAddr.sin_port 
= htons((u_short)server->port);    
    ServerAddr.sin_addr.s_addr 
= inet_addr(server->address); 

    
// Connect to server.
    iResult = connect( fd, (SOCKADDR*&ServerAddr, sizeof(ServerAddr) );
    
if ( iResult == SOCKET_ERROR) {
        closesocket (fd);
        
//printf("Unable to connect to server: %ld ", WSAGetLastError());
        WSACleanup();
        
return 1;
    }


    
//// Send a query 
    iResult = send(fd, querystring, strlen(querystring), 0);
    
if (iResult == SOCKET_ERROR) {
        printf(
"send() failed with error: %d ", WSAGetLastError());
        closesocket(fd);
        WSACleanup();
        
return 1;
    }

    
//printf("Bytes Sent: %d ", iResult);

    
// shutdown the connection since no more data will be sent
    iResult = shutdown(fd, SD_SEND);
    
if (iResult == SOCKET_ERROR) {
        printf(
"shutdown failed with error: %d ", WSAGetLastError());
        closesocket(fd);
        WSACleanup();
        
return 1;
    }


    
// Receive response data

    timeOut.tv_sec 
=1// receive timeout
    timeOut.tv_usec = 0
    
    FD_ZERO(
&readfds);   
    FD_ZERO(
&exceptfds);   
    FD_SET(fd,
&exceptfds);   
    FD_SET(fd,
&readfds); 
    
    iResult 
= select(fd+1&readfds, NULL, &exceptfds, &timeOut);   
    
if(iResult==0)   
    
{   
        
//printf("request timeout ");
        closesocket(fd);
        WSACleanup();
        
return   1;   
    }
      
    
if(iResult==SOCKET_ERROR)   
    
{   
        printf(
"recv failed: %d ", WSAGetLastError());
        closesocket(fd);
        WSACleanup();
        
return   1;   
    }
   
    
if(FD_ISSET(fd,&exceptfds))   
    
{   
        printf(
"recv failed.");    
         closesocket(fd);
        WSACleanup();
        
return   1;   
    }


    iResult 
= recv(fd, readbuf,BufLength, 0);
    
if ( iResult > 0 )
    
{     
        
//printf("Bytes received: %d ", iResult);
        closesocket(fd);
        WSACleanup();    
        
return 0;
    }

    
else if ( iResult == 0 )
    
{
        printf(
"Connection closed ");
        closesocket(fd);
        WSACleanup();
        
return  1
    }

    
else
    
{
        
//printf("recv failed: %d ", WSAGetLastError());
        closesocket(fd);
        WSACleanup();
        
return 1;
    }
    
}


/// distill next string from responsed data
/// <param name="readbuf">the responsed data</param>
/// <param name="offset">start from where to read</param>

char *  ReadNextParam( char *  readbuf, int *  offset)
{
       
char temp[30];
       
char* val;
       
int pos=0;
       
for (; (*offset) < BufLength; (*offset)++)
       
{
          
if (readbuf[*offset] == 0)
          
{
             (
*offset)++;
             
break;
          }

          temp[pos
++]=readbuf[*offset];
       }

       temp[pos]
='/0';
       val
=(char*)malloc(strlen(temp)+1);
       strcpy(val,temp);
       
return val;
}


/// distill the first string which looks like "127.0.0.1:27015" from the responsed data 
/// and then cut down the last part which is the port ":27015"
/// so the return value would be the address "127.0.0.1"

char *  FetchAddress( char *  readbuf, int *  offset)
{
    
char *val,*tmp;
    
int addresslen;
    tmp
=ReadNextParam(readbuf,offset);
    addresslen
=strlen(tmp)-6;
    val
=(char*)malloc(addresslen+1);
    strncpy(val,tmp,addresslen);
    val[addresslen]
='/0';
    free(tmp);
    
return val;    
}


// parse the responsed data
/// <param name="readbuf">the responsed data</param>
/// <param name="gsinfo">store details about the CS server</param>

void  ParseDetails( char *  readbuf, GameServerInfo *  gsinfo)
{
     
int offset=5;
     gsinfo
->serveraddress=FetchAddress(readbuf,&offset);
     gsinfo
->hostname=ReadNextParam(readbuf,&offset);
     gsinfo
->mapname=ReadNextParam(readbuf,&offset);
     ReadNextParam(readbuf,
&offset);    
     ReadNextParam(readbuf,
&offset);
     
//translate the rest byte by byte
     gsinfo->playernum=(int)readbuf[offset++];
     gsinfo
->maxplayers=(int)readbuf[offset++];
     gsinfo
->protocolver=(int)readbuf[offset++];
     gsinfo
->servertype=(char)readbuf[offset++];
     gsinfo
->serveros=(char)readbuf[offset++];
     gsinfo
->passworded=(int)readbuf[offset++];
}


// reverse a string
void  reverse( char *  s)
{
    
int c,i,j;
    
for(i=0,j=strlen(s)-1;i<j;i++,j--)
    
{
        c
=s[i];
        s[i]
=s[j];
        s[j]
=c;
    }

}


// convert a int to a c-style string
char *  int2str( int  n)
{
    
char* val;
    
char temp[5];
    
int i=0;
    
do{
        temp[i
++]=n%10+'0';
    }
while((n/=10)>0);
    temp[i]
='/0';
    reverse(temp);
    val
=(char*)malloc(strlen(temp)+1);
    strcpy(val,temp);
    
return val;
}




/// generate a Server structure    using number
/// <param name="pserv">store the generated Server structure</param>    
/// <param name="prefix">the first and second part of the IP address which is fixed</param>    
/// <param name="third">the third part of the IP address</param>    
/// <param name="fourth">the fourth part of the IP address</param>    

void  GenServer(PServer pserv, const   char *  prefix,  int  third,  int  fourth)
{
    
char * thd=int2str(third);
    
char * foh=int2str(fourth);
    pserv
->address[0]='/0';
    strcat(pserv
->address,prefix);
    strcat(pserv
->address,thd);
    strcat(pserv
->address,".");
    strcat(pserv
->address,foh);
    free(thd);
    free(foh);
    pserv
->port=27015;
}


//  a single thread to process one Server
void  ThreadProc ( void *  pserv)
{
    
char readbuf[BufLength]={0};
    GameServerInfo gsi;
    PServer pserver
=(Server*)pserv; // convert 
    if(!Query(pserver,readbuf)) // query succeed
    {
        ParseDetails(readbuf,
&gsi);
        
if(gsi.protocolver!=47// only cs1.5 (protocol version for CS1.5 is 46, and CS 1.6 is 47 )
        {
            FILE 
*serverlist;
            ResultSet[liveServNum
++]=gsi; //add the result to the result set

            
//append a server details at the end of the "favsvrs.dat" file
            serverlist=fopen("favsvrs.dat","a");
            fprintf(serverlist,
"%s%s%s%s%s%s%s%d%s%d%s%d%s",
            
" server { "
            
""address" "",gsi.serveraddress,"" "
            
""port" "27015" "
            
""name" "",gsi.hostname,"" "
            
""map" "",gsi.mapname,"" "
            
""game" "CounterStrike" "
            
""dir" "cstrike" "
            
""url" "" "
            
""dl" "" "
            
""maxplayers" "",gsi.maxplayers,"" "
            
""currentplayers" "",gsi.playernum,"" "
            
""protocol" "46" "
            
""favorite" "0" "
            
""ipx" "0" "
            
""mod" "0" "
            
""version" "1" "
            
""size" "0" "
            
""svtype" "l" "
            
""svos" "w" "
            
""password" "",gsi.passworded,"" "
            
""secure" "0" "
            
""svonly" "0" "
            
""cldll" "1" "
            
""lan" "0" "
            
""svping" "0.016388" "
            
""noresponse" "0" "
            
""packetloss" "100.000000" "
            
""status" "13" "
            
""filtered" "0" "
            
""fullmax" "0" "
            
""hlversion" "" "
            
""proxy" "0" "
            
""proxytarget" "0" "
            
""proxyaddress" "" }");
            fclose(serverlist);
        }

    }

    free(pserver); 
//molloced before the thread start, free it now
    threadNum--//decrease the number of running threads
    _endthread();
    
}


// set the color of the output
void  SetColor(unsigned  short  ForeColor)
{
HANDLE hCon
=GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(hCon,ForeColor);
}


// output the searching result to the console
void  DisplayResult()
{
    
int i;
    printf(
"____________________________________________________________________________ ");
    
for(i=0;i<liveServNum;i++)
    
{
        printf(
"[%d]  ",i);
        SetColor(
4);
        
if(ResultSet[i].passworded) printf(" * ");
        
else printf("   ");
        SetColor(
3);
        
if(strlen(ResultSet[i].hostname)>15) printf("Counter-Strike ");
        
else printf("%-15s",ResultSet[i].hostname);
        SetColor(
5);
        printf(
"%-40s",ResultSet[i].mapname);
        SetColor(
6);
        printf(
"%d/%d ",ResultSet[i].playernum,ResultSet[i].maxplayers);
        SetColor(
7);
    }

    printf(
"____________________________________________________________________________ ");

}


void  DisplayChoice()
{
    
char input[50],command[50];
    
int choice;
    printf(
"请输入想要加入的服务器序号(输入Q或q结束程序):");    
    
do{    
        scanf(
"%s",input);
        
if(input[0]=='q'||input[0]=='Q'return;
        
if(input[0]>'9'||input[0]<'0'
        
{
            choice
=-1;
            printf(
"非法输入,请重新选择(输入Q或q结束程序):");
        }

        
else 
        
{
            sscanf(input,
"%d",&choice);
            
if(choice>=liveServNum)
            
{
                choice
=-1;
                printf(
"非法输入,请重新选择(输入Q或q结束程序):");
            }

        }

    }
while(choice<0);
    sprintf(command,
"%s%s%s","cstrike.exe -console -game cstrike +connect ",ResultSet[choice].serveraddress,":27015");
    system(command);    
}



/// <param name="prefix">the first and second part of the IP address which is fixed</param>    
/// <param name="first">the third part of the begin IP address</param>    
/// <param name="last">the third part of the end IP address</param>    
/// <param name="finished">the number of finished IP</param>    
/// <param name="total">total number of IP </param>    

void  SearchLoop( char   * prefix,  int  first,  int  last, int  finished,  int  total)
{
    
int i,j;
    
int percents;
    SetColor(
4);
    
for(i=first;i<last+1;i++)
    
{
        
for(j=1;j<255;j++)
        
{
            
            PServer pserv;
            pserv
=(PServer)malloc(sizeof(Server));//critical. can't use local variables here
                                                
//because it will be released before the thread using it finish
            GenServer(pserv,prefix,i,j);
            _beginthread(ThreadProc,
64*1024, (void *)pserv);//start a new thread with the stack size set to 64K
            threadNum++//increase the number of running threads 
            if(threadNum>2000) Sleep(3000); //it is said that the max number of threads in Windows is 2000
                               
//if the number of running threads excess 2000,
                                            
//wait 3 second hoping that some of the running threads will finished
        }

        
//show the percents completed
        if(i==first) 
        
{
            printf(
"   ");
            
if(finished>0) printf("");
        }

        
else
        
{
            percents
=(i-first+finished)*100/total;//last-1-first
            printf("");
            printf(
"%2d%%",percents);            
        }

    }

}



int  main()
{        
    
char input[50];
    
int choice=1;
    
int waittimes=0;
    FILE 
*ffav;
    clock_t start,finish;
    ffav
=fopen("favsvrs.dat","w");
    fprintf(ffav,
"{");
    fclose(ffav);
    SetColor(
5);
    printf(
"北京交通大学CS局域网搜索程式 (Author:Kay Email:linuxyuking@gmail.com) ");
    printf(
"For Yuki, without whom nothing would be much worth doing. ");
    SetColor(
7);
    printf(
"____________________________________________________________________________ ");    
    printf(    
        
"$ 本程序只适用于CS1.5,请将本程序拷贝到CS1.5根目录下运行。 "
        
"$ 程序会将搜索结果自动导入CS根目录下的'favsvrs.dat'文件, "
        
"  即'进入游戏'->'网上对战'内的服务器列表,免去了您手动添加服务器的麻烦。 "
        
"  您也可以直接通过本程序选择要加入的服务器快速开始游戏。 "
        
"$ 由于本程序搜索过程会占用大量CPU资源,程序运行期间请尽量不要运行其他程序。 "
        );
    printf(
"____________________________________________________________________________ ");
    SetColor(
2);
    printf(
        
" [0] 59.64.*.* "
        
"[1] 59.65.*.* "
        
"[2] 219.242.*.* ");
    SetColor(
7);
    printf(
"请选择要搜索的IP段(0,1,2):");
    
do{    
        gets(input);
        
if(input[0]>'2'||input[0]<'0'
        
{
            choice
=-1;
            printf(
"非法输入,请重新选择(0,1,2):");
        }

        
else choice=1;
    }
while(choice<0);
    
switch(input[0])
    
{
    
case '0':    printf("开始搜索,已完成: ");    
                start
=clock(); // begin to time    
                SearchLoop("59.64.",0,15,0,15);
                
break;
    
case '1':    printf("开始搜索,已完成: ");    
                start
=clock(); // begin to time    
                SearchLoop("59.65.",160,191,0,31);
                
break;
    
case '2':    printf("开始搜索,已完成: ");    
                start
=clock(); // begin to time    
                SearchLoop("219.242.",112,127,0,30);
                SearchLoop(
"219.242.",240,255,15,30);
    }

    SetColor(
7);
    
//wait until all threads finished.
    printf(" 等待所有线程结束,请稍候");
    
do
    
{    
        
if(waittimes>=3break//wait no more than 3 sec in case of the dead loop
        printf(".");
        Sleep(
1000);
        waittimes
++;
    }
while(threadNum>0);

    ffav
=fopen("favsvrs.dat","a");
    fprintf(ffav,
" }");
    fclose(ffav);

    finish
=clock();

    printf(
" 搜索已完成,耗时: ");
    SetColor(
4);
    printf(
"%f",((double)(finish-start))/CLOCKS_PER_SEC);
    SetColor(
7);
    printf(
" 秒.");
    printf(
" 共搜索到 ");
    SetColor(
4);
    printf(
"%d",liveServNum);
    SetColor(
7);
    printf(
" 个CS服务器.");        
    
if(liveServNum>0
        
{
            printf(
" 服务器列表已生成. ");
            DisplayResult();
            DisplayChoice();
        }

    
else system("pause");
    
return 0;
}


 

先来看查询一个主机的过程,即上面代码中的Query函数。所有通信都是通过UDP包进行的,先初始化一个UDP socket,然后用connect函数将这个socket连接到要查询的那个主机(UDP本身是无连接的,这里的connect函数只是将这个socekt绑定到那个主机,这样该socket就会忽略任何来自其他主机的数据,详见MSDN connect函数)。接着用send函数向主机发送查询字符串,如果主机在线,接收函数recv就会将返回的信息保存在readbuf里。如果主机不在线,接受函数recv会一直等待主机回应,造成堵塞,为了使Query函数尽快返回,这里用了select函数设置超时时间。

现在来考虑如何扫描一个IP段,显然,如果按顺序对每个主机调用Query()效率是很低的,因为如果主机不在线(大多数机器当然都没在玩反恐)recv函数要等1秒(上面设置的超时时间)才能返回,然后才能进行下一个。如果把超时时间设的太低,主机响应延迟一点,程序就会漏掉该主机。

解决方法之一就是让这些Query并发运行。所以,我们第一种方案就是使用多线程,为每个要扫描的IP分配一个线程,让这个线程对该IP执行Query,执行完毕后释放资源。这样我们就无需等待一个查询的结束,而是让多个查询并发执行。暂时解决了我们的问题,但这样做的缺点也是显而易见的。系统支持的线程数量是有限的。每个线程都要分配一定的资源,线程本身需要一定的私有地址空间,而且在Query函数内部每个线程都要初始化一个socket,如果分配太多线程,就会占用大量的系统资源,而且有的线程会因为申请不到系统资源而终止。这样的程序运行时,CPU会占用100%,而且会漏掉大量结果。我们可以尝试把并发线程的数目限制到一个比较合理的数目,这样不断有旧的线程结束释放资源,给新产生的线程,这样也不至于漏掉结果。但是这个数目到底是多少,很难确定。并且在宏观上并行的这些线程在单核CPU上还是顺序执行的,如果线程数越多,CPU context swith的代价就会越高,效率也就越低。不管怎样,仍不失为一种方案。

另做一些补充说明,全局变量threadNum是用来把线程控制在一定数目内. ParseDetails函数解析从Query返回的readbuf,然后将结果保存在一个全局的列表ResultSet中,并将结果写入反恐根目录下的favsvrs.dat文见。程序运行图如下:

方案二,让我们换一种思考的方式。这一次我们只用两个线程,一个负责发送,一个负责接受。我们发现在方案一中每个线程发送完后,都要等等接受完毕才能返回,这就是瓶颈所在。而且线程的创建,伴随着socket的不断创建和销毁,效率很低。 在本方案中,发送线程和接受线程共用一个全局的socket,socket初始化绑定到一个端口(这里使用了27013),然后发送线程使用该socket不断发送请求,不等待主机相应立即返回,而接受线程使用该socket监听27013端口,如果有数据进入则做相应处理,然后继续监听。不用担心这个socket会共享冲突,这个操作系统会负责处理。这个方案是相对比较高效的,两个线程同时进行,无需等待,而且该方案资源使用很少,只用一个共享的socket。下面是代码:

 

// csgoogle.h

#include
< winsock2.h >
#include
< string .h >
#include
< stdio.h >
#include
< process.h >   
#include
< time.h >

#pragma  comment(lib, "wsock32.lib")

#define  BufLength 256

// 地址
typedef  struct  tagServerAddr {
    
char address[16]; 
    
int  port;
}
ServerAddr, * PServerAddr;

// 服务器信息
typedef  struct  tagGameServerInfo {
  
char* serveraddress;
  
char* hostname;
  
char* mapname;
  
int playernum;
  
int maxplayers;
  
int protocolver;
  
char servertype;
  
char serveros;
  
int passworded;
}
GameServerInfo;

typedef 
struct {
    HANDLE    hEvent;
    
char    choice;
}
PARAMS,  * PPARAMS;

// Global variables
int  liveServNum;  // the number of running CS server found
GameServerInfo ResultSet[ 30 ]; // store the searching results for the purpose of outputing in console
SOCKET    SharedSocket;
int  isExit = 0 ;

void  SendThread( void   * pvoid);
void  RecvThread( void   * dummy);
void  ParseServer( char   * ReceiveBuf);
char *  ReadNextParam( char *  readbuf, int *  offset);
char *  FetchAddress( char *  readbuf, int *  offset);
void  ParseDetails( char *  readbuf, GameServerInfo *  gsinfo);
void  GenServer(PServerAddr pservaddr, const   char *  prefix,  int  third,  int  fourth);
void  SearchLoop( char   * prefix,  int  first,  int  last, int  finished,  int  total);
void  DisplayResult();
void  DisplayChoice();
void  reverse( char *  s);
char *  int2str( int  n);
void  SetColor(unsigned  short  ForeColor);

 

// csgoogle.c

#include 
" csgoogle.h "

int  main()
{
    WSADATA    wsaData;
    HANDLE    hEvent;
    PARAMS    
params;
    SOCKADDR_IN        LocalAddr;
    
char    input[50];
    
int        choice=1,i;
    FILE    
*ffav;
    clock_t start,finish;

    ffav
=fopen("favsvrs.dat","w");
    fprintf(ffav,
"{");
    fclose(ffav);
    SetColor(
5);
    printf(
"北京交通大学CS局域网搜索程式 (Author:Kay Email:linuxyuking@gmail.com) ");
    printf(
"For Yuki, without whom nothing would be much worth doing. ");
    SetColor(
7);
    printf(
"____________________________________________________________________________ ");    
    printf(    
        
"$ 本程序只适用于CS1.5,请将本程序拷贝到CS1.5根目录下运行。 "
        
"$ 程序会将搜索结果自动导入CS根目录下的'favsvrs.dat'文件, "
        
"  即'进入游戏'->'网上对战'内的服务器列表,免去了您手动添加服务器的麻烦。 "
        
"  您也可以直接通过本程序选择要加入的服务器快速开始游戏。 "
        );
    printf(
"____________________________________________________________________________ ");
    SetColor(
2);
    printf(
        
" [0] 59.64.*.* "
        
"[1] 59.65.*.* "
        
"[2] 219.242.*.* ");
    SetColor(
7);
    printf(
"请选择要搜索的IP段(0,1,2):");
    
do{    
        gets(input);
        
if(input[0]>'2'||input[0]<'0'
        
{
            choice
=-1;
            printf(
"非法输入,请重新选择(0,1,2):");
        }

        
else choice=1;
    }
while(choice<0);
    
    printf(
"开始搜索,已完成: ");
    start
=clock(); 
    WSAStartup(MAKEWORD(
2,2), &wsaData);
    SharedSocket 
= socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

    LocalAddr.sin_family 
= AF_INET;
    LocalAddr.sin_addr.s_addr 
=htonl(INADDR_ANY); 
    LocalAddr.sin_port 
= htons(27013);

    
if(bind( SharedSocket, (SOCKADDR*&LocalAddr, sizeof(LocalAddr))== SOCKET_ERROR) 
    
{
        printf(
"bind() failed. ");
        
return 1;
    }


    _beginthread(RecvThread, 
0, NULL );
    hEvent
=CreateEvent(NULL, TRUE, FALSE, NULL);
    
params.choice=input[0];
    
params.hEvent=hEvent;
    _beginthread(SendThread, 
0, (void*)&params);
    
    WaitForSingleObject(hEvent, INFINITE);
    finish
=clock();
    SetColor(
7);
    printf(
" Please wait 3 seconds to terminate");
    
for(i=0;i<3;i++)
    
{
        printf(
".");
        Sleep(
1000);
    }

    

    ffav
=fopen("favsvrs.dat","a");
    fprintf(ffav,
" }");
    fclose(ffav);

    printf(
" 搜索已完成,耗时: ");
    SetColor(
4);
    printf(
"%f",((double)(finish-start))/CLOCKS_PER_SEC);
    SetColor(
7);
    printf(
" 秒.");
    printf(
" 共搜索到 ");
    SetColor(
4);
    printf(
"%d",liveServNum);
    SetColor(
7);
    printf(
" 个CS服务器.");        
    
if(liveServNum>0
    
{
        printf(
" 服务器列表已生成. ");
        DisplayResult();
        DisplayChoice();
    }

    
else system("pause");
    
return 0;    
}


void  SendThread( void   * pvoid)
{
    PPARAMS pparams;
    pparams
=(PPARAMS)pvoid;
    
switch(pparams->choice)
    
{
    
case '0':    SearchLoop("59.64.",0,15,0,15);
                
break;
    
case '1':    SearchLoop("59.65.",160,191,0,31);
                
break;
    
case '2':    SearchLoop("219.242.",112,127,0,30);
                SearchLoop(
"219.242.",240,255,15,30);
    }

    SetEvent(pparams
->hEvent);
    
//isExit=1;
    _endthread();
}


void  SearchLoop( char   * prefix,  int  first,  int  last, int  finished,  int  total)
{
    
int                    i,j;
    
int                    percents;
    ServerAddr            servaddr;
    SOCKADDR_IN            ReceiverAddr;
    
const  char*        querystring="details"// query string
    
    SetColor(
4);

    
for(i=first;i<last+1;i++)
    
{
        
for(j=1;j<255;j++)
        
{                        
            GenServer(
&servaddr,prefix,i,j);
            ReceiverAddr.sin_family 
= AF_INET;
            ReceiverAddr.sin_port 
= htons((u_short)servaddr.port);    
            ReceiverAddr.sin_addr.s_addr 
= inet_addr(servaddr.address);

            sendto(SharedSocket, querystring, strlen(querystring), 
0, (SOCKADDR *)&ReceiverAddr, sizeof(ReceiverAddr));
            
        }

        
//show the percents completed
        if(i==first) 
        
{
            printf(
"   ");
            
if(finished>0) printf("");
        }

        
else
        
{
            percents
=(i-first+finished)*100/total;//last-1-first
            printf("");
            printf(
"%2d%%",percents);            
        }

    }

}

void  RecvThread( void   * dummy)
{
    SOCKADDR_IN          SenderAddr;
    
char                 ReceiveBuf[BufLength];
    
int                  SenderAddrSize = sizeof(SenderAddr);
    
int                     RecvLen;

    
while(1){
        RecvLen
=recvfrom(SharedSocket, ReceiveBuf, BufLength, 0,
            (SOCKADDR 
*)&SenderAddr, &SenderAddrSize);
        
if(RecvLen>0) ParseServer(ReceiveBuf);
    }

}


void  ParseServer( char   * ReceiveBuf)
{
    GameServerInfo gsi;
    ParseDetails(ReceiveBuf,
&gsi);
    
if(gsi.protocolver==46// only cs1.5 (protocol version for CS1.5 is 46, and CS 1.6 is 47 )
    {
        FILE 
*serverlist;
        ResultSet[liveServNum
++]=gsi; //add the result to the result set

        
//append a server details at the end of the "favsvrs.dat" file
        serverlist=fopen("favsvrs.dat","a");
        fprintf(serverlist,
"%s%s%s%s%s%s%s%d%s%d%s%d%s",
        
" server { "
        
""address" "",gsi.serveraddress,"" "
        
""port" "27015" "
        
""name" "",gsi.hostname,"" "
        
""map" "",gsi.mapname,"" "
        
""game" "CounterStrike" "
        
""dir" "cstrike" "
        
""url" "" "
        
""dl" "" "
        
""maxplayers" "",gsi.maxplayers,"" "
        
""currentplayers" "",gsi.playernum,"" "
        
""protocol" "46" "
        
""favorite" "0" "
        
""ipx" "0" "
        
""mod" "0" "
        
""version" "1" "
        
""size" "0" "
        
""svtype" "l" "
        
""svos" "w" "
        
""password" "",gsi.passworded,"" "
        
""secure" "0" "
        
""svonly" "0" "
        
""cldll" "1" "
        
""lan" "0" "
        
""svping" "0.016388" "
        
""noresponse" "0" "
        
""packetloss" "100.000000" "
        
""status" "13" "
        
""filtered" "0" "
        
""fullmax" "0" "
        
""hlversion" "" "
        
""proxy" "0" "
        
""proxytarget" "0" "
        
""proxyaddress" "" }");
        fclose(serverlist);
    }

}


/// distill next string from responsed data
/// <param name="readbuf">the responsed data</param>
/// <param name="offset">start from where to read</param>

char *  ReadNextParam( char *  readbuf, int *  offset)
{
       
char temp[30];
       
char* val;
       
int pos=0;
       
for (; (*offset) < BufLength; (*offset)++)
       
{
          
if (readbuf[*offset] == 0)
          
{
             (
*offset)++;
             
break;
          }

          temp[pos
++]=readbuf[*offset];
       }

       temp[pos]
='/0';
       val
=(char*)malloc(strlen(temp)+1);
       strcpy(val,temp);
       
return val;
}


/// distill the first string which looks like "127.0.0.1:27015" from the responsed data 
/// and then cut down the last part which is the port ":27015"
/// so the return value would be the address "127.0.0.1"

char *  FetchAddress( char *  readbuf, int *  offset)
{
    
char *val,*tmp;
    
int addresslen;
    tmp
=ReadNextParam(readbuf,offset);
    addresslen
=strlen(tmp)-6;
    val
=(char*)malloc(addresslen+1);
    strncpy(val,tmp,addresslen);
    val[addresslen]
='/0';
    free(tmp);
    
return val;    
}


// parse the responsed data
/// <param name="readbuf">the responsed data</param>
/// <param name="gsinfo">store details about the CS server</param>

void  ParseDetails( char *  readbuf, GameServerInfo *  gsinfo)
{
     
int offset=5;
     gsinfo
->serveraddress=FetchAddress(readbuf,&offset);
     gsinfo
->hostname=ReadNextParam(readbuf,&offset);
     gsinfo
->mapname=ReadNextParam(readbuf,&offset);
     ReadNextParam(readbuf,
&offset);    
     ReadNextParam(readbuf,
&offset);
     
//translate the rest byte by byte
     gsinfo->playernum=(int)readbuf[offset++];
     gsinfo
->maxplayers=(int)readbuf[offset++];
     gsinfo
->protocolver=(int)readbuf[offset++];
     gsinfo
->servertype=(char)readbuf[offset++];
     gsinfo
->serveros=(char)readbuf[offset++];
     gsinfo
->passworded=(int)readbuf[offset++];
}


/// generate a Server structure    using number
/// <param name="pserv">store the generated Server structure</param>    
/// <param name="prefix">the first and second part of the IP address which is fixed</param>    
/// <param name="third">the third part of the IP address</param>    
/// <param name="fourth">the fourth part of the IP address</param>    

void  GenServer(PServerAddr pservaddr, const   char *  prefix,  int  third,  int  fourth)
{
    
char * thd=int2str(third);
    
char * foh=int2str(fourth);
    pservaddr
->address[0]='/0';
    strcat(pservaddr
->address,prefix);
    strcat(pservaddr
->address,thd);
    strcat(pservaddr
->address,".");
    strcat(pservaddr
->address,foh);
    free(thd);
    free(foh);
    pservaddr
->port=27015;
}


// set the color of the output
void  SetColor(unsigned  short  ForeColor)
{
HANDLE hCon
=GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(hCon,ForeColor);
}


// output the searching result to the console
void  DisplayResult()
{
    
int i;
    printf(
"____________________________________________________________________________ ");
    
for(i=0;i<liveServNum;i++)
    
{
        printf(
"[%d]  ",i);
        SetColor(
4);
        
if(ResultSet[i].passworded) printf(" * ");
        
else printf("   ");
        SetColor(
3);
        
if(strlen(ResultSet[i].hostname)>15) printf("Counter-Strike ");
        
else printf("%-15s",ResultSet[i].hostname);
        SetColor(
5);
        printf(
"%-40s",ResultSet[i].mapname);
        SetColor(
6);
        printf(
"%d/%d ",ResultSet[i].playernum,ResultSet[i].maxplayers);
        SetColor(
7);
    }

    printf(
"____________________________________________________________________________ ");

}


void  DisplayChoice()
{
    
char input[50],command[50];
    
int choice;
    printf(
"请输入想要加入的服务器序号(输入Q或q结束程序):");    
    
do{    
        scanf(
"%s",input);
        
if(input[0]=='q'||input[0]=='Q'return;
        
if(input[0]>'9'||input[0]<'0'
        
{
            choice
=-1;
            printf(
"非法输入,请重新选择(输入Q或q结束程序):");
        }

        
else 
        
{
            sscanf(input,
"%d",&choice);
            
if(choice>=liveServNum)
            
{
                choice
=-1;
                printf(
"非法输入,请重新选择(输入Q或q结束程序):");
            }

        }

    }
while(choice<0);
    sprintf(command,
"%s%s%s","cstrike.exe -console -game cstrike +connect ",ResultSet[choice].serveraddress,":27015");
    system(command);    
}


// reverse a string
void  reverse( char *  s)
{
    
int c,i,j;
    
for(i=0,j=strlen(s)-1;i<j;i++,j--)
    
{
        c
=s[i];
        s[i]
=s[j];
        s[j]
=c;
    }

}


// convert a int to a c-style string
char *  int2str( int  n)
{
    
char* val;
    
char temp[5];
    
int i=0;
    
do{
        temp[i
++]=n%10+'0';
    }
while((n/=10)>0);
    temp[i]
='/0';
    reverse(temp);
    val
=(char*)malloc(strlen(temp)+1);
    strcpy(val,temp);
    
return val;
}

程序运行图:

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值