C/C++:UDP(广播、组播),TCP单播

xingyun86 2020-9-24 1012

1. udp


1.1 udp通信流程

- 服务器端
    - 1.创建通信的套接字
         - int fd = socket( af_inet, SOCK_DGRAM, 0)
    - 2. 绑定 -> 通信的fd 和本地 IP / port 绑定
           - struct sockaddr_in addr;
    - 3.通信
          - 接收数据: recvfrom
          - 发送数据: sendto
    - 4.关闭通信的fd
         - close(fd);
  - 客户端
    - 1.创建一个通信的套接字
    - 2.通信
         - 接收数据: recvfrom
         - 发送数据: sendto
    - 3.关闭通信的文件描述符
         - close();

 1.2 操作函数

  • send、sendto

#include <sys/types.h>
#include <sys/socket.h>
 
#include <arpa/inet.h>   //也可以只用这一个头文件,包含了上面2个头文件
 
  // tcp 发送数据的函数 write
  ssize_t send(int sockfd, const void *buf, size_t len, int flags);
 
  // udp 发送数据
  ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                        const struct sockaddr *dest_addr, socklen_t addrlen);
  	参数:
  		- sockfd: 通信的fd
		- buf: 要发送的数据
  		- len: 要发送的数据的长度
  		- flags: 0
          - dest_addr: 通信的另外一端的地址信息
          - addrlen: dest_addr的内存大小

recv、recvfrom

#include <sys/types.h>
#include <sys/socket.h>
 
#include <arpa/inet.h>   //也可以只用这一个头文件,包含了上面2个头文件
 
  // tcp 接收数据 read
  ssize_t recv(int sockfd, void *buf, size_t len, int flags);
 
  // udp 接收数据函数
  ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                          struct sockaddr *src_addr, socklen_t *addrlen);
  	参数:
  		- sockfd: 通信的fd
  		- buf: 接收数据的一块内存
  		- len: 接收数据的内存(第二个参数)大小
  		- flags: 0
                - src_addr: 接收谁的数据, 就写入了那个终端的地址信息, 如果不要这部分数据 -> NULL
                - addrlen: src_addr参数对应内存大小(传入传出参数)
                  
   int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

2. 广播

向子网中多台计算机发送消息,并且子网中所有的计算机都可以接收到发送方发送的消息。

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

2.1 广播通信流程

服务器端 -> 广播的一端: 

- 创建通信的套接字
              int fd = socket( af_inet, SOCK_DGRAM, 0);
- 设置udp广播属性
                  setsockopt();
- 通信 -> 发送广播数据
                struct sockaddr_in cliaddr;
                cliaddr.sin_port(8888);    // 广播数据发送到客户端的8888端口, 客户端需要绑定该端口
                cliaddr.sin_addr.s_addr -> 初始化一个广播地址
               发送数据: sendto(fd, buf, len, 0, &cliaddr, len);
- 关闭通信的fd
              close(fd);


客户端

- 创建一个通信的套接字
                  int fd = socket( af_inet, SOCK_DGRAM, 0);
- 如果想接收广播数据, 需要绑定以固定端口(服务器广播数据使用的端口)
                struct sockaddr_in cliaddr;
                cliaddr.sin_port(8888);    
                bind(fd, cliaddr, len);
- 通信
              接收数据: recvfrom
- 关闭通信的文件描述符
              close();


2.2 设置广播属性函数:setsockopt

这个函数有许多功能,这里只讨论设置广播属性函数功能

#include <arpa/inet.h>
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
  	参数:
  		- sockfd: 文件描述符
  		- level: SOL_SOCKET
  		- optname: SO_BROADCAST
  		- optval: int数值为1, 允许广播
  		- optlen: optval内存大小

2.3 广播代码

服务器:

#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);
    }
    // 设置广播属性
    int op = 1;
    setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &op, sizeof(op));
    // 将数据发送给客户端, 使用广播地址和固定端口
    // 初始化客户端的地址信息
    struct sockaddr_in cliaddr;
    cliaddr.sin_family = AF_INET;
    cliaddr.sin_port = htons(8989); // 客户端也需要绑定这端口
    inet_pton(AF_INET, "192.168.247.255", &cliaddr.sin_addr.s_addr);
 
    int num = 0;
    // 3. 通信
    while(1)
    {
        // 接收数据
        char buf[128];
        // 发送数据
        sprintf(buf, "hello, client...%d\n ", num++);
        sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&cliaddr, sizeof(cliaddr));
        printf("广播的数据: %s\n", buf);
        sleep(1);
    }
 
    close(fd);
 
    return 0;
}

客户端:

#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. 通信的fd绑定本地的IP和端口
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8989);
    addr.sin_addr.s_addr = INADDR_ANY;
    int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret == -1)
    {
        perror("bind");
        exit(0);
    }
    //inet_pton(AF_INET, "0.0.0.0", &addr.sin_addr.s_addr);
 
    // 3. 通信
    while(1)
    {
        // 接收数据
        char buf[128];
        recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL);
        printf("server say: %s\n", buf);
    }
 
    close(fd);
 
    return 0;
}

