UDP通信分类
在基于UDP(面向无连接)的socket编程 这篇文章中,给出了UDP服务端和客户端编码流程。根据不同的场景需要,我们可以将UDP编程设置为三种通信模式,分别如下:
- 单播
- 广播
- 组播(也叫多播)
不同的通信模式是通过setsockopt
系统接口来完成,默认是UDP是单播模式,组播和广播需要setsockopt来配合完成。
以下是对三种通信方式概念说明
- 单播,一对一的通信方式,一个客户端和一个服务端之间的消息通信
- 广播,一对多的通信方式,会将消息分发给整个局域网内的所有主机,
- 组播,一对多的通信方式,将网络上的主机进行逻辑上的分组,通信只会在同一个分组上面进行收发消息。
适用场景说明:
单播
下图是单播场景的网络拓扑图,常见于多个client向服务端上报各自消息,用于信息收集,逻辑上是一对一的,因为上报的消息是不同的。此时,客户端的sockaddr_in中的IP地址信息需要设置为具体的服务器地址。
组播
下图是组播的网络拓扑图,主机被逻辑上归类为两组,后续消息收发都在各自分组上进行,互不影响。此时,服务端需要设置加入组播组和退出组播组的设置即可。
广播
下图是广播场景的网络拓扑图,常见于一个客户端向多个服务端广播消息,执行同一个动作或者初始化。此时,需要对客户端的socket设置广播标志以及sockaddr_in中的IP地址设置"255.255.255.255"
或者使用htonl(INADDR_BROADCAST)
组播通信设置
为了完成UDP上的组播通信,需要使用setsocketopt接口完成网络组播设置,其设置主要如下:
//加入多播组
struct ip_mreq stMreq;
//IP multicast address of group
stMreq.imr_multiaddr.s_addr = inet_addr("239.1.100.1");
//Local IP address of interface
stMreq.imr_interface.s_addr=htonl(INADDR_ANY);
setsockopt(RecvSocket,IPPROTO_IP,IP_ADD_MEMBERSHIP,(char *)&stMreq,sizeof(stMreq));
//结束后退出组播地址
setsockopt(RecvSocket,IPPROTO_IP,IP_DROP_MEMBERSHIP,(char *)&stMreq,sizeof(stMreq));
组播通信源码
服务端:
#include "stdafx.h"
#include <WinSock2.h>
#include <stdlib.h>
#include <Ws2tcpip.h>
#ragma comment(lib, "ws2_32.lib")
int _tmain(int argc, _TCHAR* argv[])
{
int iResult = 0;
const int BufLen = 1024;
char RecvBuf[BufLen];
WSADATA wsaData;
SOCKET RecvSocket;
sockaddr_in SenderAddr;
int SenderAddrSize = sizeof (SenderAddr);
// Initialize Winsock
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != NO_ERROR)
{
printf("WSAStartup failed with error %d\n", iResult);
return 1;
}
// Create a receiver socket to receive datagrams
RecvSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (RecvSocket == INVALID_SOCKET)
{
printf("socket failed with error %d\n", WSAGetLastError());
return 1;
}
// Bind the socket to any address and the specified port.
sockaddr_in RecvAddr;
RecvAddr.sin_family = AF_INET;
RecvAddr.sin_port = htons(6000);
RecvAddr.sin_addr.s_addr = htonl(INADDR_ANY);
iResult = bind(RecvSocket, (sockaddr *) & RecvAddr, sizeof (RecvAddr));
if (iResult != 0)
{
printf("bind failed with error %d\n", WSAGetLastError());
return 1;
}
//加入多播组
struct ip_mreq stMreq;
//IP multicast address of group
stMreq.imr_multiaddr.s_addr = inet_addr("239.1.100.1");
//Local IP address of interface
stMreq.imr_interface.s_addr=htonl(INADDR_ANY);
setsockopt(RecvSocket,IPPROTO_IP,IP_ADD_MEMBERSHIP,(char *)&stMreq,sizeof(stMreq));
// Call the recvfrom function to receive datagrams
// on the bound socket.
char szMsgData[128] = {};
while (1)
{
printf("waiting get msg ....\n");
memset(RecvBuf,0, sizeof(RecvBuf));
iResult = recvfrom(RecvSocket,RecvBuf, BufLen, 0, (sockaddr*)&SenderAddr, &SenderAddrSize);
if (iResult == SOCKET_ERROR)
{
printf("recvfrom failed with error %d\n", WSAGetLastError());
}
else
{
printf("client->server:%s\n",RecvBuf);
int nSendSize = sendto(RecvSocket,"i received",strlen("i received"),0,(sockaddr*)&SenderAddr,SenderAddrSize);
if (nSendSize <= 0)
{
printf("send data error...\n");
}
}
if (strlen(RecvBuf) == 1 && 'q' == RecvBuf[0])
{
break;
}
}
// Close the socket when finished receiving datagrams
printf("Finished receiving. Closing socket.\n");
//结束后退出组播地址
setsockopt(RecvSocket,IPPROTO_IP,IP_DROP_MEMBERSHIP,(char *)&stMreq,sizeof(stMreq));
if (closesocket(RecvSocket) == SOCKET_ERROR)
{
printf("closesocket failed with error %d\n", WSAGetLastError());
return 1;
}
// Clean up and exit.
WSACleanup();
return 0;
}
客户端:
客户端代码和单播的场景是一样的,不需要进行特殊的设置。
#include "stdafx.h"
#include <Windows.h>
#pragma comment(lib, "ws2_32.lib")
int _tmain(int argc, _TCHAR* argv[])
{
WORD wVersionRequested;
WSADATA wsaData;
wVersionRequested = MAKEWORD(2, 2);
int err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
/* Tell the user that we could not find a usable */
printf("WSAStartup failed with error: %d\n", err);
return 1;
}
/* Confirm that the WinSock DLL supports 2.2.*/
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
printf("Could not find a usable version of Winsock.dll\n");
WSACleanup();
return 1;
}
//创建socket
SOCKET socketClient = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (socketClient == INVALID_SOCKET)
{
printf("socket failed with error %d\n", WSAGetLastError());
return 1;
}
//填充服务端通信端口以及通信地址
SOCKADDR_IN addrServer;
addrServer.sin_family = AF_INET;
addrServer.sin_port = htons(6000);
addrServer.sin_addr.S_un.S_addr = inet_addr("239.1.100.1");
int nLength = sizeof(addrServer);
char szSendMsg[100]= {0};
char szRecvBuf[100] = {0};
while (1)
{
//发送数据
printf("pls input msg...:\n");
memset(szSendMsg,0, sizeof(szSendMsg));
gets_s(szSendMsg, sizeof(szSendMsg));
int nSendSize = sendto(socketClient,szSendMsg,strlen(szSendMsg),0,(SOCKADDR*)&addrServer,nLength);
if (nSendSize > 0)
{
printf("client->server:%s\n",szSendMsg);
}
else
{
printf("send data error...\n");
}
//接收数据
memset(szRecvBuf,0, sizeof(szRecvBuf));
int nRecvSize = recvfrom(socketClient,szRecvBuf,100,0,(SOCKADDR*)&addrServer,&nLength);
if (nRecvSize > 0)
{
printf("server->client:%s\n",szRecvBuf);
}
}
closesocket(socketClient);
WSACleanup();
return 0;
}
更多的多播参数设置可参考如下资料: