網路程式設計——6. 基於UDP的伺服器端/客戶端
阿新 • • 發佈:2018-12-20
6.1 理解UDP
UDP套接字的特點
跟寄信一樣,我寫好名字和地址,貼上郵票寄出去就好了。 郵寄過程的丟失或者損壞我都沒辦法保證,是一種不可靠的傳輸方式。但相比TCP,雖然可靠性差一些,但比TCP簡潔一些,速度也更快一些(在每次交換的資料量越小的情況下)。。
TCP和UDP的差異只在於流控制機制:TCP在不可靠的IP層進行流控制,而UDP缺少這種流控制機制。
6.2 實現基於UDP的伺服器端/客戶端
UDP中的伺服器端和客戶端沒有連線
與TCP不同,無需經過連線過程即可交換資料 (UDP中只有建立套接字過程和資料交換過程)
UDP伺服器端和客戶端都只需1個套接字
TCP中,套接字之間是1對1的關係。若要向10個客戶端提供服務,除了守門的伺服器套接字外,還需要10個伺服器端套接字
把郵筒比作套接字,只要附近有1個郵筒,就能通過它向任意地址寄出信件。 也就是說,只要1個UDP套接字就能向任意主機傳輸資料。
基於UDP的資料IO函式
建立好TCP套接字後,傳輸資料時就不用再新增地址資訊。因為TCP套接字將保持與對方套接字的連線。
但UDP套接字不會保持連線狀態,因此每次傳輸資料都需要新增目標地址資訊(相當於寄信前在信件中填地址)。
可以看到,sendto函式與TCP輸出函式的最大區別在於,此函式需要向它傳遞目標地址資訊。 上述就是UDP程式最核心的部分。
基於UDP的回聲伺服器端/客戶端
UDP不同於TCP,不存在請求連線和受理過程,因此在某種意義上沒辦法去明確區分伺服器端和客戶端。
1)伺服器端
// uecho_server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 30
void error_handling(char *message);
int main(int argc, char *argv[]){
int serv_sock;
char message[ BUF_SIZE];
int str_len;
struct sockaddr_in serv_adr;
struct sockaddr_in clnt_adr;
socklen_t clnt_addr_sz;
if(argc != 2){
printf("Usage : %s <port>\n",argv[0]);
exit(1);
}
// 建立UDP套接字,向socket函式第二個引數傳遞SOCK_DGRAM
serv_sock = socket(PF_INET,SOCK_DGRAM,0);
if(serv_sock == -1){
error_handling("socket() error");
}
memset(&serv_adr,0,sizeof(serv_adr));
serv_adr.sin_family=AF_INET;
serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
serv_adr.sin_port=htons(atoi(argv[1]));
if(bind(serv_sock,(struct sockaddr*)&serv_adr,sizeof(serv_adr)) == -1){
error_handling("bind() error");
}
while(1){
clnt_addr_sz = sizeof(clnt_adr);
// 利用bind分配的地址接收資料,不限制資料傳輸物件
str_len = recvfrom(serv_sock,message,BUF_SIZE,0,(struct sockaddr*)&clnt_adr,&clnt_addr_sz);
// 通過上面獲得的資料傳輸端的地址,來將接收的資料逆向重傳
sendto(serv_sock,message,str_len,0,(struct sockaddr*)&clnt_adr,clnt_addr_sz);
}
close(serv_sock);
return 0;
}
void error_handling(char *message){
fputs(message,stderr);
fputs("\n",stderr);
exit(1);
}
2)客戶端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 30
void error_handling(char *message);
int main(int argc, char *argv[]){
int sock;
char message[BUF_SIZE];
int str_len;
socklen_t adr_sz;
struct sockaddr_in serv_adr, from_adr;
if(argc != 3){
printf("Usage : %s <IP> <port>\n",argv[0]);
exit(1);
}
sock = socket(PF_INET,SOCK_DGRAM,0);
if(sock == -1){
error_handling("socket() error");
}
memset(&serv_adr,0,sizeof(serv_adr));
serv_adr.sin_family=AF_INET;
serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
serv_adr.sin_port=htons(atoi(argv[2]));
while(1){
fputs("Insert message(q to quit):",stdout);
fgets(message,sizeof(message),stdin);
if(!strcmp(message,"q\n") || !strcmp(message,"Q\n"))
break;
// 向伺服器端一次性發送完畢(不保證完全接收)
sendto(sock,message,strlen(message),0,(struct sockaddr *)&serv_adr,sizeof(serv_adr));
adr_sz = sizeof(from_adr);
str_len = recvfrom(sock,message,BUF_SIZE,0,(struct sockaddr *)&from_adr,&adr_sz);
message[str_len] = 0;
printf("Message from server: %s",message);
}
close(sock);
return 0;
}
void error_handling(char *message){
fputs(message,stderr);
fputs("\n",stderr);
exit(1);
}
UDP客戶端套接字的地址分配
仔細觀察UDP客戶端會發現,缺少把IP地址和埠分配給套接字的過程。 TCP客戶端呼叫connect函式自動完成IP地址和埠分配給套接字的過程。 UDP客戶端通常無需額外的地址分配過程。呼叫sendto函式時自動分配IP和埠號
6.3 UDP的資料傳輸特性和呼叫connect函式
UDP是具有資料邊界的協議,傳輸中呼叫IO函式的次數非常重要。輸入函式的呼叫次數應和輸出函式的呼叫次數一致,這樣才能保證接收全部已傳送資料。