c/c++:UDP(udp通信、廣播、組播),本地套接字

目錄

1. udp

1.1 udp通信流程

1.2 操作函數

send、sendto

recv、recvfrom

2. 廣播

2.1 廣播通信流程

2.2 設置廣播屬性函數:setsockopt

2.3 廣播代碼

3 組播

3.1 組播地址

3.2 組播通信流程

3.3 設置組播屬性函數:setsockopt

3.4 組播代碼

4. 本地套接字

4.1 結構體sockaddr_un 

4.2 本地套接字—進程間通信流程

4.3 本地套接字—進程間通代碼


 

 

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

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章