基本套接字編程(7) -- udp篇

1. UDP概述

        UDP 是User Datagram Protocol的簡稱, 中文名是用戶數據報協議,是OSI(Open System Interconnection,開放式系統互聯) 參考模型中一種無鏈接的傳輸層協議,提供面向事務的簡單不可靠信息傳送服務,IETF RFC 768是UDP的正式規範。UDP在IP報文的協議號是17。
UDP協議全稱是用戶數據報協議 ,在網絡中它與TCP協議同樣用於處理數據包,是一種無鏈接的協議。在OSI模型中,在第四層--傳輸層,處於IP協議的上一層。UDP有不提供數據包分組、組裝和不能對數據包進行排序的缺點,也就是說,當報文發送以後,是沒法得知其是否安全完整到達的。UDP用來支持那些須要在計算機之間傳輸數據的網絡應用。包括網絡視頻會議系統在內的衆多的客戶/服務器模式的網絡應用都須要使用UDP協議。UDP協議從問世至今已經被使用了不少年,雖然其最初的光彩已經被一些相似協議所掩蓋,可是即便是在今天UDP仍然不失爲一項很是實用和可行的網絡傳輸層協議。
與所熟知的TCP(傳輸控制協議)協議同樣,UDP協議直接位於IP(網際協議)協議的頂層。根據OSI(開放系統互連)參考模型,UDP和TCP都屬於傳輸層協議。UDP協議的主要做用是將網絡數據流量壓縮成數據包的形式。一個典型的數據包就是一個二進制數據的傳輸單位。每個數據包的前8個字節用來包含報頭信息,剩餘字節則用來包含具體的傳輸數據。
在使用TCP編寫的應用程序與使用UDP編寫的應用程序之間存在一些本質差別,其緣由在於這兩個傳輸層之間的差異:UDP是無鏈接不可靠的數據報協議,很是不一樣於TCP提供的面向鏈接的可靠字節流協議。然而,相比TCP,有些場合確實更適合使用UDP,典型應用程序又:DNS(域名系統)、NFS(網絡文件系統)和SNMP(簡單網絡管理協議)。

2. UDP套接字編程

與面向鏈接的協議相比,面向無鏈接協議極爲不一樣。其中一個重要的不一樣點就是客戶端與服務器之間沒必要創建鏈接。
對於UDP套接字編程而言,服務器建立套接字後,調用bind()函數將套接字與準備接收數據的接口綁定在一塊兒。和TCP編程不一樣的是,應用程序沒必要調用listen()和accept()函數等待客戶端的鏈接。而只須要等待接收數據了。開發UDP套接字應用程序,有兩個重要的函數sendto()和recvfrom()。服務器採用recvfrom()來接收來自客戶端的數據報,並得到客戶端的端地址,以後向客戶端發送數據時,採用sendto()函數。
下圖爲 UDP套接字編程流程圖

從圖示中能夠明顯的看出UDP套接字網絡編程與TCP的區別。

3. UDP套接字函數

套接字建立socket()、地址綁定bind()函數與TCP套接字編程相同,具體請參考 基本套接字編程(1) -- tcp篇,此處僅介紹消息傳輸函數sendto()與recvfrom();

3.1 消息發送函數sendto()

函數原型:
#include <sys/socket.h>
ssize_t sendto(int sockfd , const void *buf , size_t nbytes , int flags , const struct sockaddr *to , socklen_t addrlen);
		<span style="white-space:pre">								</span>返回值:成功返回寫的字節數,出錯返回-1
函數說明:
sendto() 用來將數據由指定的socket傳給對方主機。參數s爲已建好連線的socket,若是利用UDP協議則不需通過連線操做。參數buf指向欲連線的數據內容,參數flags 通常設0,詳細描述請參考send()。參數to用來指定欲傳送的網絡地址,結構sockaddr請參考bind()。參數addrlen爲sockaddr的結構長度。
參數:
  • 前三個參數sockfd , buff 和 nbytes等同於read 和 write函數的三個參數:描述符、指向寫出緩衝區的指針和寫字節數;
  • falgs參數通常置0;
  • to參數指向一個含有數據報接收者的協議地址(如IP地址和端口號)的套接字地址結構,其大小由addrlen指定;
