【網路】UDP伺服器的實現
UDP伺服器實現的基本步驟
(1)利用socket函式建立套接字
(2)伺服器用bind進行IP和埠號的繫結
(3)不需要設定監聽狀態
(4)繫結後直接讀寫
相關函式
recvfrom
作用
用來資料的接收
標頭檔案
#include<sys/types.h>
#include<sys/socket.h>
函式原型
int recvfrom(int sockfd, void* buf, ssize_t len, int flags, struct sockaddr* src_addr,socklen_t *addrlen);
引數
sockfd表示當前的套接字
buf表示要接收的資料指標
len表示接收的長度
flags引數設定為0
src_addr表示傳送方的協議地址
addrlen表示src_addr的大小
返回值
成功返回0
錯誤返回-1
sendto
作用
用來進行資料的傳送
標頭檔案
#include<sys/types.h>
#include<sys/socket.h>
函式原型
int sendto(int sockfd, void* buf , ssize_t len, int flags, struct sockaddr * src_addr, socklen_t addrlen);
引數
sockfd表示當前的套接字
buf表示要傳送的資料指標
len表示要傳送的資料長度
flags設定為0
src_addr為傳送方的協議地址
addrlen為src_addr的大小
返回值
成功返回0錯誤返回-1
程式碼實現
server.c
#include<stdio.h> #include<stdlib.h> #include<error.h> #include<unistd.h> #include<sys/types.h> #include<sys/socket.h> #include<netinet/in.h> #include<string.h> static void Usage() { printf("Usage: [ipaddr] [port]\n"); exit(1); } int main(int argc, char* argv[]) { if(argc != 3) { Usage(); } //建立socket int sockfd = socket(AF_INET,SOCK_DGRAM,0); if(sockfd < 0) { perror("socket"); exit(4); } struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(atoi(argv[2])); addr.sin_addr.s_addr = inet_addr(argv[1]); //進行繫結 if(bind(sockfd,(struct sockaddr*)&addr,sizeof(addr)) < 0) { perror("bind"); exit(2); } char buf[1024]; //收發資料 while(1) { struct sockaddr_in client; socklen_t clientLen = sizeof(client); ssize_t s = recvfrom(sockfd, buf ,sizeof(buf)-1,0,(struct sockaddr*)&client,&clientLen); if(s < 0) { perror("recvfrom"); exit(3); } else if(s == 0) { continue; } else { buf[s] = 0; printf("IP: %s , Port : %d : %s\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port),buf); sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&client,clientLen); } } close(sockfd); return 0; }
client.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<error.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
static Usage()
{
printf("Usage: [ipaddr] [port]\n");
exit(1);
}
int main(int argc,char* argv[])
{
if(argc != 3)
{
Usage();
}
//建立socket
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd < 0)
{
perror("sockfd");
exit(2);
}
printf("請輸入...\n");
char buf[1024];
//進行寫和讀
while(1)
{
printf("#client : ");
fflush(stdout);
ssize_t s = read(0,buf,sizeof(buf)-1);
if(s < 0)
{
perror("read");
exit(3);
}
buf[s-1] = '\0';
//進行傳送
struct sockaddr_in client;
client.sin_family = AF_INET;
client.sin_port = htons(atoi(argv[2]));
client.sin_addr.s_addr = inet_addr(argv[1]);
s = sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&client,sizeof(client));
if(s < 0)
{
perror("sendto");
exit(4);
}
struct sockaddr_in server;
socklen_t len = sizeof(server);
s = recvfrom(sockfd,buf,sizeof(buf)-1,0,(struct sockaddr*)&server,&len);
if(s < 0)
{
perror("recvfrom");
exit(5);
}
//正常收到
buf[s] = 0;
printf("IP : %s , Port : %d : %s\n",inet_ntoa(server.sin_addr),ntohs(server.sin_port),buf);
}
close(sockfd);
return 0;
}
執行結果
客戶端傳送訊息
伺服器接受訊息
如何保證UDP的可靠性
傳輸層無法保證資料的可靠傳輸,只能通過應用層來實現了。
實現的方式可以參照tcp可靠性傳輸的方式,只是實現不在傳輸層,實現轉移到了應用層。
實現確認機制、重傳機制、視窗確認機制。
除了利用Linux協議棧以及上層socket機制之外,自己可以通過抓包和發包的方式去實現可靠性傳輸
需要實現如下功能:
傳送:包的分片、包確認、包的重發
接收:包的調序、包的序號確認
目前有如下開源程式利用udp實現了可靠的資料傳輸
分別為RUDP、RTP、UDT。
方法1:RUDP
RUDP 提供一組資料服務質量增強機制,如擁塞控制的改進、重發機制及淡化伺服器演算法等,從而在包丟失和網路擁塞的情況下, RTP 客戶機(實時位置)面前呈現的就是一個高質量的 RTP 流。
在不干擾協議的實時特性的同時,可靠 UDP 的擁塞控制機制允許 TCP 方式下的流控制行為。
方法2:RTP
實時傳輸協議(RTP)為資料提供了具有實時特徵的端對端傳送服務,如在組播或單播網路服務下的互動式視訊音訊或模擬資料。應用程式通常在 UDP 上執行 RTP 以便使用其多路結點和校驗服務;
這兩種協議都提供了傳輸層協議的功能。但是 RTP 可以與其它適合的底層網路或傳輸協議一起使用
如果底層網路提供組播方式,那麼 RTP 可以使用該組播表傳輸資料到多個目的地。
RTP 本身並沒有提供按時傳送機制或其它服務質量(QoS)保證,它依賴於底層服務去實現這一過程。
RTP 並不保證傳送或防止無序傳送,也不確定底層網路的可靠性。
RTP 實行有序傳送, RTP 中的序列號允許接收方重組傳送方的包序列,同時序列號也能用於決定適當的包位置,例如:在視訊解碼中,就不需要順序解碼。
方法3:UDT
基於UDP的資料傳輸協議(UDP-basedData Transfer Protocol,簡稱UDT)是一種網際網路資料傳輸協議。
UDT的主要目的是支援高速廣域網上的海量資料傳輸,而網際網路上的標準資料傳輸協議TCP在高頻寬長距離網路上效能很差。
顧名思義,UDT建於UDP之上,並引入新的擁塞控制和資料可靠性控制機制。
UDT是面向連線的雙向的應用層協議。它同時支援可靠的資料流傳輸和部分可靠的資料報傳輸。
由於UDT完全在UDP上實現,它也可以應用在除了高速資料傳輸之外的其它應用領域,例如點到點技術(P2P),防火牆穿透,多媒體資料傳輸等等。