14_UDP_广播_组播_本地套接字

代码: https://github.com/WHaoL/study/tree/master/00_06_Linux_SystemCode_and_SocketCode

代码: https://gitee.com/liangwenhao/study/tree/master/00_06_Linux_SystemCode_and_SocketCode

1. UDP

1.1.UDP概念

1.传输层协议
2.面向无连接的, 不安全(会丢失数据), 报式(报文)传输协议
  无连接:没有连接,通信不需要建立连接
  不安全:数据会丢失
     客户端给服务器发送1k数据, 服务器没收到
	    全部没收到->可能
		收到了一部分->不可能
  报文传输:发送和接收的数据是等量的
	 只有收到没收到
		收到了就是全部的
		没收到就是一丁点都没有收到
  优势: 效率高
  
报式(UDP):发多少收多少--每次发送和接收的数据是等量的
	 一次发10k,对方就一次收10k
流式(TCP):发多少,不一定收多少--每次发送和接收的数据是不等量的
     一次发10k,对方可以分十次接受 每次收1k

Qt中
// udp -> QUdpSocket
// Tcp -> QTcpServer (监听)  QTcpSocket(通信)

1.2.UDP通信流程

在这里插入图片描述

1.2.1.UDP操作函数
// 接收数据
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                 struct sockaddr *src_addr, socklen_t *addrlen);
参数:
    - sockfd: 通信的文件描述符
    - buf: 接收数据的缓冲区(数组地址)
    - len: buf的容量
    - flags:0, 使用默认属性
    - src_addr: 发送数据的那一方地址信息: IP, Port, 地址族协议 -> 传出参数
    - addrlen: 传入传出参数, src_addr 对应的内存大小
        
// 发送数据
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
               const struct sockaddr *dest_addr, socklen_t addrlen);
参数:
	- sockfd: 通信的文件描述符
	- buf: 存储了要发送的数据
	- len: 要发送的数据的长度 -> 通过strlen() 计算
	- flags:0, 使用默认属性
	- dest_addr: 接收数据的一方的地址信息
	- addrlen: 传入, dest_addr对应的内存大小
1.2.2.通信流程
// 服务器端
1. 创建用于通信的套接字 -> 使用报式协议( SOCK_DGRAM )
int cfd = socket(af_inet, SOCK_DGRAM, 0);
2. 绑定本地的IP和端口 和 用于通信的套接字绑定 
bind();
3. 通信
    - 接收数据: recvfrom
    - 发送数据: sendto
4. 关闭连接: close(cfd);
// 客户端通信流程:
1. 创建用于通信的套接字 -> 使用报式协议( SOCK_DGRAM )
int cfd = socket(af_inet, SOCK_DGRAM, 0);
2. 通信
    - 接收数据: recvfrom
    - 发送数据: sendto
3. 关闭连接: close(cfd);
client.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <errno.h>

int main()
{
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (-1 == fd)
    {
        perror("socket");
        exit(0);
    }

    struct sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(8888);
    unsigned int lenser = sizeof(serverAddr);
    inet_pton(AF_INET, "192.168.184.134", &serverAddr.sin_addr.s_addr);

    int num = 0;
    while (1)
    {
        char buf[1024] = {0};
        sprintf(buf, "----你好服务器--%d...", num++);
        sendto(fd, buf, strlen(buf) + 1, 0, (struct sockaddr *)&serverAddr, lenser);

        recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL);
        printf("服务器发送来的数据是:%s\n", buf);
        sleep(1);
    }

    close(fd);
    return 0;
}
server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <errno.h>

int main()
{
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (-1 == fd)
    {
        perror("socket");
        exit(0);
    }

    struct sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(8888);
    inet_pton(AF_INET, "192.168.184.134", &serverAddr.sin_addr.s_addr);
    int ret = bind(fd, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
    if (-1 == ret)
    {
        perror("bind");
        exit(0);
    }

    while (1)
    {
        char buf[1024] = {0};
        struct sockaddr_in clientAddr;
        unsigned int lencli = sizeof(clientAddr);
        recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&clientAddr, (socklen_t *)&lencli);

        char ip[17] = {0};
        inet_ntop(AF_INET, &clientAddr.sin_addr.s_addr, ip, sizeof(ip));
        unsigned short int port = ntohs(clientAddr.sin_port);
        printf("client IP: %s, Port: %d, Data: %s\n", ip, port, buf);

        sprintf(buf, "客户端的IP:%s,Port:%d", ip, port);
        sendto(fd, buf, strlen(buf) + 1, 0, (struct sockaddr *)&clientAddr, lencli);
    }

    close(fd);
    return 0;
}

