UDP簡單介紹與通訊模擬(C)

參考:

        UDP是面向數據報的運輸層協議、無連接通信協議,即在數據傳輸時,數據的發送端和接收端不建立邏輯連接。簡單來說,當一臺計算機向另外一臺計算機發送數據時,發送端不會確認接收端是否存在,就會發出數據,同樣接收端在收到數據時,也不會向發送端反饋是否收到數據。但是在使用UDP協議傳送數據時,由於UDP的面向無連接性,不能保證數據的完整性,因此在傳輸重要數據時不建議使用UDP協議。UDP傳輸數據被限制在64K以內。

UDP和TCP最大的區別:

     1) TCP最大的特點就是面向連接、安全可靠,也就是說TCP通信必須要先建立連接,並且通信過程需要時時校驗,如果數據有誤需要重發;

     2) UDP最大的特點就是面向無連接,不可靠,也就是說不用建立連接就直接向目標發送信息,並且通信過程中不做任何校驗,如果數據丟失或者有誤也不管;

     3) 聽上去UDP非常的無用,但其實不然,UDP最大的優勢就是速度快,而TCP在連接和校驗的過程中會消耗非常多的時間,因此TCP一般用於對數據要求精確無誤的場合下,比如下載程序(迅雷等),可想而知,若你下載一個軟件,中間傳輸的數據有誤那軟件豈不是用不了了嗎?

     4) UDP的應用場合通常是即時通訊等要求速度高於質量的場合,比如視頻對話、網絡對話等,在這種場合下,特別是在視頻聊天時,視頻質量可以不那麼清晰(UDP不對數據校驗),但是畫面必須是時時的,如果用TCP的話可能視頻聲音是當前的聲音,但是畫面可能還是是幾秒前的畫面,這就不符合即時的要求了!!因此UDP的應用場合還是非常多的!

UDP數據報格式

        UDP數據報封裝成一份IP數據報的格式如下圖所示。端口號表示發送進程和接收進程。由於IP層已經把IP數據報分配給TCP或UDP(根據IP首部中協議字段值),因此TCP端口號由TCP來查看,而UDP端口號由UDP來查看。TCP端口號與UDP端口號是相互獨立的。UDP長度字段指的是UDP首部和UDP數據的字節長度。該字段的最小值爲8字節(發送一份0字節的UDP數據報是OK)。這個UDP長度是有冗餘的。IP數據報長度指的是數據報全長,因此UDP數據報長度是全長減去IP首部的長度(該值在首部長度字段中指定)。

     

 

UDP使用
        在選擇使用協議的時候,選擇UDP必須要謹慎。在網絡質量令人十分不滿意的環境下,UDP協議數據包丟失會比較嚴重。但是由於UDP的特性:它不屬於連接型協議,因而具有資源消耗小,處理速度快的優點,所以通常音頻、視頻和普通數據在傳送時使用UDP較多,因爲它們即使偶爾丟失一兩個數據包,也不會對接收結果產生太大影響。比如我們聊天用的QQ就是使用的UDP協議。

        既然UDP是一種不可靠的網絡協議,那麼還有什麼使用價值或必要呢?其實不然,在有些情況下UDP協議可能會變得非常有用。因爲UDP具有TCP所望塵莫及的速度優勢。雖然TCP協議中植入了各種安全保障功能,但是在實際執行的過程中會佔用大量的系統開銷,無疑使速度受到嚴重的影響。反觀UDP由於排除了信息可靠傳遞機制,將安全和排序等功能移交給上層應用來完成,極大降低了執行時間,使速度得到了保證。

對UDP一次發送多少bytes好?

  • 在普通的局域網環境下,建議將UDP的數據控制在1472字節以下爲好。
  • 在進行Internet的UDP編程時. 最好將UDP的數據長度控件在548字節以內.

