1 概述
TCP和UDP網絡程式設計存在一些本質的差異,主要是由于傳輸層的差别:UDP是無連接配接的不可靠的資料報協定,而TCP是面向連接配接的位元組流協定。
下圖是典型的UDP用戶端和伺服器之間的通信流程。客戶不與伺服器建立連接配接,而是隻管使用sendto函數。伺服器不接受來自客戶的連接配接,而是隻管調用recvfrom函數,等待某個客戶的資料到達。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicGcq5COxADM0UzM1ITMtUTN5UzM4MjNxEjMzAzNxAjMtQTO3UTO48CXzAzNxAjMvwFN5cTN5gzLcd2bsJ2Lc12bj5ycn9Gbi52YuUTMwIzcldWYtl2Lc9CX6MHc0RHaiojIsJye.jpg)
本章學習用于UDP套接字的兩個新函數recvfrom和sendto,并使用UDP重寫ECHO程式。還将學習connect函數在UDP套接字中的用法,同時了解異步錯誤的概念
2 recvfrom和sendto函數
這兩個函數類似标準的read、write函數,不過需要額外的三個參數。
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t nbytes, int flags, struct sockaddr *from, socklen_t *addrlen);
ssize_t sendto(int sockfd, const void *buf, size_t nbytes, int flags, const struct sockaddr *to, socklen_t addrlen);
/*以上函數傳回:成功傳回讀或寫到的位元組數,出錯傳回-1*/
前三個參數sockfd、buff和nbytes分别代表:描述符、指向輸入或寫出緩沖區的指針和讀寫的位元組數。
flags參數在讨論recv、send、recvmsg和sendmsg函數時再介紹,這裡預設設定為0。
sendto的to參數指向一個含有資料報接受者的協定位址(IP位址及端口号)的套接字位址結構,其大小由addrlen指定。recvfrom的from參數指向一個含有資料報發送者的協定位址(IP位址及端口号)的套接字位址結構,其大小由addrlen指定。注意sendto的最後一個參數是一個整數值,而recvfrom的最後一個參數是一個指向整形的指針(即值-結果函數)。
寫一個長度位(0)的資料報是可行的(TCP不允許),這會發送一個隻有IP首部和UDP首部,沒有資料的資料報。類似的,recvfrom傳回0值也是可以的(TCP則代表對端已關閉連接配接)。如果recvfrom的from參數是一個空指針,相應的addrlen為0,說明我們不關心發送者的協定位址。recvfrom和sendto函數都可以用于TCP,盡管通常不這麼做。
3 最原始的UDP Echo程式
udp_echo_server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
void echo_server(int sockfd, struct sockaddr *p_client_addr, socklen_t cli_len) {
int n;
socklen_t len;
char buf[1024];
while(1) {
len = cli_len;
n = recvfrom(sockfd, buf, 1024, 0, p_client_addr, &len);
sendto(sockfd, buf, n, 0, p_client_addr, len);
}
}
int main(int argc, char **argv) {
int sockfd;
struct sockaddr_in server_addr, client_addr;
sockfd=socket(AF_INET, SOCK_DGRAM, 0);
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family=AF_INET;
server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
server_addr.sin_port=htons(12345);
if(bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr))<0)
perror("bind error!");
echo_server(sockfd, (struct sockaddr *)&client_addr, sizeof(client_addr));
}
該程式存在一些問題:
- 函數永遠不會終止,因為UDP是一個無連接配接的協定,沒有像TCP中EOF之類的東西
- 該函數提供的是一個疊代伺服器,而不是并發服務。是以單個伺服器程序就得處理所有客戶
- 對于本套接字,UDP層隐含有排隊的發生
udp_echo_client.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
void echo_client(FILE *fp, int sockfd, const struct sockaddr *p_server_addr, socklen_t serv_len) {
int n;
char send[1024], recv[1024];
while(fgets(send, 1024, fp) !=NULL) {
sendto(sockfd, send, strlen(send), 0, p_server_addr, serv_len);
n=recvfrom(sockfd, recv, 1024, 0, NULL, NULL);
recv[n]='\0';
fputs(recv, stdout);
}
}
int main(int argc, char **argv) {
int sockfd;
struct sockaddr_in server_addr;
if(argc!=2) {
printf("usage: udp_client <IP Address>\n");
exit(0);
}
sockfd=socket(AF_INET, SOCK_DGRAM, 0);
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(12345);
inet_pton(AF_INET, argv[1], &server_addr.sin_addr);
echo_client(stdin, sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
}
該程式也隐含一個問題:
- 用戶端尚未請求核心給它的套接字指派一個臨時端口
- recvfrom第5個和第6個參數是空指針,表示不關心資料是由誰發送。是以任何程序的資料包都會被當成是伺服器的應答。
- UDP是不可靠的。如果一個資料報丢失,客戶将永遠阻塞在recvfrom函數上。
假設在伺服器不啟動的情況下啟動用戶端,資料報由客戶發出,伺服器主機響應一個端口不可達的ICMP消息,而這個ICMP消息不傳回給客戶程序,客戶将永遠阻塞于recvfrom調用,等待伺服器的應答。我們稱這個錯誤為異步錯誤(asynchronous error)。該錯誤由sendto引起,但sendto本身卻成功傳回。UDP輸出成功僅僅代表在接口隊列中有存放IP資料報的空間。改ICMP直到後來才傳回,這就是稱其為異步的原因。
4 UDP程式例子小結
轉載于:https://www.cnblogs.com/alwayswangzi/p/6595310.html