udp是一個面向無連接的,不安全的,報式傳輸層協議,udp的通信過程默認也是阻塞的。使用UDP進行通信,服務器和客戶端的處理步驟比TCP要簡單很多,并且兩端是對等的 (通信的處理流程幾乎是一樣的),也就是說并沒有嚴格意義上的客戶端和服務器端。
UDP通信不需要建立連接 ,因此不需要進行connect()操作,在通信過程中,每次都需要指定數據接收端的IP和端口。UDP不對收到的數據進行排序,在UDP報文的首部中并沒有關于數據順序的信息。
UDP對接收到的數據報不回復確認信息,發送端不知道數據是否被正確接收,也不會重發數據。如果發生了數據丟失,不存在丟一半的情況,如果丟當前這個數據包就全部丟失了?
UDP通信過程雖然默認還是阻塞的,但是通信函數和TCP不同,操作函數原型如下:
// 接收數據, 如果沒有數據,該函數阻塞
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
參數:
- sockfd: 基于udp的通信的文件描述符
- buf: 指針指向的地址用來存儲接收的數據
- len: buf指針指向的內存的容量, 最多能存儲多少字節
- flags: 設置套接字屬性,一般使用默認屬性,指定為0即可
- src_addr: 發送數據的一端的地址信息,IP和端口都存儲在這里邊, 是大端存儲的,如果這個參數中的信息對當前業務處理沒有用處, 可以指定為NULL, 不保存這些信息
- addrlen: 類似于accept() 函數的最后一個參數, 是一個傳入傳出參數,傳入的是src_addr參數指向的內存的大小, 傳出的也是這塊內存的大小。如果src_addr參數指定為NULL, 這個參數也指定為NULL即可
- 返回值:成功返回接收的字節數,失敗返回-1
// 發送數據函數
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
參數:
- sockfd: 基于udp的通信的文件描述符
- buf: 這個指針指向的內存中存儲了要發送的數據
- len: 要發送的數據的實際長度
- flags: 設置套接字屬性,一般使用默認屬性,指定為0即可
- dest_addr: 接收數據的一端對應的地址信息, 大端的IP和端口
- addrlen: 參數 dest_addr 指向的內存大小
- 返回值:函數調用成功返回實際發送的字節數,調用失敗返回-1?
服務端代碼示例:
在UDP通信過程中,服務器和客戶端都可以作為數據的發送端和數據接收端,假設服務器端是被動接收數據,客戶端是主動發送數據,那么在服務器端就必須綁定固定的端口了。
#include <iostream>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>int main() {int fd = socket(AF_INET, SOCK_DGRAM, 0);if (fd == -1) {perror("socket");return -1;}// 初始化服務器地址sockaddr_in server_addr;server_addr.sin_family = AF_INET;server_addr.sin_port = htons(9999); server_addr.sin_addr.s_addr = INADDR_ANY;if (bind(fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {perror("bind failed");exit(EXIT_FAILURE);
}char buffer[8888];sockaddr_in client_addr;socklen_t client_addr_len = sizeof(client_addr);while (1) {// 接收客戶端數據ssize_t recv_len = recvfrom(fd, buffer, sizeof(buffer)-1, 0,(struct sockaddr*)&client_addr,&client_addr_len);if (recv_len == -1) {perror("recvfrom failed");continue;}buffer[recv_len] = '\0'; // 確保字符串終止// 打印客戶端信息char client_ip[INET_ADDRSTRLEN];inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);std::cout << "Client (" << client_ip << ":" << ntohs(client_addr.sin_port) << "): " << buffer << std::endl;// 構造響應(添加前綴)char prefix[] = "server say:";char response[sizeof(buffer)];sprintf(response, "%s%s", prefix, buffer);// 發送響應(使用正確的長度)ssize_t send_len = sendto(fd, response, strlen(response), 0,(const struct sockaddr*)&client_addr,client_addr_len);if (send_len == -1) {perror("sendto failed");continue;}}close(fd);return 0;
}
作為數據接收端,服務器端通過bind()函數綁定了固定的端口,然后基于這個固定的端口通過recvfrom()函數接收客戶端發送的數據,同時通過這個函數也得到了數據發送端的地址信息(recvfrom的第三個參數),這樣就可以通過得到的地址信息通過sendto()函數給客戶端回復數據了。
客戶端代碼示例:
#include <iostream>
#include <arpa/inet.h>
#include <string>
#include <unistd.h>
#include <pthread.h> int main() {int fd = socket(AF_INET, SOCK_DGRAM, 0);if (fd == -1) {perror("socket");return -1;}// 初始化服務器地址sockaddr_in server_addr;server_addr.sin_family = AF_INET;server_addr.sin_port = htons(9999); inet_pton(AF_INET, "192.168.175.130", &server_addr.sin_addr); int num = 0;std::string t = std::to_string(pthread_self());std::string s = ":Hello World!!!";while (1) {std::string S = t + s + std::to_string(++num);// 發送數據到服務器ssize_t send_len = sendto(fd, S.data(), S.size(), 0,(const struct sockaddr*)&server_addr,sizeof(server_addr));if (send_len == -1) {perror("sendto failed");continue;}// 接收服務器響應sockaddr_in server_response_addr;socklen_t addr_len = sizeof(server_response_addr);char buffer[1024] = {0};ssize_t recv_len = recvfrom(fd, buffer, sizeof(buffer)-1, 0,(struct sockaddr*)&server_response_addr,&addr_len);if (recv_len == -1) {perror("recvfrom failed");continue;}// 打印服務器地址和響應char str[INET_ADDRSTRLEN];std::cout << "Server (" << inet_ntop(AF_INET, &server_response_addr.sin_addr, str, INET_ADDRSTRLEN)<< ":" << ntohs(server_response_addr.sin_port) << "): " << buffer << std::endl;sleep(1);}close(fd);return 0;
}
作為數據發送端,客戶端不需要綁定固定端口,客戶端使用的端口是隨機綁定的(也可以調用bind()函數手動進行綁定)。
同時開啟三個客戶端一個服務端運行結果:
?
UDP是一種無連接的傳輸層協議,其特性天然支持多客戶端同時通信,服務器不需要與每個客戶端建立持久連接,只需接收來自不同源地址的數據包,每個客戶端發送的 UDP 數據包都是獨立的,服務器可以逐個處理,每個數據包都包含發送方的 IP 和端口信息,服務器可以據此區分不同客戶端。
所以可以啟動多個客戶端與服務器進行通信,UDP 協議的特性會確保每個客戶端的請求被正確路由和處理。