c++基于ip多播实现网络会议
单播多播与广播
普通 IP 通信是在一个发送者和一个接收者之间进行的,我们常把它称为点对点的通信,我们可以称之为单播。
多播我们可以认为是一对多,但并不是在一个局域网里的所有对象发送通讯,而是有选择的,广播我们可以理解为一个人通过广播喇叭对在场的全体说话,这样做的好处是通话效率高,信息一下子就可以传递到全体。
多播较于单播广播的优势之处,首先我们如果相对一个局域网里的特定几个人进行信息发送,如果采用单播方式,逐个节点传输,有多少个目标节点,就会有多少次传送过程,这种方式显然效率极低,是不可取的;如果采用不区分目标、全部发送的广播方式,虽然一次可以传送完数据,但是显然达不到区分特定数据接收对象的目的。
思路
首先为实现收发两个功能,需要分别创建两个project,分别进行收发:通过创建一个 SOCK_DGRAM 类型的 Socket,将此 Socket 绑定到本地的一个端口上,为了接收服务器端发送的多播数据,最后加入多播组,这样便可以接受局域网下的多播数据。这样在同一个局域网下我们可以分别实现收与发的功能。
问题是两种功能并不可以同时进行。因此为实现收发同时进行的功能,特引入线程的概念,将收发数据这两个功能分别写入两个线程。首先在初始化套接字,接入多播组,两个线程里的函数便共享同一个套接字。这里需要说明的是,未引入线程的时候,二者不能同时执行的原因并不因为二者引用的端口冲突。
实际上,收与发可以使用同一个端口,但是send函数以及recv函数是冲突的,不能同时执行,这也是引入线程的原因,通过引入子线程使得send函数以及recv函数可以同时执行,这样便实现了用户可以同时收发的功能。
winsock 版本初始化 建立套接字
WSADATA wsd;
struct sockaddr_in remote;
TCHAR sendbuf[BUFSIZE];
int len = sizeof(struct sockaddr_in);
//初始化 WinSock2.2
if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0)
{
printf("WSAStartup() failed\n");
return -1;
}
if ((sock = WSASocket(AF_INET, SOCK_DGRAM, 0, NULL, 0,
WSA_FLAG_MULTIPOINT_C_LEAF | WSA_FLAG_MULTIPOINT_D_LEAF |
WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)
{
printf("socket failed with:%d\n", WSAGetLastError());
WSACleanup();
return -1;
}
//加入多播组
remote.sin_family = AF_INET;
remote.sin_port = htons(MCASTPORT);
remote.sin_addr.s_addr = inet_addr(MCASTADDR);
if ((sockM = WSAJoinLeaf(sock, (SOCKADDR *) &remote,
sizeof(remote), NULL, NULL, NULL, NULL,
JL_BOTH)) == INVALID_SOCKET)
{
printf("WSAJoinLeaf() failed:%d\n", WSAGetLastError());
closesocket(sock);
WSACleanup();
return -1;
}
send 线程
void* sendsocket(void* arg)//向服务端socket发送数据的线程
{
int st = *(int*)arg;
char s[1024];
while (1)
{
memset(s, 0, sizeof(s));
scanf("%s", s);
if (sendto(sockM, (char *) s, strlen(s), 0, (struct sockaddr *)
&remote, sizeof(remote)) == SOCKET_ERROR)
{
printf("sendto failed with: %d\n", WSAGetLastError());
closesocket(sockM);
closesocket(sock);
WSACleanup();
break;
}
// if (strcmp(s, "QUIT") == 0)
// break;
Sleep(500);
}
return NULL;
}
recv 线程
void* recvsocket(void* arg)//接受来着客户端数据的线程
{
int st = *(int*)arg;
char s[1024];
while (1)
{
memset(s, 0, sizeof(s));
int rc = recv(st, s, sizeof(s), 0);
if (rc <= 0)//代表socket被关闭(0)或者出错(-1)
{
break;
}
printf("client receive:%s\n", s);
}
return NULL;
}
开启两个线程
pthread_t thrd1, thrd2;//定义一个线程
pthread_create(&thrd1, NULL, recvsocket, &sock);
pthread_create(&thrd2, NULL, sendsocket, &sock);
pthread_join(thrd1,NULL);
pthread_join(thrd2,NULL);
完整代码
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define MCASTADDR "233.0.0.1" //本例使用的多播组地址。
#define MCASTPORT 5150 //本地端口号。
#define BUFSIZE 1024 //发送数据缓冲大小。
#pragma comment(lib, "ws2_32")
struct sockaddr_in remote;
SOCKET sock, sockM;
void* recvsocket(void* arg)//接受来着客户端数据的线程
{
int st = *(int*)arg;
char s[1024];
while (1)
{
memset(s, 0, sizeof(s));
int rc = recv(st, s, sizeof(s), 0);
if (rc <= 0)//代表socket被关闭(0)或者出错(-1)
{
break;
}
printf("client receive:%s\n", s);
}
return NULL;
}
void* sendsocket(void* arg)//向服务端socket发送数据的线程
{
int st = *(int*)arg;
char s[1024];
while (1)
{
memset(s, 0, sizeof(s));
scanf("%s", s);
if (sendto(sockM, (char *) s, strlen(s), 0, (struct sockaddr *)
&remote, sizeof(remote)) == SOCKET_ERROR)
{
printf("sendto failed with: %d\n", WSAGetLastError());
closesocket(sockM);
closesocket(sock);
WSACleanup();
break;
}
// if (strcmp(s, "QUIT") == 0)
// break;
Sleep(500);
}
return NULL;
}
int main(int argc, char **argv)
{
WSADATA wsd;
struct sockaddr_in remote;
TCHAR sendbuf[BUFSIZE];
int len = sizeof(struct sockaddr_in);
//初始化 WinSock2.2
if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0)
{
printf("WSAStartup() failed\n");
return -1;
}
if ((sock = WSASocket(AF_INET, SOCK_DGRAM, 0, NULL, 0,
WSA_FLAG_MULTIPOINT_C_LEAF | WSA_FLAG_MULTIPOINT_D_LEAF |
WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)
{
printf("socket failed with:%d\n", WSAGetLastError());
WSACleanup();
return -1;
}
//加入多播组
remote.sin_family = AF_INET;
remote.sin_port = htons(MCASTPORT);
remote.sin_addr.s_addr = inet_addr(MCASTADDR);
if ((sockM = WSAJoinLeaf(sock, (SOCKADDR *) &remote,
sizeof(remote), NULL, NULL, NULL, NULL,
JL_BOTH)) == INVALID_SOCKET)
{
printf("WSAJoinLeaf() failed:%d\n", WSAGetLastError());
closesocket(sock);
WSACleanup();
return -1;
}
//发送多播数据,当用户在控制台输入"QUIT"时退出。
pthread_t thrd1, thrd2;//定义一个线程
pthread_create(&thrd1, NULL, recvsocket, &sock);
pthread_create(&thrd2, NULL, sendsocket, &sock);
pthread_join(thrd1,NULL);
pthread_join(thrd2,NULL);
closesocket(sockM);
closesocket(sock);
WSACleanup();
return 0;
}
实验结果截图