如何搜索局域网内打开某个端口主机?

cqsfd 2009-11-21 03:16:49
小弟想实现诸如CS,WAR3那样的局域网链接形式:
游戏启动后,会自动搜索局域网内的主机显示出来,用户可以选择加入一个主机
现在的思路这样的:游戏主机一直打开某个端口监听。想加入游戏的客户机在局域网内发一个广播包,询问是否有主机打开某个特定端口(如10000);将收到的应答机器IP显示出来,用户选择加入某个ip地址的主机时,与该主机10000端口长链接开始游戏。
于是我查找资料,想用ICMP协议来做,在局域网内发一个255.255.255.255目的地址的广播包。局域网内的主机收到这个包就会回一个应答包。但问题是不能指定特定的端口。
请问大家有什么更好的思路吗?提供源码最好!
...全文
1522 14 打赏 收藏 转发到动态 举报
写回复
用AI写文章
14 条回复
切换为时间正序
请发表友善的回复…
发表回复
cqsfd 2009-11-23
  • 打赏
  • 举报
回复
感谢楼上兄弟提供的代码!
扫描1-255的机器,我也尝试过,还是不能解决子网大小的问题,不是所有子网都是分成这样的吧...
也许我该像9楼说的,用UDP发个特定端口的广播包
再等1天结贴吧


田暗星 2009-11-22
  • 打赏
  • 举报
回复
主机端 应答
头文件
class LanServer
{
public:
//客户端最大15 最小1
LanServer(int _Port,int _Max_Client,int _TimeOut,int _HeartTimeOut);
~LanServer();
BOOL InitServer();
BOOL Listen(short blockbuf);
BOOL GetPlayer(int id);
static BOOL Try3_SendSelect(fd_set r,struct timeval tm);
static BOOL Try3_RecvSelect(fd_set r,struct timeval tm);
// BOOL ClientMSG_PRC(int id,char recv[],PLAYER_DATA player_data);
BOOL EndSock(int id);
static UINT ServerThread(LPVOID p);
static BOOL GetFreeSock(int &id,int &num);


private:
int Port;
int PlayerPos[16];
static int TimeOut;
static int HeartTimeOut;
static SOCKET Socket[17];
static sockaddr_in Addr;
static int AddrLen;
static int Max_Client;


};

实现文件
//主机端
#include "Server.h"

int LanServer::TimeOut;
int LanServer::HeartTimeOut;
int LanServer::Max_Client;
int LanServer::AddrLen=0;
SOCKET LanServer::Socket[17];
sockaddr_in LanServer::Addr;

char StrGet[300];
//构造函数
LanServer::LanServer(int _Port,int _Max_Client,int _TimeOut,int _HeartTimeOut)
{
Port = _Port;
Max_Client = _Max_Client;
TimeOut = _TimeOut;
HeartTimeOut = _HeartTimeOut;
}

//析构函数
LanServer::~LanServer()
{
LanServer::EndSock(-1);
}

//初始化服务器 绑定端口
BOOL LanServer::InitServer()
{
int i;

//所有套接字初始化
for (i=0;i<=Max_Client+1;i++)
Socket[i] = -1;

//设定地址
Addr.sin_addr.s_addr= htonl(INADDR_ANY);
Addr.sin_family = AF_INET;
Addr.sin_port = htons(Port);
AddrLen = sizeof(Addr);

//创建socket
Socket[0] =socket(AF_INET,SOCK_STREAM,0);

//创建socket失败
if( Socket[0] == INVALID_SOCKET )
return FALSE;

//强制关闭,不经历TIME_WAIT过程
BOOL bDontLinger = FALSE;
setsockopt(Socket[0],SOL_SOCKET,SO_DONTLINGER,(const char*)&bDontLinger,sizeof(BOOL));

//绑定成功
if ( bind(Socket[0],(sockaddr*)&Addr,sizeof(Addr)) ==0)
return TRUE;

//绑定失败
else
return FALSE;
}

//开始监听
BOOL LanServer::Listen(short blockbuf)
{
listen(Socket[0],blockbuf);
return TRUE;
}