返回值:
若無錯誤發生,返回所發送數據的總數(請注意這個數字可能小於len中所規定的大小)。不然的話,返回SOCKET_ERROR錯誤(-1),應用程序可經過WSAGetLastError()獲取相應錯誤代碼。
  • WSANOTINITIALISED:在使用此API以前應首先成功地調用WSAStartup();
  •   WSAENETDOWN:WINDOWS套接口實現檢測到網絡子系統失效;
  •   WSAEACESS:要求地址爲廣播地址,但相關標誌未能正確設置;
  •   WSAEINTR:經過一個WSACancelBlockingCall()來取消一個(阻塞的)調用;
  •   WSAEINPROGRESS:一個阻塞的WINDOWS套接口調用正在運行中;
  •   WSAEFAULT:buf或to參數不是用戶地址空間的一部分,或to參數過小(小於sockaddr結構大小);
  •   WSAENETRESET:因爲WINDOWS套接口實現放棄了鏈接,故該鏈接必需被複位;
  •   WSAENOBUFS:WINDOWS套接口實現報告一個緩衝區死鎖;
  •   WSAENOTCONN:套接口未被鏈接;
  •   WSAENOTSOCK:描述字不是一個套接口;
  •   WSAEOPNOTSUPP:已設置了MSG_OOB,但套接口非SOCK_STREAM類型;
  •   WSAESHUTDOWN:套接口已被關閉。一個套接口以1或2的how參數調用shutdown()關閉後,沒法再用sned()函數;
  •   WSAEWOULDBLOCK:套接口被標誌爲非阻塞, 但該調用會產生阻塞;
  •   WSAEMSGSIZE:套接口爲SOCK_DGRAM類型,且數據報大於WINDOWS套接口實現所支持的最大值;
  •   WSAECONNABORTED:因爲超時或其餘緣由引發虛電路的中斷;
  •   WSAECONNRESET:虛電路被遠端復位;
  •   WSAEADDRNOTAVAIL:所指地址沒法從本地主機得到;
  •   WSAEAFNOSUPPORT:所指定地址族中地址沒法與本套接口一切使用;
  •   WSAEDESADDRREQ:須要目的地址;
  •   WSAENETUNREACH:當前沒法從本主機聯上網絡;

3.2 消息接收函數recvfrom()

函數原型:
#include <sys/socket.h>
ssize_t recvfrom(int sockfd , const void *buf , size_t nbytes , int flags , const struct sockaddr *from, socklen_t addrlen);
										返回值:成功返回寫的字節數,出錯返回-1
函數說明:
recvfrom()用來接收遠程主機經指定的socket傳來的數據,並把數據傳到由參數buf指向的內存空間,參數nbytes爲可接收數據的最大長度.參數,flags通常設0,其餘數值定義參考recv(),參數from用來指定欲傳送的網絡地址,結構sockaddr請參考bind()函數,參數fromlen爲sockaddr的結構長度。
參數說明:
  • 前三個參數sockfd , buff 和 nbytes等同於read 和 write函數的三個參數:描述符、指向寫出緩衝區的指針和寫字節數;
  • falgs參數通常置0;
  • from參數指向一個含有數據報接收者的協議地址(如IP地址和端口號)的套接字地址結構,其大小由addrlen指定;