2. 广播

概念

向子网中多台计算机发送消息,并且子网中所有的计算机都可以接收到发送方发送的消息,每个广播消息都包含一个特殊的IP地址,这个IP中子网内主机标志部分的二进制全部为1。

- 只能在局域网中使用
- 客户端只要绑定了服务器广播使用的端口, 就可以接收到广播数据

UDP天然支持广播

在这里插入图片描述

UDP广播:通信流程

// 广播的一端
1. 创建用于通信的套接字 -> 使用报式协议( SOCK_DGRAM )
int cfd = socket(af_inet, SOCK_DGRAM, 0);
2. 设置广播属性
setsockopt();
3. 通信之前的准备工作--设置广播地址和广播端口:发送数据时使用
struct sockaddr_in recvAddr;
recvAddr.sin_family = af_inet;
//接收数据的一方需要绑定这个端口
recvAddr.sin_port = htons(8989);
// 需要指定广播地址
inet_pton(af_inet, "xx.xx.xx.255", &recvAddr.sin_addr.s_addr);
4. 发送广播消息
sendto(cfd, msg, msgLen, 0, &recvAddr, sizeof(recvAddr));
5. 关闭连接: close(cfd);

// 接收广播的一端
1. 创建用于通信的套接字 -> 使用报式协议( SOCK_DGRAM )
int cfd = socket(af_inet, SOCK_DGRAM, 0);
2. 端口绑定 -> 需要和发送广播一端发送数据指定的端口对应
struct sockaddr_in addr;
//接受广播数据的端口
addr.sin_port = htons(8989);
bind(cfd, &addr, sizeof(addr));
3. 通信
	- 接收数据: recvfrom
4. 关闭连接: close(cfd);

设置广播属性的函数

int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
	- sockfd: udp通信的套接字
	- level: SOL_SOCKET
	- optname: SO_BROADCAST
	- optval: 整形数。 值为1:设置广播;值为0:不设置广播属性
	- optlen: optval参数对应的内存大小 -> sizeof(optval)

广播的特点:

1. 在局域网范围内只要是客户端绑定的对应的端口, 都可以收到广播消息
	- 在这种情况下, 是不能拒绝接收广播消息的
2. 只能在局域网中使用, 不能在广域网中使用
3. 必须要使用广播地址 -> xxx.xxx.xxx.255
client.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <errno.h>

int main()
{
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (-1 == fd)
    {
        perror("socket");
        exit(0);
    }

    struct sockaddr_in Addr;
    Addr.sin_family = AF_INET;
    Addr.sin_port = htons(8888); // 监听的port和广播的port相同
    //inet_pton(AF_INET, "192.168.184.134", &Addr.sin_addr.s_addr);
    Addr.sin_addr.s_addr = INADDR_ANY; //自动绑定本地IP
    int ret = bind(fd, (struct sockaddr *)&Addr, sizeof(Addr));
    if (-1 == ret)
    {
        perror("bind");
        exit(0);
    }

    while (1)
    {
        char buf[1024] = {0};
        recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL);
        printf("服务端发来的数据是: %s\n", buf);
    }

    close(fd);
    return 0;
}
server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <errno.h>

int main()
{
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (-1 == fd)
    {
        perror("socket");
        exit(0);
    }

    //设置广播属性
    int opt = 1;
    setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt));

    struct sockaddr_in Addr;
    Addr.sin_family = AF_INET;
    Addr.sin_port = htons(8888);                                  //广播的port
    inet_pton(AF_INET, "192.168.184.255", &Addr.sin_addr.s_addr); //广播的ip

    int num = 0;
    while (1)
    {
        char buf[1024] = {0};
        sprintf(buf, "你好客户端:%d", num++);
        sendto(fd, buf, strlen(buf) + 1, 0, (struct sockaddr *)&Addr, sizeof(Addr));
        sleep(1);
    }

    close(fd);
    return 0;
}