3 组播

广播:无论连接到局域网的客户端想不想接收该数据,Server都会给客户端发送该数据。

进而造成客户端上数据的拥塞,因此引出了组播:Server可以将数据包只发送给指定组内的客户端,而不发送给指定组外的客户端。 

特点:

  1. 可以在internet中进行组播
  2. 加入到广播的组织中才可以收到数据

3.1 组播地址

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

3.2 组播通信流程

服务器端 -> 播的一端: 

- 1.创建通信的套接字
              int fd = socket( af_inet, SOCK_DGRAM, 0);
          - 设置udp组播属性
                  setsockopt();
- 2.通信 -> 发送组播数据
                struct sockaddr_in cliaddr;
                cliaddr.sin_port(8888);    // 广播数据发送到客户端的8888端口, 客户端需要绑定该端口
                cliaddr.sin_addr.s_addr -> 初始化一个组播地址
              发送数据: sendto(fd, buf, len, 0, &cliaddr, len);
- 3.关闭通信的fd
              close(fd);


客户端

- 1.创建一个通信的套接字
                  int fd = socket( af_inet, SOCK_DGRAM, 0);
- 2.如果想接收组播数据, 需要绑定以固定端口(服务器组播数据使用的端口)
                  struct sockaddr_in cliaddr;
                cliaddr.sin_port(8888);    
                  bind(fd, cliaddr, len);
- 3.客户端加入到组播网络中
                  setsockopt():
- 4.通信
              接收数据: recvfrom
- 5.关闭通信的文件描述符
              close();


3.3 设置组播属性函数:setsockopt

这个函数有许多功能,这里只讨论设置组播属性函数功能

int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
  
  // 服务器端 -> 进程组播
  	参数: 
  		- sockfd: 通信的文件描述符
  		- level: IPPROTO_IP
  		- optname: IP_MULTICAST_IF
  		- optval: struct in_addr
          - optlen: optval 的内存大小
  // 客户端 -> 加入到多播组
  	参数:
  		- sockfd: 通信的文件描述符
  		- level: IPPROTO_IP
  		- optname: IP_ADD_MEMBERSHIP
  		- optval: struct ip_mreqn
         - optlen: optval 的内存大小
          
  struct ip_mreqn
  {
  	// 组播组的IP地址.
  	struct in_addr imr_multiaddr; 
  	// 本地某一网络设备接口的IP地址。
  	struct in_addr imr_address;   
  	int   imr_ifindex;   // 网卡编号
  };

3.4 组播代码

服务器:

#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);
    }
 
    // 设置组播属性
    struct in_addr imr_multiaddr; 
    // 初始化组播地址
    inet_pton(AF_INET, "239.0.0.10", &imr_multiaddr.s_addr);
    setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &imr_multiaddr, sizeof(imr_multiaddr));
 
    // 将数据发送给客户端, 使用广播地址和固定端口
    // 初始化客户端的地址信息
    struct sockaddr_in cliaddr;
    cliaddr.sin_family = AF_INET;
    cliaddr.sin_port = htons(8989); // 客户端也需要绑定这端口
    inet_pton(AF_INET, "239.0.0.10", &cliaddr.sin_addr.s_addr);
 
    int num = 0;
    // 3. 通信
    while(1)
    {
        // 接收数据
        char buf[128];
        // 发送数据
        sprintf(buf, "hello, client...%d\n ", num++);
        sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&cliaddr, sizeof(cliaddr));
        printf("组播的数据: %s\n", buf);
        sleep(1);
    }
    close(fd);
    return 0;
}

客户端:

#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. 通信的fd绑定本地的IP和端口
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8989);
    addr.sin_addr.s_addr = INADDR_ANY;
    int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret == -1)
    {
        perror("bind");
        exit(0);
    }
    //inet_pton(AF_INET, "0.0.0.0", &addr.sin_addr.s_addr);
    // 加入到组播
    struct ip_mreqn op;
    op.imr_address.s_addr = INADDR_ANY; // 本地地址
    inet_pton(AF_INET, "239.0.0.10", &op.imr_multiaddr.s_addr);
    op.imr_ifindex = if_nametoindex("ens33");
 
    setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &op, sizeof(op));
    // 3. 通信
    while(1)
    {
        // 接收数据
        char buf[128];
        recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL);
        printf("server say: %s\n", buf);
    }
    close(fd);
    return 0;
}

4. 本地套接字

本地套接字用于进程间通信,有没有血缘关系都可以。通信流程 -> 一般按照tcp流程处理

4.1 结构体sockaddr_un 

结构体 sockaddr、sockaddr_in用于网络通信

结构体 sockaddr_un用于进程间通信

结构体 sockaddr_in用于ipv6通信

