局域网多点互连的实现方法

     通常局域网内用户的点对点连接多采用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程序,作为网络编程,此处要多注意相关函数调用是错误的处理,怎样才能够保证网络连接各种情况的正确性,同时要根据应用需求设置合理的缓冲区大小,否则可能发生未知错误。

以此为基础可以实现   点对点,点对多,多对点通信,当然这需要的适当数据包设计。以及相应的识别处理。