3. 组播/组播

概念&特点

广播时Server给局域网的交换机发送数据,无论连接到局域网的客户端想不想接收该数据,Server都会给客户端发送该数据。—>进而造成客户端上数据的拥塞—>因此引出了多播(组播)

特点:
- Server可以将数据包只发送给指定组内的客户端,而不发送给指定组外的客户端。
- 可以在internet(广域网)中进行组播,可以在局域网中进行组播
- 加入到组播地址中才能接收到数据
- 组播也有特定的组播地址, 需要人为指定才可以

在这里插入图片描述

组播地址

IP 多播通信必须依赖于 IP 多播地址,在 IPv4 中它的范围从 224.0.0.0239.255.255.255,并被划分为局部链接多播地址、预留多播地址和管理权限多播地址三类:

IP地址说明
224.0.0.0~224.0.0.255局部链接多播地址:是为路由协议和其它用途保留的地址,路由器并不转发属于此范围的IP包
224.0.1.0~224.0.1.255预留多播地址:公用组播地址,可用于Internet;使用前需要申请
224.0.2.0~238.255.255.255预留多播地址:用户可用组播地址(临时组地址),全网范围内有效
239.0.0.0~239.255.255.255本地管理组播地址,可供组织内部使用,类似于私有 IP 地址,不能用于 Internet,可限制多播范围

UDP组播:通信流程

// 发送组播的一端
1. 创建用于通信的套接字 -> 使用报式协议( SOCK_DGRAM )
int cfd = socket(af_inet, SOCK_DGRAM, 0);
2. 设置组播属性
setsockopt();
3. 通信之前的准备工作--设置组播地址和组播端口:发送数据时使用
struct sockaddr_in recvAddr;
recvAddr.sin_family = af_inet;
//接收数据的一方需要绑定这个端口
recvAddr.sin_port = htons(8989);
//需要指定组播地址
inet_pton(af_inet, "239.0.1.10", &recvAddr.sin_addr.s_addr);
4. 发送组播消息
sendto(cfd, msg, msgLen, 0, &recvAddr, sizeof(recvAddr));
5. 关闭连接: close(cfd);

// 接收组播的一端
1. 创建用于通信的套接字 -> 使用报式协议( SOCK_DGRAM )
int cfd = socket(af_inet, SOCK_DGRAM, 0);
2. 端口绑定 -> 需要和发送广播一端发送数据指定的端口对应
struct sockaddr_in addr;
// 接受组播数据的端口
addr.sin_port = htons(8989); 	
bind(cfd, &addr, sizeof(addr));
3. 加入到组播地址(加入到多播组)
setsockopt();
4. 通信
- 接收数据: recvfrom
5. 关闭连接: close(cfd);

UDP设置组播属性

int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
- 设置组播属性:发送组播消息--那一端
	- sockfd: 通信的fd
	- level: IPPROTO_IP
	- optname: IP_MULTICAST_IP
	- optval: struct in_addr 类型->ip地址->组播的IP地址
	- optlen: optval的内存大小, sizeof(optval)
        
- 加入到多播组:接收组播消息--那一端
	- sockfd: 通信的fd
	- level: IPPROTO_IP
	- optname: IP_ADD_MEMBERSHIP
	- optval: struct ip_mreqn 类型 
	- optlen: optval的内存大小, sizeof(optval)

typedef unsigned int uint32_t;
typedef uint32_t     in_addr_t;
struct in_addr
{
    in_addr_t s_addr;
};         
struct ip_mreqn	// 美人去哪儿
{
	struct in_addr imr_multiaddr;// 组播组的IP地址. 
	struct in_addr imr_address;// 本地某一网络设备接口的IP地址。   
	int   imr_ifindex;//网卡编号
};
// 通过网卡名字得到网卡编号
#include <net/if.h>
unsigned int if_nametoindex(const char *ifname);
03_multicast_client.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <net/if.h>