//获取空闲套接字
BOOL LanServer::GetFreeSock(int &id,int &num)
{
int i;
id = 0;
num = 0;
for(i=Max_Client+1;i>=1;i--)
if (Socket[i]==-1)
{
id = i;
num ++ ;
}
return TRUE;
}



//终止连接
BOOL LanServer::EndSock(int id)
{
int i;

//非法操作
if(id<-1 || id>15)
return FALSE;

//若索引为0-15 则终止该索引对应的套接字
if(id>=0 && Socket[id]!=-1)
{
closesocket(Socket[id]);
Socket[id]=-1;
}

//若索引为 -1 则终止所有 套接字
else if(id==-1)
for(i=0;i<=Max_Client;i++)
{
closesocket(Socket[id]);
Socket[id]=-1;
}

return TRUE;
}

//尝试3次接收监听 成功返回1 失败返回0
BOOL LanServer::Try3_RecvSelect(fd_set r,struct timeval tm)
{
int num = 1;

//尝试第一1次
int ret = select(0, &r, NULL, NULL, &tm);

//若失败再尝试2次
while( ret<=0 && num<3 )
{
ret = select(0, &r, NULL, NULL, &tm);
num++;
}

//返回结果
if( ret>0 )
return TRUE;
else
return FALSE;
}

//尝试3次接收监听 成功返回1 失败返回0
BOOL LanServer::Try3_SendSelect(fd_set r,struct timeval tm)
{
int num = 1;

//尝试第一1次
int ret = select(0, NULL, &r, NULL, &tm);

//若失败再尝试2次
while( ret<=0 && num<3 )
{
ret = select(0, NULL, &r, NULL, &tm);
num++;
}

//返回结果
if( ret>0 )
return TRUE;
else
return FALSE;
}


//服务器工作线程
UINT LanServer::ServerThread(LPVOID p)
{
int id,num;

//获得客户端数量
LanServer::GetFreeSock(id,num);

while( num==1 )
{
//终止线程
Sleep(20);
LanServer::GetFreeSock(id,num);
}

//接受连接
Socket[id]=accept(Socket[0],(sockaddr*)&Addr,&AddrLen);

//强制关闭,不经历TIME_WAIT过程
BOOL bDontLinger = FALSE;
setsockopt(Socket[id],SOL_SOCKET,SO_DONTLINGER,(const char*)&bDontLinger,sizeof(BOOL));

//开启新线程
AfxBeginThread(LanServer::ServerThread,0);

CAMatchDlg *dlg= (CAMatchDlg *)AfxGetApp()->GetMainWnd();

if ( Socket[id]!=INVALID_SOCKET ) //连接正确
{
//设置非阻塞方式
unsigned long ul = 1;
ioctlsocket(Socket[id], FIONBIO, (unsigned long*)&ul);

fd_set r;
FD_ZERO(&r);
FD_SET(Socket[id], &r);

struct timeval tm;
tm.tv_sec = TimeOut/1000 ;
tm.tv_usec = (TimeOut%1000)*1000;

while(1)
{
//尝试3次接收监听
if( LanServer::Try3_RecvSelect(r,tm) == FALSE )
break;

//接收数据
if( recv(Socket[id],StrGet,230,0) <=0 )
break;

//处理接收数据
ClientMSG_Processing(id,msg_data.StrGet,client_data);

//处理发送数据
int Ret = ClientMSG_Put(id, msg_data.StrGet, am_skin, client_data, am_game, msg_data.StrPut);

//发送数据
if( (Ret==2 || Ret==3) && LanServer::Try3_SendSelect(r,tm) == TRUE )
{
if( send(Socket[id], msg_data.StrPut, 50,0)<=0 )
send(Socket[id], msg_data.StrPut, 50,0);
}
//关闭套接字
if( Ret==0 || Ret==2 )
break;
}
}


closesocket(Socket[id]);
Socket[id]=-1;
::AfxMessageBox("服务器断开",0,0);

//终止线程
AfxEndThread(0);
return 0;
}



