局域网多点互连的实现方法
通常局域网内用户的点对点连接多采用C/S架构,这种情况下就必须有一方作为客户端另一方作为服务器端,本文将叙述如何在各用户处于“平级”情况下不依赖服务器所进行的多点互连。
(本文仅罗列相关操作步骤和简单说明,具体函数说明请参考MSDN)
步骤一: 创建广播套接字
我将创建的整个过程封装在SOCKET NET_CreateBroadCastSocket(char *szAddress)中
其中szAddress为本地IP地址的字符串形式具体获得可以通过gethostname()和gethostbyname()函数获得。
Winsock的广播必须基于UDP实现,故首先使用SOCK_DGRAM参数创建套接字,然后将其绑定到szAddress的地址上,最后使用setsockopt()设置套接字的广播属性。通常情况下还会使用getsockopt()进行属性验证,本例进行了省略。
函数实现代码如下
/*
功能: 创建广播套接字
参数: szAddress 绑定IP的字符串形式
返回值: 具有广播属性基于UDP的套接字
*/
SOCKET NET_CreateBroadCastSocket(char *szAddress)
{
SOCKET sockBroad;
NET_InitSock();
sockBroad = socket(AF_INET, SOCK_DGRAM, 0);
if (INVALID_SOCKET == sockBroad)
{
MessageBox(NULL, "网络初始化失败!", "错误", MB_OK | MB_ICONEXCLAMATION);
PostQuitMessage(0); //此为配合窗口过程用
}
//绑定
int retVal;
SOCKADDR_IN addrHost;
addrHost.sin_family = AF_INET;
addrHost.sin_addr.S_un.S_addr = inet_addr(szAddress);
addrHost.sin_port = htons(MAIN_PORT);
retVal = bind(sockBroad, (SOCKADDR*)&addrHost, sizeof(SOCKADDR));
if (SOCKET_ERROR == retVal)
{
MessageBox(NULL, TEXT("网络错误!"), TEXT("错误"), MB_OK | MB_ICONEXCLAMATION);
PostQuitMessage(0);
}
//设置广播属性
BOOL value = TRUE;
retVal = setsockopt(sockBroad,SOL_SOCKET,SO_BROADCAST, (char *) &value, 1);
if(SOCKET_ERROR == retVal)
{
closesocket(sockBroad);
WSACleanup();
return 0;
}
return sockBroad;
}
注意: 此处广播属性若未设置成功将导致发送数据失败, 此处还要根据所设计数据包的大小设置缓冲区得大小,设置说使用的函数为setsockopt()并使用getsockopt()进行确认设置成功。
步骤二: 创建用户列表,用于保存相关用户信息
//用户信息
typedef struct _USERINFO
{
char szUsername[MAX_LEN]; //主机名
char cGroup; //用户所选分组 用于用户选择 此字段暂时未用
char szAddress[MAX_LEN]; //IP地址
struct _USERINFO *next;
}USERINFO;
//用户链表
typedef struct _USERLIST
{
USERINFO *pUserList; //用户列表
int nUser; //用户数目
}USERLIST;
//用户列表相关操作都是一些简单的链表操作
//创建用户列表
USERLIST *USER_CreateUserList();
//添加用户
void USER_AddUser(USERLIST *pUserlist, USERINFO newUser);
//删除用户
void USER_DeleteUser(USERLIST *pUserlist, USERINFO delUser);
//是否在用户列表
BOOL USER_IsUser(USERLIST *pUserlist, USERINFO isUser);
//销毁用户列表
void USER_DestroyList(USERLIST *pUserlist);
//相关用户初始化代码如下
/*
此代码片段实现功能,创建用户列表,获取本地用户并将其加入到用户列表中
*/
char hostName[MAX_PATH] = "";
gethostname(hostName, MAX_PATH); //获取主机名
strcpy(userName, hostName);
HOSTENT *hostInfo = gethostbyname(hostName); //获取主机相关信息
//创建用户列表
pUserlist = USER_CreateUserList();
//将自身加入用户列表
USERINFO newUser;
memset(&newUser, 0, sizeof(USERINFO));
strcpy(newUser.szAddress, inet_ntoa(*(in_addr *)hostInfo->h_addr));//主机IP地址
strcpy(newUser.szUsername, userName);
USER_AddUser(pUserlist, newUser);
步骤三: 使用异步I/O基于windows消息的select模型
The WSAAsyncSelect function requests Windows message-based notification of network events for a socket.
int WSAAsyncSelect(SOCKET s, HWND hWnd, unsigned int wMsg, long lEvent);
此处我们感兴趣的网络时间包括:FD_CLOST 和 FD_READ
wMsg 为用户自定义消息 UM_MAINSOCK
在创建广播套接字后我们注册相应的网络事件
//相关代码解释如下
//注册网络事件消息
……
#define UM_MAINSOCK WM_USER + 1
……
int retVal;
retVal = WSAAsyncSelect(sockMain, hwndMain, UM_MAINSOCK, FD_READ | FD_CLOSE);
if (SOCKET_ERROR == retVal)
{
MessageBox(hwndMain, TEXT("网络错误!"), TEXT("错误"),
MB_OK | MB_ICONEXCLAMATION);
PostQuitMessage(0); //配合窗口话过程
}
……
//消息相应
case UM_MAINSOCK:
{
if (WSAGETSELECTERROR(lParam)) //是否有错误
{
closesocket(sockMain);
return FALSE;
}
switch (WSAGETSELECTEVENT(lParam)) //处理相关时间
{
case FD_READ: //有可接受的数据包
{
int recvLen = sizeof(SOCKADDR);
SOCKADDR_IN addrRecv;
recvfrom(sockMain,recvBuf,MAX_RECVLEN,0,(SOCKADDR*)&addrRecv,
&recvLen);
//获取数据包内容
USERINFO userInfo;;
userInfo = *((USERINFO *)recvBuf); //接受到的系统请求信息
if (!USER_IsUser(pUserlist, userInfo)) //保证用户为加入
{
USER_AddUser(pUserlist, userInfo);
sendto(sockMain,(char*)&LocalUserInfo,sizeof(USERINFO), 0,(SOCKADDR *)&addrBroad, sizeof(SOCKADDR));
//此操作保证无论用户登录时间如何都能够获得当前所有和连接用户
}
}
Break;
case FD_CLOSE:
{
closesocket(sockMain);
}
break;
}
}
break;
注意:此处在接受用户数据包时仅简单的将其加入到用户列表,并未牵扯到用户离开时的删除工作,要完成这项工作需要对数据包进行重新包装,并对相关标识字段进行处理。
用户的唯一标识符为IP地址
步骤四: 广播用户登录信息
//广播登录消息
addrBroad.sin_family = AF_INET;
addrBroad.sin_addr.S_un.S_addr = htonl(INADDR_BROADCAST); //广播的地址
addrBroad.sin_port = htons(MAIN_PORT);
//登录数据包设置
strcpy(LocalUserInfo.szUsername, userName);
strcpy(LocalUserInfo.szAddress, newUser.szAddress);
//广播登录消息 此消息允许不成功
sendto(sockMain, (char *)&reqPacket, sizeof(REQPACKET), 0,
(SOCKADDR *)&addrBroad, sizeof(SOCKADDR));
至此,局域网内多点互联基本工作已经完成,在此基础上可以完成局域网聊天室或简单IM程序,作为网络编程,此处要多注意相关函数调用是错误的处理,怎样才能够保证网络连接各种情况的正确性,同时要根据应用需求设置合理的缓冲区大小,否则可能发生未知错误。
以此为基础可以实现 点对点,点对多,多对点通信,当然这需要的适当数据包设计。以及相应的识别处理。