int main()
{
    //1.创建通信的套接字
    int fd = socket(AF_INET,SOCK_DGRAM,0);
    if(fd == -1)
    {
        perror("socket");
        exit(0);
    }
	
    //2.初始化本地地址信息:主要是绑定Port(接受数据使用)
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(9898);
    addr.sin_addr.s_addr = INADDR_ANY;
    bind(fd,(struct sockaddr*)&addr,sizeof(addr));
	
    //3.加入到多播组
    struct ip_mreqn mymreqn;
    mymreqn.imr_address.s_addr = INADDR_ANY;//本地IP
    inet_pton(AF_INET,"239.0.1.10",&mymreqn.imr_multiaddr.s_addr);//组播地址
    mymreqn.imr_ifindex = if_nametoindex("ens33");//获取网卡信息
    setsockopt(fd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mymreqn,sizeof(mymreqn));

    
    //4.通信:接收组播消息
    int num = 0;
    while(1)
    {
        char buf[1024];
        recvfrom(fd,buf,sizeof(buf),0,NULL,NULL);
        printf("服务器的数据:%s\n",buf);
        
    }
    //5.关闭fd
    close(fd);
    
    return 0;
}
03_multicast_server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>

int main()
{
    //1.创建通信的套接字
    int fd = socket(AF_INET,SOCK_DGRAM,0);
    if(fd == -1)
    {
        perror("socket");
        exit(0);
    }
	
    //2.设置组播属性
    struct in_addr myaddr;
    inet_pton(AF_INET,"192.168.184.132",&myaddr.s_addr);
    setsockopt(fd,IPPROTO_IP,IP_MULTICAST_IF,&myaddr,sizeof(myaddr));
	
    //3.初始化组播的地址信息:发送时使用(组播端口和组播地址)
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(9898);
    inet_pton(AF_INET,"239.0.1.10",&addr.sin_addr.s_addr);
    
    //4.通信:组播发送数据
    int num = 0;
    while(1)
    {
        char buf[1024];
        sprintf(buf,"你好客户端...%d...",num++);
        sendto(fd,buf,strlen(buf)+1,0,(struct sockaddr*)&addr,sizeof(addr));
        sleep(1);
    }
    //5.关闭fd
    close(fd);
    
    return 0;
}

4. 本地套接字

1.本地套接字: 只能用于某个终端上的进程之间的通信
 - 有血缘关系
 - 没有血缘关系
2.通信流程 
- 按照tcp的通信流程编写:常用这种方式
- 按照udp的通信流程编写

在这里插入图片描述

结构体

// 头文件:  sys/un.h
#include <sys/un.h> 
#define UNIX_PATH_MAX 108
struct sockaddr_un {
	sa_family_t sun_family;      // 地址族协议 af_local
	char sun_path[UNIX_PATH_MAX];// 套接字文件的路径, 这是一个伪文件, 大小永远=0
};

通信流程

// 服务器端进程
1. 创建套接字, 得到一个文件描述符
	int fd = socket(int domain, int type, int protocol);
		- 参数1: AF_UNIX, AF_LOCAL
		- 参数2: 
			- SOCK_STREAM: 按照tcp流程通信
			- SOCK_DGRAM: 按照udp流程通信
		- 参数3: 0
     int fd = socket(AF_LOCAL,SOCK_STREAM,0)   
            
2. 将第一步得到的文件描述符和本地的IP端口进行绑定
	struct socaddr_un addr;
	addr.sun_family = AF_LOCAL;
	strcpy(addr.sun_path, "server.sock");
	// 绑定成功 套接字文件会被创建: server.sock
	bind(fd, (struct sockaddr*)&addr, sizeof(addr));

3. 设置监听(绑定成功的文件描述符) -> 成功之后可以接收客户端连接
    listen();

4. 如果有客户端连接服务器, 服务器接受请求并建立连接, 得到一个文件描述符
	struct sockaddr_un cliaddr;
	int fd1 = accept(fd, (struct sockaddr*)cliaddr, &len);

5. 通信
	- 接收数据
		read();
    	recv();
	- 发送数据
		write();
		send();
6. 通信完成, 关闭文件描述符
	close();
// 客户端进程
1. 创建套接字
	int fd = socket(int domain, int type, int protocol);
		- 参数1: AF_UNIX, AF_LOCAL
		- 参数2: 
			- SOCK_STREAM: 按照tcp流程通信
			- SOCK_DGRAM: 按照udp流程通信
		- 参数3: 0