//处理客户端信息 TRUE 保持 FALSE 断开
BOOL LanServer::ClientMSG_PRC(int id,char recv[],PLAYER_DATA player_data,char Msg[])
{
//非法数据不处理
if( !(recv[0]=='A' && recv[1]=='M') )
return TRUE;

//依据协议 为即将发送的数据 添加数据头
memset(Msg,0,sizeof(Msg));
Msg[0]='A';
Msg[1]='M';

//获取消息类型
int MSG_ID = (unsigned char)recv[2];

//依据类型处理
switch( MSG_ID )
{
//查询服务器信息
case CMSG_See:
{
Msg[2] = SMSG_Server;
DataCopy(player_data.GSP,Msg+3,32);
int i,num=0;
for(i=0;i<16;i++)
if( client_data.Flag[i] )
num++;
Msg[35] = num;
Msg[36] = '\0';

return TRUE;
}



//申请加入服务器
case CMSG_Ask:
{
//拷贝玩家名称
char sname[17];
DataCopy(recv+3,sname,16);

//增加玩家信息
AddPlayer(id, sname, player_data.Live, player_data.Flag, player_data.Name[id]);
return TRUE;
}

//准备完毕 等待开始
case CMSG_Ready:
{
//拷贝初始化棋盘数据
DataCopy(recv+3, player_data.Init[id], 216);
return TRUE;
}

//游戏进度
case CMSG_Game:
{
//数据包次序
int PackID = (unsigned char)recv[3];

//对比次序 判断是否丢失严重 100 210 321 432 543
if( PackID - (unsigned char)palyer_data.PID[id] >= 3 )
return FALSE;

//更新数据包次序
palyer_data.PID[id] = PackID;

//拷贝销毁点 与 交换点
DataCopy(recv+4, player_data.SP[id], 3);
DataCopy(recv+7, player_data.EP[id], 3);
return TRUE;
}

//离开游戏
case CMSG_Leave:
{
//删除玩家信息
DeletePlayer(id, player_data.Live, player_data.Flag, player_data.Name[id]);
return FALSE;
}

//保持信号
case CMSG_Keep:
{
//心跳包计数器 增加
player_data.Signal[id] ++;
return TRUE;
}

//默认不处理
default :
return TRUE;
}


}

你再稍加修改 就行啦
田暗星 2009-11-22
  • 打赏
  • 举报
回复
实现文件 .cpp
#include "LanHost.h"

DWORD LanActiveHost::TimeOutValue ;
short LanActiveHost::ThreadCount ;
short LanActiveHost::Port ;
char LanActiveHost::IP[256][16] ;
char LanActiveHost::Index[256] ;
char LanActiveHost::send_str[5] ;
char LanActiveHost::recv_str[256][40] ;
int LanActiveHost::send_len ;
int LanActiveHost::recv_len ;
char LanActiveHost::RecvMsg[256][34];

//构造函数
LanActiveHost::LanActiveHost(short _MaxThread,short _Port,DWORD _TimeOutValue)
{
MaxThread = _MaxThread;
Port = _Port;
TimeOutValue = _TimeOutValue;
//配置数据协议
LanActiveHost::CFG_DP();
}


//析构函数
LanActiveHost::~LanActiveHost()
{
}


//获取本机IP地址
BOOL LanActiveHost::GetHostIP()
{
char HostName[128];
LPSTR Addr;
struct hostent FAR *HostEnt;

//获取主机名称 -1错误 0成功
if( gethostname(HostName, sizeof(HostName)) == SOCKET_ERROR )
return FALSE;

//获取名称结构
HostEnt = gethostbyname (HostName);

//主机名称可能有多个IP地址 在此默认第一个地址
Addr = HostEnt->h_addr_list[0];

//IP -> XXX.XXX.XXX.XXX
if (!Addr)
return FALSE;
else
{
struct in_addr inAddr;
memmove (&inAddr, Addr, 4);
strcpy(HostIP,inet_ntoa (inAddr));
}
return TRUE;
}


// 得到IP前3段 XXX.XXX.XXX.XXX -> XXX.XXX.XXX. ---------------------
BOOL LanActiveHost::GetIP3()
{
int i=0,j=0;
while(1)
{
IP3[i]=HostIP[i];
if(HostIP[i]=='.')
j++;
if(j==3)
break;
i++;
}
IP3[i+1]='\0';
return TRUE;
}


