《Linux網路開發必學教程》15_UDP 資料收發實戰

TianSong 發表於 2022-05-16
Linux
問題:如何使用 UDP 進行資料收發?

再論 UDP 協議

  • UDP 是無連線的(不可靠的,無應答訊息,資料包無序號標識
  • UDP 是面向資料包的,對應用層資料既不合並也不拆分(保留資料包邊界)
  • UDP 沒有擁塞控制,網路出現的擁塞不會使源主機的傳送速率降低
  • UDP 支援一對一,一對多,多對一和多對多的互動通訊
  • UDP 訊息頭開銷小,只有 8 個位元組(TCP 訊息頭共 20 個位元組)

image.png

UDP 和 IP 的區別

UDP 是建立於 IP 之上的資料傳輸協議
  • IP 負責將 UDP 資料包從源主機傳輸到目標主機
  • UDP 則將應用層資料投遞到目標 socket (埠號)
UDP 幾乎完整 “繼承” 了 IP 傳輸的特性
  • 通訊兩端無互動,無流控,無超時重發,不具備可靠性

UDP 收發資料

#include "sys/socket.h"

ssize_t send(int sock,                // socket 檔案描述符
             void *buf,               // 需要傳送的資料
             size_t nbytes,           // 需要傳送的資料量
             int flags,               // 傳送選項
             struct sockaddr *to,     // 接收地址資訊
             socklen_t addrlen);      // to 引數長度

ssize_t recvfrom(int sock,            // socket 檔案描述符
                 void *buf,            // 儲存接收資料的地址
                 size_t nbytes,        // 可接收的最大資料量
                 int flags,            // 接收選項
                 struct sockaddr *from, // 接收地址資訊
                 socklen_t *addrlen);    // 指向儲存 from 引數長度的變數地址

UDP 程式設計模式

image.png

程式設計實驗:UDP 資料收發

client.c
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main()
{
    int sock = 0;
    struct sockaddr_in addr = {0};
    struct sockaddr_in remote = {0};
    socklen_t len = 0;
    char buf[128] = {0};
    char input[32] = {0};
    int r = 0;

    sock = socket(PF_INET, SOCK_DGRAM, 0);

    if (sock == -1) {
        printf("socket error\n");
        return -1;
    }

    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_port = htons(7777);

    if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1) {  // 特別說明, 當使用 udp socket 進行資料傳送時,不指定 bind (即,ip 和 port) 也能將資料成功傳送
        printf("udp bind error\n");                                 // 但 udp 是無連線的,因此不像 TCP 有非常明顯的服務端等待客戶端發起連線的過程 
        return -1;                                                  // udp 的通訊端點是對等的,為了方便接收其他端點傳送的資料,因此指定了 ip 和 埠號 (尤其埠號)
    }

    remote.sin_family = AF_INET;
    remote.sin_addr.s_addr = inet_addr("127.0.0.1");
    remote.sin_port = htons(8888);

    while (1) {
        printf("Input: ");

        scanf("%s", input);

        len = sizeof(remote);

        sendto(sock, input, strlen(input), 0, (struct sockaddr*)&remote, len);

        r = recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr*)&remote, &len);

        if (r > 0) {
            buf[r] = 0;
            printf("Recvfrom %s\n", buf);
        } else {
            break;
        }
    }

    close(sock);

    return 0;
}
server.c
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main()
{
    int server = 0;
    struct sockaddr_in saddr = {0};
    int client = 0;
    struct sockaddr_in remote = {0};
    socklen_t asize = 0;
    int len = 0;
    char buf[32] = {0};
    int r = 0;

    server = socket(PF_INET, SOCK_DGRAM, 0);

    if (server == -1) {
        printf("server socket error");
        return -1;
    }

    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = htonl(INADDR_ANY);
    saddr.sin_port = htons(8888);

    if (bind(server, (struct sockaddr*)&saddr, sizeof(saddr)) == -1) {
        printf("udp server bind error\n");
        return -1;
    }

    printf("udp server start sucess\n");

    while (1) {
        len = sizeof(remote);

        r = recvfrom(server, buf, sizeof(buf), 0, (struct sockaddr*)&remote, &len);

        buf[r] = 0;

        printf("r = %d\n", r);
        printf("buf = %s\n", buf);
        printf("remote ip = %s\n", inet_ntoa(remote.sin_addr));
        printf("remote port = %d\n", ntohs(remote.sin_port));

        sendto(server, buf, r, 0, (struct sockaddr*)&remote, len);
    }

    close(server);

    return 0;
}
輸出:
server:
udp server start sucess
r = 5
buf = hello
remote ip = 127.0.0.1
remote port = 7777

---

client:
Input: hello
Recvfrom hello

思考:如何進行一對多的 UDP 資料收發?