2. 客户端也需要绑定一个本地套接字
	struct socaddr_un addr;
	addr.sun_family = af_unix;
	strcpy(addr.sun_path, "client.sock");	// 客户端绑定的套接字文件
	// 绑定成功 套接字文件会被创建: client.sock
	bind(fd, (struct sockaddr*)&addr, sizeof(addr));

3. // 客户端的端口不需要调用函数绑定, 自动就绑定了, 如果非得自己绑定也可以 -> bind
   连接服务器: 在客户端需要知道服务器绑定的IP和端口是多少
    struct sockaddr_un seraddr;
	seraddr.sun_family = af_local;
	strcpy(seraddr.sun_path, "server.sock");	// 指定服务器端使用的套接字文件
    connect(fd, (struct sockaddr*)&seraddr, sizeof(seraddr));



4. 通信
	- 接收数据
		read();
		recv();
	- 发送数据
		write();
		send();
5. 断开连接
	close():
04_localipc_client.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/un.h>

int main()
{
    // 1. 创建用于通信的套接字
    int fd = socket(AF_LOCAL, SOCK_STREAM, 0);
    if(fd == -1)
    {
        perror("socket");
        exit(0);
    }

    // 2. 绑定一个本地套接字
    struct sockaddr_un cliaddr;
    cliaddr.sun_family = AF_LOCAL;
    strcpy(cliaddr.sun_path, "client.sock");    // 客户端绑定的套接字文件
    bind(fd, (struct sockaddr*)&cliaddr, sizeof(cliaddr));
    
    // 3. 连接服务器
    struct sockaddr_un addr;
    addr.sun_family = AF_LOCAL;
    strcpy(addr.sun_path, "server.sock");    // 服务器端绑定的套接字文件
    int ret = connect(fd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret == -1)
    {
        perror("connect");
        exit(0);
    }


    int i = 0;
    // 通信
    while(1)
    {
        // 读数据
        char recvBuf[1024];
        // 写数据
        sprintf(recvBuf, "data: %d\n", i++);
        write(fd, recvBuf, strlen(recvBuf)+1);
        // 如果客户端没有发送数据, 默认阻塞
        read(fd, recvBuf, sizeof(recvBuf));
        printf("recv buf: %s\n", recvBuf);
        sleep(1);
    }

    // 释放资源
    close(fd); 

    return 0;
}
04_localipc_server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/un.h>

int main()
{
    // 1. 创建用于监听的套接字
    int fd = socket(AF_LOCAL, SOCK_STREAM, 0);
    if(fd == -1)
    {
        perror("socket");
        exit(0);
    }

    // 2. 绑定本地
    struct sockaddr_un addr;
    addr.sun_family = AF_LOCAL;
    strcpy(addr.sun_path, "server.sock");
    // 绑定成功server.sock文件被创建
    int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret == -1)
    {
        perror("bind");
        exit(0);
    }

    // 3.设置监听
    ret = listen(fd, 100);
    if(ret == -1)
    {
        perror("listen");
        exit(0);
    }

    // 4. 等待, 接受连接请求
    struct sockaddr_un addrCli;//存储客户端的addr
    int len = sizeof(addrCli);
    printf("正在焦急等待客户端的连接...\n");
    int connfd = accept(fd, (struct sockaddr*)&addrCli, &len);
    if(connfd == -1)
    {
        perror("accept");
        exit(0);
    }
    // 得到客户端进程绑定的本地套接字文件的名字
    printf("client socket fileName: %s\n", addrCli.sun_path);

    int num = 0;
    // 通信
    while(1)
    {
        // 读数据
        char recvBuf[1024];
        // 如果客户端没有发送数据, 默认阻塞
        int ret = read(connfd, recvBuf, sizeof(recvBuf));
        if(ret == -1)
        {
            perror("read");
            break;
        }
        else if(ret == 0)
        {
            printf("客户端已经断开了连接...\n");
            break;
        }
        else
        {
            printf("recv buf: %s\n", recvBuf);
            // 写数据
            write(connfd, recvBuf, strlen(recvBuf));
        }
    }
	
	
    // 释放资源
	close(connfd);  // 通信
    close(fd);  // 监听
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值