//初始化局域网 IP列表
BOOL LanActiveHost::InitIPList()
{
int i;
char IP4[4];

//获取本机IP
if( !LanActiveHost::GetHostIP() )
return FALSE;
//获取IP前三段
LanActiveHost::GetIP3();

//初始化局域网IP
for(i=1;i<=255;i++)
{
strcpy(IP[i],IP3);
itoa(i,IP4,10);
strcat(IP[i],IP4);
IP[i][strlen(IP[i])]='\0';
}
return TRUE;
}

//配置网络数据协议
BOOL LanActiveHost::CFG_DP()
{
send_str[0] = 'A';
send_str[1] = 'M';
send_str[2] = CMSG_See;
send_str[3] = '\0';
send_len = 5;
recv_len = 40;
return TRUE;
}

//检查接收内容是否正确
BOOL LanActiveHost::Chk_ServMsg(int id)
{
if( recv_str[id][0]=='A' && recv_str[id][1]=='M' && (unsigned char)recv_str[id][2]==SMSG_Server )
return TRUE;
else
return FALSE;
}


//设置接收信息记录
BOOL LanActiveHost::SetMsgRec(BOOL flag,int id)
{
if(flag==TRUE)
{
//记录
Index[id]=1;
DataCopy(recv_str[id]+3,RecvMsg[id],33);
RecvMsg[id][33]='\0';
}
else
{
//清零
Index[id]=0;
memset(RecvMsg[id],0,sizeof(RecvMsg[id]));
}
return TRUE;
}



//连接监听尝试3次 成功返回1 失败返回0
BOOL LanActiveHost::Try3_ConnSelect(fd_set r,struct timeval tm)
{
int num = 1;

//尝试第一1次
int ret = select(0, NULL, &r, NULL, &tm);

//若失败再尝试2次
while( ret<=0 && num<3 )
{
ret = select(0, NULL, &r, NULL, &tm);
num++;
}

//返回结果
if( ret>0 )
return TRUE;
else
return FALSE;
}

//发送监听尝试3次 成功返回1 失败返回0
BOOL LanActiveHost::Try3_SendSelect(fd_set r,struct timeval tm)
{
int num = 1;

//尝试第一1次
int ret = select(0, NULL, &r, NULL, &tm);

//若失败再尝试2次
while( ret<=0 && num<3 )
{
ret = select(0, NULL, &r, NULL, &tm);
num++;
}

//返回结果
if( ret>0 )
return TRUE;
else
return FALSE;
}

//接收监听尝试3次 成功返回1 失败返回0
BOOL LanActiveHost::Try3_RecvSelect(fd_set r,struct timeval tm)
{
int num = 1;

//尝试第一1次
int ret = select(0, &r, NULL, NULL, &tm);

//若失败再尝试2次
while( ret<=0 && num<3 )
{
ret = select(0, &r, NULL, NULL, &tm);
num++;
}

//返回结果
if( ret>0 )
return TRUE;
else
return FALSE;
}

//对外接口 提供接收的服务器信息
BOOL LanActiveHost::GetServerMSG(char Msg[256][34])
{
int i;
//Msg清零
memset(Msg,0,sizeof(Msg));

//如果是服务器 提交信息
for(i=1;i<=255;i++)
if(Index[i]==1)
DataCopy(RecvMsg[i],Msg[i],33);
return TRUE;
}



//扫描局域网存活主机 子线程函数
DWORD WINAPI LanActiveHost::DoScanPort(LPVOID lpParam)
{
int id = (int)lpParam;

//创建套接字
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);

//创建套接字失败
if(sock == INVALID_SOCKET)
{
ThreadCount --;
return 0;
}

