加入星計(jì)劃,您可以享受以下權(quán)益:

  • 創(chuàng)作內(nèi)容快速變現(xiàn)
  • 行業(yè)影響力擴(kuò)散
  • 作品版權(quán)保護(hù)
  • 300W+ 專業(yè)用戶
  • 1.5W+ 優(yōu)質(zhì)創(chuàng)作者
  • 5000+ 長期合作伙伴
立即加入
  • 正文
    • 1. UDP通信流程
    • 2. UDP客戶端與服務(wù)端實(shí)現(xiàn)
    • 3. 心跳包
    • 4. TCP/UDP應(yīng)用場景
  • 相關(guān)推薦
  • 電子產(chǎn)業(yè)圖譜
申請入駐 產(chǎn)業(yè)圖譜

UDP通信機(jī)制詳解

12小時(shí)前
208
閱讀需 12 分鐘
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點(diǎn)資訊討論

1. UDP通信流程

前面介紹了TCP,TCP是面向連接的、安全的、流式傳輸協(xié)議。UDP是面向無連接的、不安全的、報(bào)式傳輸協(xié)議。UDP通信流程如下:

服務(wù)器端:

創(chuàng)建套接字 - socket

第二個(gè)參數(shù)使用SOCK_DGRAM,表示報(bào)式協(xié)議,即UDP。TCP使用的是SOCK_STREAM。

綁定IP和端口:bind

fd

struct sockaddr ?—— 服務(wù)器

通信

接收數(shù)據(jù):recvfrom

?ssize_t?recvfrom(int?sockfd,?void?*buf,?size_t?len,?int?flags,struct?sockaddr *src_addr,?socklen_t?*addrlen);

同accept的第2、3個(gè)參數(shù)使用方法相同。

sockfd:文件描述符

buf:接收數(shù)據(jù)緩沖區(qū)

len:buf的最大容量

flags:0

src_addr:另一端的IP和端口, 傳出參數(shù)

addrlen:傳入傳出參數(shù)

發(fā)送數(shù)據(jù): sendto

?ssize_t?sendto(int?sockfd,?const?void?*buf,?size_t?len,?int?flags,?const?struct?sockaddr *dest_addr,?socklen_t?addrlen);

sockfd:socket函數(shù)創(chuàng)建出來的

buf:存儲發(fā)送的數(shù)據(jù)

len:發(fā)送的數(shù)據(jù)的長度,注意是發(fā)送數(shù)據(jù)的大小,而不是buf的大小 strlen()

flags:0

dest_addr:另一端的IP和端口

addrlen:dest_addr長度

UDP服務(wù)器端:需要一個(gè)套接字, 通信

客戶端:

創(chuàng)建一個(gè)用于通信的套接字:socket

通信

發(fā)送數(shù)據(jù):sendto,如果發(fā)送的數(shù)據(jù)太大,sendto會(huì)調(diào)用失敗,UDP報(bào)文的長度是有上限的。

需要先準(zhǔn)備好一個(gè)結(jié)構(gòu)體:struct sockaddr_in

存儲服務(wù)器的IP和端口

接收數(shù)據(jù):recvform

udp的數(shù)據(jù)是不安全的, 容易丟包

丟包, 丟全部還一部分?

只能丟全部,不存在只丟一部分的情況

優(yōu)點(diǎn): 效率高

UDP通信流程示意圖如下

2. UDP客戶端與服務(wù)端實(shí)現(xiàn)

server

#include?<stdio.h>#include?<unistd.h>#include?<stdlib.h>#include?<sys/types.h>#include?<sys/stat.h>#include?<string.h>#include?<arpa/inet.h>

int main(int argc, const char* argv[]){? ??// 創(chuàng)建套接字? ? int fd = socket(AF_INET, SOCK_DGRAM, 0);? ? if(fd == -1)? ? {? ? ? ? perror("socket error");? ? ? ? exit(1);? ? }? ??? ? // fd綁定本地的IP和端口? ? struct sockaddr_in serv;? ? memset(&serv, 0, sizeof(serv));? ? serv.sin_family = AF_INET;? ? serv.sin_port = htons(8765);? ? serv.sin_addr.s_addr = htonl(INADDR_ANY);? ? int ret = bind(fd, (struct sockaddr*)&serv, sizeof(serv));? ? if(ret == -1)? ? {? ? ? ? perror("bind error");? ? ? ? exit(1);? ? }

? ? struct sockaddr_in client;? ? socklen_t cli_len = sizeof(client);? ? // 通信? ? char buf[1024] = {0};? ? while(1)? ? {? ? ? ? int recvlen = recvfrom(fd, buf, sizeof(buf), 0,?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?(struct sockaddr*)&client, &cli_len);? ? ? ? if(recvlen == -1)? ? ? ? {? ? ? ? ? ? perror("recvform error");? ? ? ? ? ? exit(1);? ? ? ? }? ? ? ??? ? ? ? printf("recv buf: %sn", buf);? ? ? ? char ip[64] = {0};? ? ? ? printf("New Client IP: %s, Port: %dn",? ? ? ? inet_ntop(AF_INET, &client.sin_addr.s_addr, ip, sizeof(ip)),? ? ? ? ntohs(client.sin_port));

? ? ? ? // 給客戶端發(fā)送數(shù)據(jù)? ? ? ? sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&client, sizeof(client));? ? }? ??? ? close(fd);

? ? return 0;}

 