UDP數據報重組
        與TCP的通信機制不同,由於UDP是無連接協議,因此通信發生之前不會建立會話。UDP是基於事務的,換言之,應用程序要發送數據時,它僅是發送數據而已。很多使用UDP的應用程序發送的數據量很小,用一個數據段就夠了。但是也有一些應用程序需要發送大量數據,因此需要用多個數據段。UDP PDU(協議數據單元)的實際意義是數據報,儘管數據段和數據報可以互換使用來描述某個傳輸層PDU。

        將多個數據報發送到目的主機時,它們可能使用了不同的路徑,到達順序也可能跟發送時的順序不同。與TCP不同,UDP不跟蹤序列號。UDP不會對數據報重組,因此也不會將數據恢復到傳輸時的順序。因此,UDP僅僅是將接收到的數據按照先來後到的順序轉發到應用程序。如果數據的順序對應用程序很重要,那麼應用程序只能自己標誌數據的正確順序,並決定如何處理這些數據。

UDP服務器進程與請求

        與基於TCP的應用程序相同的是,基於UDP的服務器應用程序也被分配了公認端口或已註冊的端口。當上述應用程序或進程運行時,它們就會接受與所分配端口相匹配的數據。當UDP收到用於某個端口的數據報時,它就會按照應用程序的端口號將數據發送到相應的應用程序。

UDP客戶端進程
        對於TCP而言,客戶端/服務器模式的通信初始化採用由客戶端應用程序向服務器進程請求數據的形式。而UDP客戶端進程則是從動態可用端口中隨機挑選一個端口號,用來作爲會話的源端口。而目的端口通常都是分配到服務器進程的公認端口或已註冊的端口。

        採用隨機的源端口號的另一個優點是提高安全性。如果目的端口的選擇方式容易預測,那麼網絡入侵者很容易就可以通過嘗試最可能開放的端口號訪問客戶端。

        由於UDP不建立會話,因此一旦數據和端口號準備就緒,UDP就可以生成數據報並遞交給網絡層,並在網絡上尋址和發送。

        需要謹記的是,客戶端選定了源端口和目的端口後,通信事務中的所有數據報文頭都採用相同的端口對。對於從服務器到達客戶端的數據來說,數據報頭所含的源端口和目的端口作了互換。

UDP的Sockets編程:

 1) 首先最大的特點就是客戶端不需要使用connect連接,服務器端也不需要listen來監聽請求,但不過建立套接字的過程還是和TCP一樣的;

    2) 服務器端不需要accept了,因爲不需要監聽,而是直接可以用recvfrom函數接受客戶端發送的數據!

    3) 而客戶端由於不需要connect連接服務器端,因此可以直接使用sendto函數向目標服務器發送數據;

    4) 雙方都可以直接使用sendto和recvfrom進行數據通信;

    5) UDP套接字的配置:

         i. 首先需要在socket()函數中指定爲SOCK_DGRAM,即數據包套接字類型(基於UDP);

         ii. 在TCP中,數據收發必須持有對方的套接字,而服務器端監聽、接收請求必須持有本地的套接字,一般需要兩個套接字來支持;

         iii. 但是在UDP中,通信雙方只能持有一個套接字,即都是本地的套接字,發送的時候需要指定對方的套接字地址,而接收的時候需要用一個空的套接字地址接收對方的地址,即收發時sendto和recvfrom中的套接字句柄s都是綁定了本地地址的套接字,收發統統必須持有自己的套接字;

         iv. 也就是說數據收發的緩存都是用本地套接字!而TCP中數據收發的緩存都是用對方的套接字(建立在本地程序中);

         v. 因此,雙方在收發數據之前必須先對本地地址進行綁定,服務器端仍然可以使用bind進行顯示的綁定,但是在Winsock手冊中明確講了不支持在客戶端中使用bind來顯示綁定自己的地址,因爲顯示綁定往往需要你輸入精確的地址,而有些時候地址是動態分配的,每次使用的都可能不一樣,因此不推薦在客戶端中顯示的使用bind來綁定自己的地址;

         vi. 還好,sendto函數在第一次調用的時候就能隱式地綁定當前的地址,由於服務器端只能被動地等待請求,因此不可能比recvfrom先調用sendto,所以服務器端要先使用bind來綁定本地地址,而客戶端必須主動請求向服務器端發送信息,因此不可能比sendto先調用recvfrom,因此sendto一定先調用,而調用的同時也自動綁定了本地地址了;

代碼是在網上找的:https://blog.csdn.net/crazycz/article/details/12071705

server.c

#include <stdio.h>
#include <string.h>
#include <winsock.h>
#include <windows.h>
 