//创建套接字成功
else
{
//强制关闭,不经历TIME_WAIT过程
BOOL bDontLinger = FALSE;
setsockopt(sock,SOL_SOCKET,SO_DONTLINGER,(const char*)&bDontLinger,sizeof(BOOL));
//设置非阻塞socket
unsigned long flag = 1;
int ret = ioctlsocket(sock, FIONBIO, (unsigned long*)&flag);

//设置失败
if( ret == SOCKET_ERROR )
{
ThreadCount --;
return 0;
}

//主机地址
sockaddr_in Addr;
Addr.sin_family = AF_INET;
Addr.sin_port = htons(Port);
Addr.sin_addr.S_un.S_addr =inet_addr(IP[id]);

//连接主机
connect(sock, (sockaddr*)&Addr, sizeof(Addr));

//超时设置
struct fd_set mask;
FD_ZERO(&mask);
FD_SET(sock, &mask);
struct timeval timeout;
timeout.tv_sec = TimeOutValue/1000; //秒
timeout.tv_usec = (TimeOutValue%1000)*1000; //微秒

//连接成功
if( LanActiveHost::Try3_ConnSelect(mask,timeout) == TRUE )

//监听发送
if( LanActiveHost::Try3_SendSelect(mask,timeout) == TRUE )
send(sock,send_str,send_len,0);

strcpy(recv_str[id],"NULL\0");

//监听接收
if( LanActiveHost::Try3_RecvSelect(mask,timeout) == TRUE )
recv(sock,recv_str[id],recv_len,0);

//接收内容正确
if( LanActiveHost::Chk_ServMsg(id) == TRUE )
LanActiveHost::SetMsgRec(TRUE,id);
else
LanActiveHost::SetMsgRec(FALSE,id);
//断开连接
shutdown(sock,0);
closesocket(sock);
sock = -1;
}

ThreadCount --;
return 1;
}


//扫描局域网存活主机 父线程函数
DWORD WINAPI LanActiveHost::StartScan(LPVOID lpParam)
{
DWORD ThreadId;
unsigned short i;

for(i=1; i<=255; i++)
if (CreateThread(NULL, 0,LanActiveHost::DoScanPort, (LPVOID)i, 0, &ThreadId) != NULL)
ThreadCount ++;

//等待所有子线程退出
while (ThreadCount > 0)
Sleep(20);

return 1;
}


//扫描局域网存活主机
BOOL LanActiveHost::StartListServer()
{
//初始化局域网 IP列表与索引
if( !LanActiveHost::InitIPList() )
return FALSE;

//线程计数器初始化
ThreadCount = 0;

//创建扫描父线程,该线程包含多个子线程
DWORD ThreadId;
Handle = CreateThread(NULL, 0,LanActiveHost::StartScan, NULL, 0, &ThreadId);

//线程创建成功
if( Handle )
return TRUE;

//线程创建失败
else
return FALSE;
}


//等待扫描线程安全退出
BOOL LanActiveHost::EndListServer()
{
//等待子线程退出
while( ThreadCount>0 )
Sleep(20);
//等待父线程退出
Sleep(100);
return TRUE;
}


//外部接口提供服务器IP列表 存活索引
BOOL LanActiveHost::Get_IPIndex(char ip_list[256][16],char ip_index[256])
{
int i;
for(i=1;i<=255;i++)
if(Index[i]==1)
{
strcpy(ip_list[i],IP[i]);
ip_index[i] = Index[i];
}
else
ip_index[i] = 0;
return TRUE;
}




田暗星 2009-11-22
  • 打赏
  • 举报
回复
哈哈,抢分来了

毕业时的课程设计 代码里正好有 开255个线程 同时扫描 端口 经测试十分可靠, 3秒内返回所有 指定端口的存活主机。
头文件:
/*
扫描局域网活动主机端口类 LanActiveHost
*/
#include <winsock.h>
#pragma comment(lib, "ws2_32.lib")