client

#include?<stdio.h>#include?<unistd.h>#include?<stdlib.h>#include?<sys/types.h>#include?<sys/stat.h>#include?<string.h>#include?<arpa/inet.h>

int main(int argc, const char* argv[]){? ??// create socket? ? int fd = socket(AF_INET, SOCK_DGRAM, 0);? ? if(fd == -1)? ? {? ? ? ? perror("socket error");? ? ? ? exit(1);? ? }

? ? // 初始化服務(wù)器的IP和端口? ? struct sockaddr_in serv;? ? memset(&serv, 0, sizeof(serv));? ? serv.sin_family = AF_INET;? ? serv.sin_port = htons(8765);? ? inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);? ? //把點(diǎn)分十進(jìn)制字符串 "127.0.0.1" 轉(zhuǎn)成整形,并存到&serv.sin_addr.s_addr中

? ? // 通信? ? while(1)? ? {? ? ? ? char buf[1024] = {0};? ? ? ? fgets(buf, sizeof(buf), stdin);? ? ? ? // 數(shù)據(jù)的發(fā)送 - server - IP port? ? ? ? sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&serv, sizeof(serv));

? ? ? ? // 等待服務(wù)器發(fā)送數(shù)據(jù)過來? ? ? ? recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL);?? ? ? ? //服務(wù)器的IP和port已經(jīng)在在初始化的時(shí)候知道了,傳NULL即可? ? ? ? printf("recv buf: %sn", buf);? ? }? ??? ? close(fd);

? ? return 0;}

 

3. 心跳包

UDP是無連接的通信協(xié)議,那么如何去判斷客戶端和服務(wù)端是否處于連接狀態(tài)呢?這就是心跳機(jī)制:

心跳機(jī)制

不會(huì)攜帶大量的數(shù)據(jù)

每隔一定時(shí)間 服務(wù)器 → 客戶端 / 客戶端→服務(wù)器 發(fā)送一個(gè)數(shù)據(jù)包

心跳包看成一個(gè)協(xié)議

應(yīng)用層協(xié)議

判斷網(wǎng)絡(luò)是否斷開

有多個(gè)連續(xù)的心跳包沒收到或沒有回復(fù)

關(guān)閉通信的套接字

重連

重新初始套接字

繼續(xù)發(fā)送心跳包

乒乓包

比心跳包攜帶的數(shù)據(jù)多一些

除了知道連接是否存在,還能獲取一些信息

如何理解心跳包呢——比如說,坐火車過隧道的時(shí)候,微信會(huì)提示服務(wù)器已斷開連接,通過隧道后,微信會(huì)自己連上服務(wù)器。微信是如何知道和服務(wù)器斷開連接了呢?就是通過心跳包機(jī)制。

比如,提前約定好,每隔多少秒客服端向服務(wù)器發(fā)1,如果服務(wù)器收到1,則回復(fù)客戶端2,客戶端收到2再發(fā)1,如此循環(huán)。如果客戶端發(fā)送完1沒有收到服務(wù)器回復(fù)的2,那么客戶端將會(huì)再次發(fā)送1,測試幾次如果依然沒有回復(fù),那么客戶端會(huì)提示“服務(wù)器已斷開連接”。

心跳包只能判斷有沒有連接,而乒乓包可以攜帶一些數(shù)據(jù)。

乒乓包——比如微信,如果有人給你發(fā)消息,或者有人發(fā)布朋友圈,給你點(diǎn)贊,都會(huì)有一個(gè)小紅點(diǎn)提示,那么你的手機(jī)微信是怎么知道有人給你點(diǎn)贊,有人發(fā)朋友圈的呢?就是通過乒乓包不停的去詢問。但是乒乓包也不是能攜帶所有數(shù)據(jù),我們看到小紅點(diǎn),得點(diǎn)進(jìn)去,然后手機(jī)客戶端向服務(wù)器請求數(shù)據(jù)才能看到具體發(fā)了啥內(nèi)容,評論了啥內(nèi)容。心跳包只能判斷是否連接,乒乓包可以攜帶少量提示信息。

4. TCP/UDP應(yīng)用場景

TCP使用場景

數(shù)據(jù)安全性要求高的時(shí)候

登錄數(shù)據(jù)的傳輸 —— 比如用戶名密碼

文件傳輸

HTTP協(xié)議

傳輸層協(xié)議 —— TCP

UDP使用場景

效率高 —— 實(shí)時(shí)性要求比較高

視頻聊天、直播

通話

有實(shí)力的大公司

使用UDP

在應(yīng)用層自定義協(xié)議來做數(shù)據(jù)校驗(yàn),既保證了傳輸效率,又保證了不丟數(shù)據(jù)。

相關(guān)推薦

電子產(chǎn)業(yè)圖譜

Linux、C、C++、Python、Matlab,機(jī)器人運(yùn)動(dòng)控制、多機(jī)器人協(xié)作,智能優(yōu)化算法,貝葉斯濾波與卡爾曼濾波估計(jì)、多傳感器信息融合,機(jī)器學(xué)習(xí),人工智能。