網絡編程學習: 05 UDP相關簡介

關鍵詞總結:UDP

代碼路徑見Github 專欄代碼

UDP 是一種“數據報”協議,而 TCP 是一種面向連接的“數據流”協議。

TCP 是一個面向連接的協議,TCP 在 IP 報文的基礎上,增加了諸如重傳、確認、有序傳輸、擁塞控制等能力,通信的雙方是在一個確定的上下文中工作的。

UDP 沒有這樣一個確定的上下文,它是一個不可靠的通信協議,沒有重傳和確認,沒有有序控制,也沒有擁塞控制。我們可以簡單地理解爲,在 IP 報文的基礎上,UDP 增加的能力有限。

UDP 不保證報文的有效傳遞,不保證報文的有序,也就是說使用 UDP 的時候,我們需要做好丟包、重傳、報文組裝等工作。

UDP 編程

UDP 程序設計時的主要過程。

圖片來自極客時間網絡編程實戰專欄
服務器端: 創建 UDP 套接字 -> 綁定到本地端口 -> 調用 recvfrom 函數等待客戶端的報文發送
客戶端: 創建套接字 -> 調用 sendto 函數往目標地址和端口發送 UDP 報文
客戶端和服務器端進入互相應答過程。

recvfrom 和 sendto

ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags, 
          struct sockaddr *from, socklen_t *addrlen); 

ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags,
                const struct sockaddr *to, socklen_t addrlen); 

recvfrom 函數: sockfd 是本地創建的套接字描述符,buff 指向本地的緩存,nbytes 表示最大接收數據字節, flags 是和 I/O 相關的參數, from 和 addrlen,實際上是返回對端發送方的地址和端口等信息。函數的返回值爲實際接收的字節數。

sendto 函數:sockfd 是本地創建的套接字描述符,buff 指向發送的緩存,nbytes 表示發送字節數,flags 是和 I/O 相關的參數, to 和 addrlen,表示發送的對端地址和端口等信息。函數的返回值爲實際發送的字節數。

TCP 的發送和接收每次都是在一個上下文中:
A 連接上: 接收→發送→接收→發送→…
B 連接上: 接收→發送→接收→發送→ …

UDP 的每次接收和發送都是一個獨立的上下文

接收 A→發送 A→接收 B→發送 B →接收 C→發送 C→ …

UDP 服務端例子

#include <stdio.h>
#include <sys/socket.h>
#include <unistd.h>
#include <errno.h>
#include <netinet/in.h>
#include <string.h>
#include <signal.h>

# define MAXLINE 4096
static int count;

static void recvfrom_int(int signo) {
    printf("\nreveived %d datagrams\n", count);
}

int main(int argc, char **argv) {
    int socket_fd;
    // 創建 UDP 套接字
    socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
    struct sockaddr_in server_addr;
    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(12345);
	// 綁定到本地端口 
    bind(socket_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));

    socklen_t client_len;
    char message[MAXLINE];
    count = 0;
	// 響應“Ctrl+C”退出時
    signal(SIGINT, recvfrom_int);
    
    struct sockaddr_in client_addr;
    client_len = sizeof(client_addr);
    for(;;) {
    	// 用 recvfrom 函數獲取客戶端發送的報文
        int n = recvfrom(socket_fd, message, MAXLINE, 0, (struct sockaddr *) &client_addr, &client_len);
        message[n] = 0; // 加上結束符
        printf("receive %d bytes: %s\n", n, message);
    
        char send_line[MAXLINE];
        // 加上“Hi”的前綴
        sprintf(send_line, "Hi, %s", message);
		// 通過 sendto 函數發送給客戶端對端。
        sendto(socket_fd, send_line, strlen(send_line), 0, (struct sockaddr *) &client_addr, client_len);

        count ++;
    }
}

### UDP 客戶端例子

#include <stdio.h>
#include <sys/socket.h>
#include <unistd.h>
#include <errno.h>
#include <netinet/in.h>
#include <string.h>
#include <signal.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#define    MAXLINE     4096

int main(int argc, char **argv) {
    if (argc != 2) {
        perror("usage: udpclient <IPaddress>");
    }
    // 創建套接字
    int socket_fd;
    socket_fd = socket(AF_INET, SOCK_DGRAM, 0);

    struct sockaddr_in server_addr;
    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(12345);
    inet_pton(AF_INET, argv[1], &server_addr.sin_addr);

    socklen_t server_len = sizeof(server_addr);

    struct sockaddr *reply_addr;
    reply_addr = (struct sockaddr *)malloc(server_len);

    char send_line[MAXLINE], recv_line[MAXLINE + 1];
    socklen_t len;
    int n;
	// 從標準輸入中讀取的字符, fgets函數加上\0
    while (fgets(send_line, MAXLINE, stdin) != NULL) {
        int i = strlen(send_line);
        if (send_line[i - 1] == '\n') {
            send_line[i - 1] = 0;
        }

        printf("now sending %s\n", send_line);
        // 調用 sendto 函數發送給目標服務器端
        size_t rt = sendto(socket_fd, send_line, strlen(send_line), 0, (struct sockaddr *) &server_addr, server_len);
        if (rt < 0) {
            perror("send failed ");
        }
        printf("send bytes: %zu \n", rt);

        len = 0;
        n = recvfrom(socket_fd, recv_line, MAXLINE, 0, reply_addr, &len);
        if (n < 0)
            perror("recvfrom failed");
        recv_line[n] = 0;
        fputs(recv_line, stdout);
        fputs("\n", stdout);
    }

    return 0;
}

三種運行場景

場景一:只運行客戶端

只運行客戶端,程序會一直阻塞在 recvfrom 上。
在這裏插入圖片描述
輸入123,打印後續輸出信息,這時程序會一直阻塞在 recvfrom,顧當再次輸入321時程序沒反應。
TCP 程序如果不開啓服務端,TCP 客戶端的 connect 函數會直接返回“Connection refused”報錯信息。而在 UDP 程序裏,則會一直阻塞在這裏。

場景二:先開啓服務端,再開啓客戶端

先開啓服務端在端口偵聽,然後再開啓客戶端:
在這裏插入圖片描述

場景三: 開啓服務端,再一次開啓兩個客戶端

在服務端開啓之後,依次開啓兩個客戶端,併發送報文
在這裏插入圖片描述
服務器端重啓後可以繼續收到客戶端的報文,這在 TCP 裏是不可以的,TCP 斷聯之後必須重新連接纔可以發送報文信息。但是 UDP 報文的”無連接“的特點,可以在 UDP 服務器重啓之後,繼續進行報文的發送。
在這裏插入圖片描述

參考資料:

網絡編程實戰(極客時間)鏈接:
http://gk.link/a/10g9X


GitHub鏈接:
https://github.com/lichangke/LeetCode
CSDN首頁:
https://me.csdn.net/leacock1991
歡迎大家來一起交流學習

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