class LanActiveHost
{
public:
//建议线程 不超过200 毫秒超时设置 最好大于1秒 小于1秒结果不可靠
LanActiveHost(short _MaxThread,short _Port,DWORD _TimeOutValue);
~LanActiveHost();
BOOL GetHostIP();
BOOL GetIP3();
BOOL InitIPList();
static BOOL Try3_ConnSelect(fd_set r,struct timeval tm);
static BOOL Try3_SendSelect(fd_set r,struct timeval tm);
static BOOL Try3_RecvSelect(fd_set r,struct timeval tm);

static BOOL CFG_DP();
static BOOL Chk_ServMsg(int id);
static BOOL SetMsgRec(BOOL flag,int id);

static DWORD WINAPI DoScanPort(LPVOID lpParam);
static DWORD WINAPI StartScan(LPVOID lpParam);
static BOOL GetServerMSG(char ServerMsg[256][34]);
BOOL StartListServer();
BOOL EndListServer();
BOOL Get_IPIndex(char ip[256][16],char index[256]);


private:
HANDLE Handle; //父线程句柄
static DWORD TimeOutValue ; //连接超时时间,以ms计
static short ThreadCount; //当前正在扫描的进程数
static short Port; //端口
static char IP[256][16]; //局域网IP列表
static char Index[256]; //局域网活动服务器 是1 否0

static char send_str[5]; //发送数据缓存
static char recv_str[256][40]; //接收数据缓存
static int send_len;
static int recv_len;

static char RecvMsg[256][34]; //收到每个主机返回的信息 32 GSP 1 Num

short MaxThread; //最大允许的扫描线程数,不宜大于200
char HostIP[16]; //本机IP
char IP3[16]; //IP前3段
};
zhuxueling 2009-11-22
  • 打赏
  • 举报
回复
是UDP广播。
(魔兽和CS应该是UDP广播)
cqsfd 2009-11-22
  • 打赏
  • 举报
回复
感谢楼上各位的回答
我现在想知道的是:还有什么其他格式的报文,可以广播,并且可以找到某个特定端口呢?ICMP执行效率非常高,但只能找到打开的主机,不能找到特定端口...
do_fork 2009-11-21
  • 打赏
  • 举报
回复
[Quote=引用 3 楼 cqsfd 的回复:]
问题是我并不知道这个局域网范围多大啊?
如果子网是192.168.0.1-192.168.0.255,我可以从1-255一个个试,但万一子网不是这么大呢?
而且一个个试,非常耗时间,基本不可能做到想CS,war3那样一下就刷出局域网内主机的效果
我觉得用ICMP也不太现实
各位有什么好提议吗?
[/Quote]

非ICMP报文也是可以广播的
do_fork 2009-11-21
  • 打赏
  • 举报
回复
[Quote=引用 4 楼 starwalker 的回复:]
我猜测如下,不一定对:
CS之类的可能是主机向子网广播的,每隔几秒就广播一次。
客户端收到主机发来的广播,就显示出来了。
[/Quote]

不用猜测,直接抓包看应该可以,
早期星际还用了IPX协议
whg01 2009-11-21
  • 打赏
  • 举报
回复
网络黑客经常是扫描某个网段范围内的所有ip。
CS之类应该是有广播。
starwalker 2009-11-21
  • 打赏
  • 举报
回复
我猜测如下,不一定对:
CS之类的可能是主机向子网广播的,每隔几秒就广播一次。
客户端收到主机发来的广播,就显示出来了。
cqsfd 2009-11-21
  • 打赏
  • 举报
回复
问题是我并不知道这个局域网范围多大啊?
如果子网是192.168.0.1-192.168.0.255,我可以从1-255一个个试,但万一子网不是这么大呢?
而且一个个试,非常耗时间,基本不可能做到想CS,war3那样一下就刷出局域网内主机的效果
我觉得用ICMP也不太现实
各位有什么好提议吗?
starwalker 2009-11-21
  • 打赏
  • 举报
回复
依次连接同一子网内的每个机器的指定端口,能连上,说明该主机开了该端口。
也可以使用多个线程同时连接不同主机。
do_fork 2009-11-21
  • 打赏
  • 举报
回复
不用ICMP,直接connect到10000,连的上就表示可以玩。

CS一般是要某一台当主机建立游戏的,建立游戏后可以监听XX端口,
其它机器扫描网络的时候,凡是xx端口打开并经过确认,就是该主机建立了游戏。

3,881

社区成员

发帖
与我相关
我的任务
社区描述
C/C++ 其它技术问题
社区管理员
  • 其它技术问题社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