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ù)。