返回值:
同sendto()函數!若無錯誤發生,返回所接收數據的總數(請注意這個數字可能小於len中所規定的大小)。不然的話,返回SOCKET_ERROR錯誤(-1),應用程序可經過WSAGetLastError()獲取相應錯誤代碼。
  • WSANOTINITIALISED:在使用此API以前應首先成功地調用WSAStartup();
  •   WSAENETDOWN:WINDOWS套接口實現檢測到網絡子系統失效;
  •   WSAEACESS:要求地址爲廣播地址,但相關標誌未能正確設置;
  •   WSAEINTR:經過一個WSACancelBlockingCall()來取消一個(阻塞的)調用;
  •   WSAEINPROGRESS:一個阻塞的WINDOWS套接口調用正在運行中;
  •   WSAEFAULT:buf或to參數不是用戶地址空間的一部分,或to參數過小(小於sockaddr結構大小);
  •   WSAENETRESET:因爲WINDOWS套接口實現放棄了鏈接,故該鏈接必需被複位;
  •   WSAENOBUFS:WINDOWS套接口實現報告一個緩衝區死鎖;
  •   WSAENOTCONN:套接口未被鏈接;
  •   WSAENOTSOCK:描述字不是一個套接口;
  •   WSAEOPNOTSUPP:已設置了MSG_OOB,但套接口非SOCK_STREAM類型;
  •   WSAESHUTDOWN:套接口已被關閉。一個套接口以1或2的how參數調用shutdown()關閉後,沒法再用sned()函數;
  •   WSAEWOULDBLOCK:套接口被標誌爲非阻塞, 但該調用會產生阻塞;
  •   WSAEMSGSIZE:套接口爲SOCK_DGRAM類型,且數據報大於WINDOWS套接口實現所支持的最大值;
  •   WSAECONNABORTED:因爲超時或其餘緣由引發虛電路的中斷;
  •   WSAECONNRESET:虛電路被遠端復位;
  •   WSAEADDRNOTAVAIL:所指地址沒法從本地主機得到;
  •   WSAEAFNOSUPPORT:所指定地址族中地址沒法與本套接口一切使用;
  •   WSAEDESADDRREQ:須要目的地址;
  •   WSAENETUNREACH:當前沒法從本主機聯上網絡;

4. UDP回射程序實例

4.1 server.c

<pre name="code" class="cpp">#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/ip.h>

const int SERV_PORT = 6000;
const int MAXLINE = 2048;
void dg_echo(int sockfd , struct sockaddr *pcliaddr , socklen_t clilen)
{
	int n;
	socklen_t len;
	char mesg[MAXLINE];
	for( ; ;)
	{
		len = clilen;
		if((n = recvfrom(sockfd , mesg , MAXLINE , 0 , pcliaddr , &len))<0)
		{
			perror("recvfrom error");
			exit(1);
		}//if

		if((n = sendto(sockfd , mesg , n , 0 , pcliaddr , len)) < 0)
		{
			perror("sendto error");
			exit(1);
		}//if
	}//for
}
int main(int argc , char **argv)
{
	int sockfd;
	struct sockaddr_in servaddr , cliaddr;
	bzero(&servaddr , sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons(SERV_PORT);
	if((sockfd = socket(AF_INET , SOCK_DGRAM , 0)) < 0)
	{
		perror("socket error");
		exit(1);
	}//if

	if(bind(sockfd , (struct sockaddr *)&servaddr , sizeof(servaddr)))
	{
		perror("bind error");
		exit(1);
	}//if
	dg_echo(sockfd , (struct sockaddr *)&cliaddr , sizeof(cliaddr));	
}
 

4.2 client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>

const int SERV_PORT = 6000;
const int MAXLINE = 2048;

void dg_cli(FILE *fp , int sockfd , const struct sockaddr *pservaddr , socklen_t servlen)
{
	int n;
	char sendline[MAXLINE] , recvline[MAXLINE+1];

	while(fgets(sendline , MAXLINE , fp) != NULL)
	{
		if(sendto(sockfd , sendline , strlen(sendline) , 0 , pservaddr ,  servlen) < 0)
		{
			perror("sendto error");
			exit(1);
		}//if

		if( ( n = recvfrom(sockfd , recvline , MAXLINE , 0 , NULL , NULL)) < 0)
		{
			perror("recvfrom error");
			exit(1);
		}//if
		recvline[n] = '\0';
		fputs(recvline , stdout);
	}//while
}

int main(int argc , char **argv)
{
	int sockfd , t;
	struct sockaddr_in servaddr;
	if(argc != 2)
	{
		perror("usage: udpcli <IPaddress>");
		exit(1);
	}//if
	bzero(&servaddr , sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(SERV_PORT);
	if((t = inet_pton(AF_INET , argv[1], &servaddr.sin_addr)) <= 0)
	{
		perror("inet_pton error");
<span style="white-space:pre">	</span>	exit(1);
	}//if

	if((sockfd = socket(AF_INET , SOCK_DGRAM , 0)) < 0)
	{
		perror("socket error");
		exit(1);
	}//if

	dg_cli(stdin , sockfd , (struct sockaddr *)&servaddr , sizeof(servaddr));
	exit(0);
} 

4.3 運行結果



相關文章
相關標籤/搜索