由于结构体sockaddr需要用指针偏移添加IP地址,这样很麻烦,在网络通信中我们使用sockaddr_in来添加端口号、IP地址。再强转成sockaddr类型,因为这2个结构体大小一样,后面的服务器—客户端程序会有具体体现。

在进程间通信中,使用sockaddr_un

#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
};

4.2 本地套接字—进程间通信流程

进程1:服务器端

1. 创建监听的套接字
      int lfd = socket(af_local, sock_stream, 0);
          第一个参数: AF_UNIX, AF_LOCAL
2. 监听的套接字绑定本地的 套接字文件-> server端
      struct sockaddr_un addr;
      // 绑定成功之后, 指定的sun_path中的套接字文件会自动生成
      bind(lfd, addr, len);
3. 监听
      listen(lfd, 100);
4. 等待并接受连接请求
      struct sockaddr_un cliaddr;
      int connfd  = accept(lfd, cliaddr, len);
5. 通信
      接收数据: read/recv
      发送数据: write/send
6. 关闭连接
      close();

进程2:客户端

  1. 创建通信的套接字
      int fd = socket(af_local, sock_stream, 0);
  2. 监听的套接字绑定本地的IP 端口
      struct sockaddr_un addr;
      // 绑定成功之后, 指定的sun_path中的套接字文件会自动生成
      bind(fd, addr, len);
  3. 连接服务器
      struct sockaddr_un serveraddr;
      connect(fd, serveraddr, sizeof(serveraddr));
  4. 通信
      接收数据: read/recv
      发送数据: write/send
  5. 关闭连接
      close();

4.3 本地套接字—进程间通代码

进程1:服务器

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/un.h>
 
int main()
{
    unlink("server.sock");
    // 1. 创建监听的套接字
    int lfd = socket(AF_LOCAL, SOCK_STREAM, 0);
    if(lfd == -1)
    {
        perror("socket");
        exit(0);
    }
 
    // 2. 绑定本地套接字文件
    struct sockaddr_un addr;
    addr.sun_family = AF_LOCAL;
    strcpy(addr.sun_path, "server.sock");       //套接字文件是伪文件,会自动生成,名字后缀随便取
    int ret = bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret == -1)
    {
        perror("bind");
        exit(0);
    }
 
    // 3. 监听
    ret = listen(lfd, 100);
    if(ret == -1)
    {
        perror("listen");
        exit(0);
    }
 
    // 4. 等待并接受连接请求
    struct sockaddr_un cliaddr;
    int len = sizeof(cliaddr);
    int connfd = accept(lfd, (struct sockaddr*)&cliaddr, &len);
    if(connfd == -1)
    {
        perror("accept");
        exit(0);
    }
    printf("client socket fileName: %s\n", cliaddr.sun_path);
 
    // 5. 通信
    while(1)
    {
        // 接收数据
        char buf[128];
        int nums = recv(connfd, buf, sizeof(buf), 0);
        if(nums == -1)
        {
            perror("recv");
            exit(0);
        }
        else if(nums == 0)
        {
            printf("client disconnect...\n");
            break;
        }
        else
        {
            printf("client say: %s\n", buf);
            send(connfd, buf, nums, 0);
        }
    }
    close(connfd);
    close(lfd);
    return 0;
}

进程2:客户端

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/un.h>
 
int main()
{
    unlink("client.sock");
    // 1. 创建通信的套接字
    int cfd = socket(AF_LOCAL, SOCK_STREAM, 0);
    if(cfd == -1)
    {
        perror("socket");
        exit(0);
    }
 
    // 2. 绑定本地套接字文件
    struct sockaddr_un addr;
    addr.sun_family = AF_LOCAL;
    strcpy(addr.sun_path, "client.sock");
    // 绑定成功, client.sock会自动生成
    int ret = bind(cfd, (struct sockaddr*)&addr, sizeof(addr));
    if(ret == -1)
    {
        perror("bind");
        exit(0);
    }
 
 
    // 3. 连接服务器
    struct sockaddr_un seraddr;
    seraddr.sun_family = AF_LOCAL;
    strcpy(seraddr.sun_path, "server.sock");
    ret = connect(cfd, (struct sockaddr*)&seraddr, sizeof(seraddr));
    if(ret == -1)
    {
        perror("connect");
        exit(0);
    }
 
    int num = 0;
    // 5. 通信
    while(1)
    {
        // 发送数据 
        char buf[128];
        sprintf(buf, "hello, everyone... %d\n", num++);
        send(cfd, buf, strlen(buf)+1, 0);
        printf("client say: %s\n", buf);
 
        // 接收数据
        int nums = recv(cfd, buf, sizeof(buf), 0);
        if(nums == -1)
        {
            perror("recv");
            exit(0);
        }
        else if(nums == 0)
        {
            printf("server disconnect...\n");
            break;
        }
        sleep(1);
    }
    close(cfd);
    return 0;
}


×
打赏作者
最新回复 (0)
只看楼主
全部楼主
返回