#pragma comment( lib, "ws2_32.lib" )
 
#define PORT 2046
#define BACKLOG 10
#define TRUE 1
#define MAXDATASIZE 1000
 
int main(void)
{
	int iServerSock;
	// int iClientSock;
 
	int addr_len;
	int numbytes;
 
	char buf[MAXDATASIZE];
 
	struct sockaddr_in ServerAddr;
	struct sockaddr_in ClientAddr;
 	
 	//³õʼ»¯WSA
	WSADATA WSAData; 
	if(WSAStartup(MAKEWORD(1,1), &WSAData))
	{
		printf( "initializationing error!\n" );
		WSACleanup();
		exit( 0 );
	}
 	//´´½¨socket 
	iServerSock = socket( AF_INET, SOCK_DGRAM, 0 );
	if(iServerSock == INVALID_SOCKET)
	{
		printf("socket error !\n");
		WSACleanup( );
		exit( 0 );
	}
 
 	
	ServerAddr.sin_family = AF_INET;
	ServerAddr.sin_port = htons( PORT );//¶Ë¿ÚºÅ 
	ServerAddr.sin_addr.s_addr = INADDR_ANY;
	memset( & ( ServerAddr.sin_zero ), 0, sizeof( ServerAddr.sin_zero ) );
 
 
	if( bind( iServerSock, ( struct sockaddr * )&ServerAddr, sizeof( struct sockaddr ) ) == -1 )
	{
		printf( "bind\n" );
		WSACleanup( );
		exit( 0 );
	}
 
 
	addr_len = sizeof( struct sockaddr );
	numbytes = recvfrom( iServerSock, buf, MAXDATASIZE, 0, ( struct sockaddr * ) & ClientAddr, &addr_len );
	if( numbytes == -1 )
	{
		printf( "recvfrom\n" );
		WSACleanup( );
		exit( 0 );
	}
 
	printf( "got packet from %s\n", inet_ntoa( ClientAddr.sin_addr ) );
	printf( "packet is %d bytes long\n", numbytes );
	buf[ numbytes ] = '\0';
	printf( "packet contains \"%s\"\n", buf );
 
	closesocket( iServerSock );
	WSACleanup( );
 
 	return 0;
}

client.c 

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <winsock.h>
#include <string.h>
 
#pragma comment( lib, "ws2_32.lib" )
 
#define PORT 2046
#define MAXDATASIZE 100
 
 
int main( void )
{
 
	int iClientSock;
	struct sockaddr_in ServerAddr;
 
	int numbytes;
	char buf[ MAXDATASIZE ] = "hello world!";
 
	WSADATA WSAData;
	if( WSAStartup( MAKEWORD( 1, 1 ), &WSAData ) )
	{
		printf( "initializationing error!\n" );
		WSACleanup( );
		exit( 0 );
	}
 
	if( ( iClientSock = socket( AF_INET, SOCK_DGRAM, 0 ) ) == -1 )
	{
		printf( "Ì×½Ó×Ö´´½¨Ê§°Ü\n" );
		WSACleanup( );
		exit( 0 );
	}
 
	ServerAddr.sin_family = AF_INET;
	ServerAddr.sin_port = htons( PORT );
	ServerAddr.sin_addr.s_addr = inet_addr( "192.168.31.97" );//
	memset( &( ServerAddr.sin_zero ), 0, sizeof( ServerAddr.sin_zero ) );
 
 
	numbytes = sendto( iClientSock, buf, strlen( buf ), 0, ( struct sockaddr * ) & ServerAddr, sizeof( struct sockaddr ) );
	if( numbytes == -1 )
	{
		printf( "sendtoµ÷ÓÃʧ°Ü\n" );
		WSACleanup( );
		exit( 0 );
	}
 
	printf( "sent %d bytes to %s\n", numbytes, inet_ntoa( ServerAddr.sin_addr ) );
 
	closesocket( iClientSock );
	WSACleanup( );
	return 0;
} 

注意:該代碼實在windows下運行的,使用dev開發環境。運行時報錯:undefined reference to `__imp_WSAStartup' 

方法,工具-編譯器環境-編譯器加入如下命令:-lwsock32

                                 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章