UDP(User Datagram Protocol) : 用戶數據報協議,是一種無連接,不可靠的數據傳輸服務。與TCP不同的是,它不需要建立連接就可以直接傳輸數據,也就不存在關閉連接之類的問題。
常見的UDP程序有:DNS,NFS,SNMP。
一、recvfrom 和 sendto 函數
recvfrom 和 sendto 函數主要用於UDP數據的讀寫操作,一個用於接收網絡數據,一個用於向對方發生數據。雖然都可以用於TCP,但一般不這麼做。
/* recvfrom
* 返回值:成功則爲讀到的字節數(可以爲0),錯誤則爲-1;
* 函數定義:
*/
# include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags,
struct sockaddr *from, socklen_t *addrlen);
/* 說明
* sockfd:要接收數據的套接字描述符;
* buff:接收數據緩衝區;
* nbytes:請求接收數據字節個數(緩衝區大小);
* flag:設置一些參數,一般爲0;
* from:數據發送者的套接字地址結構;
* addrlen:from的長度/字節數(值-結果參數);
* recvfrom的返回值可以是0,即沒有數據只有數據報頭部的UDP報文;
* 若是不關心數據發送者地址,from可以爲空,相應addrlen也要爲空;
*/
/* sendto
* 返回值:成功則爲發送的字節數(可以爲0),錯誤則爲-1;
* 函數定義:
*/
# include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags,
const struct sockaddr *to, socklen_t addrlen);
/* 說明
* sockfd:要發送數據的套接字描述符;
* buff:發送數據緩衝區;
* nbytes:可以發送數據字節個數;
* flag:設置一些參數,一般爲0;
* from:數據接收者的套接字地址結構;
* addrlen:from的長度/字節數,注意是整數不是指針;
* sendto的返回值也可以是0,即寫一個空的數據報;
*/
二、簡單的udp回射客戶/服務器程序
基於UDP協議的客戶/服務器程序與基於TCP的不同,不需要建立連接,也不需要分別對不同的客戶端進行分別處理,只是簡單的進行數據發送和接收。一般來說大多數TCP服務器都是併發的,因爲它需要與多個客戶端保持連接,而UDP服務器多是迭代的,即只需要不停的循環進行接收數據就行。UDP層中隱含有排隊發生,每一個UDP套接字都有一個接收緩衝區,到達的數據報都進入這個緩衝區,而recvfrom函數就從這個緩衝區按照順序讀取數據報。因此不管是哪一個客戶端發來的數據都放在緩衝區,由recvfrom函數來讀取,不需要類似tcp中建立子進程來處理每一個客戶端。
/* 服務器 */
#include <sys/socket.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h> //INADDR_ANY
void udp_echo(int sockfd, struct sockaddr* pcliaddr, socklen_t clilen);
int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr, cliaddr;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(6666);
bind(sockfd, (struct sockaddr*) &servaddr, sizeof(servaddr));
udp_echo(sockfd, (struct sockaddr*) &cliaddr, sizeof(cliaddr));
return 0;
}
void udp_echo(int sockfd, struct sockaddr* pcliaddr, socklen_t clilen)
{
int n;
char buf[256];
for(;;)
{
n = recvfrom(sockfd, buf, 256, 0, pcliaddr, &clilen);
write(fileno(stdout), buf, n);
sendto(sockfd, buf, n, 0, pcliaddr, clilen);
}
}
/* 客戶端 */
#include <stdio.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
int main(int argc, char** argcv)
{
int sockfd;
struct sockaddr_in servaddr;
char *ipaddr = "192.168.110.128";
int n, m;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(6666);
inet_pton(AF_INET, ipaddr, &servaddr.sin_addr);
//ip address need to be transfromed from char* to bytes
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
char sendbuf[256], recvbuf[256];
while((n = read(fileno(stdin), sendbuf, 256)) != 0)
{
sendto(sockfd, sendbuf, n, 0, (struct sockaddr*) &servaddr, sizeof(servaddr));
m = recvfrom(sockfd, recvbuf, 256, 0, NULL, NULL);
write(fileno(stdout), recvbuf, m);
}
}
對於客戶端來說,進程並沒有爲套接字sockfd綁定一個本地端口地址,與TCP一樣,內核會給它分配一個臨時端口,可以在代碼中顯示的調用bind,但是一般不會這樣做。
需要注意的是,對於IO來說,一般都存在緩衝區,因此有時最好不要連續調用讀取函數,不然可能不會快速輸出,需要強行輸入緩衝區內容可以用fflush函數。
三、UDP connect
udp中可以調用connect函數,不過結果與tcp中不大一樣,沒有3路握手過程,內核只是檢查是否存在立即可知的錯誤,記錄對端的IP地址和端口號,然後立即返回到調用進程。
調用connect之後,不再使用sendto和recvfrom來進行數據的讀寫,一般用read,write,send等函數;當然一定要用sendto也是可以的,但是不需要再指定目的地址,因爲連接之後,套機字已經保存了目的端地址,也因爲如此,連接的套接字只能與固定端進行數據交換,需要更改目的地址可以多次調用connect;
再次調用connect有兩個目的:指定新的IP地址和端口號(tcp套接字connect只能調用一次),斷開套接字(套接字地址結構的地址族設置爲AF_UNSPEC)。
調用connect的好處:已連接的套接字能夠接收到返回的異步錯誤,未連接的不能,提高傳輸效率,增加系統穩定性(不會把非目的地址發來的數據當作返回數據)。
UDP 調用 connect的作用;