14_udp基礎知識

1. UDP的特點

 無連接
 基於消息的數據傳輸服務
 不可靠
 一般情況下UDP更加高效
 不粘包但丟包

UDP客戶/服務基本模型

在這裏插入圖片描述

- UDP注意點

 UDP報文可能會丟失、重複
 UDP報文可能會亂序
 UDP缺乏流量控制
udp緩衝區寫滿以後,沒有流量控制機制,會覆蓋緩衝區。即緩衝區寫滿後,再寫會覆蓋!!
 UDP協議數據報文截斷
如果接收到的數據報,大於緩衝區;報文可以被截斷;後面的部分會丟失。
 recvfrom返回0,不代表連接關閉,因爲udp是無連接的。
sendto可以發送數據0包。。。只含有udp頭部。
 ICMP異步錯誤
觀察現象: 關閉udp服務端,若啓動udp客戶端,從鍵盤接受數據後,再發送數據。udp客戶端阻塞在recvfrom位置;
//說明1:udp發送報文的時,只把數據copy到發送緩衝區。在服務器沒有起來的情況下,可以發送成功。
說明2:所謂ICMP異步錯誤是指:發送的報文的時候,沒有錯誤,接受報文recvfrom的時候,回收到ICMP應答
說明3:異步的錯誤,是無法返回未連接的套接字。udp也可以調用connect
 UDP connect
說明1://udp調用connet,並沒有三次握手,只是維護了一個狀態信息(和對等方的)。。。
說明2)//一但調用connect,就可以使用send函數

例1 不啓動服務端,運行客戶端也發送成功

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

#define ERR_EXIT(m) \
	do \
	{ \
		perror(m); \
		exit(EXIT_FAILURE); \
	}while(0)

void echo_cli(int sock)
{
	struct sockaddr_in servaddr;
	memset(&servaddr, 0, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(8002);
	servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
	
	int ret;
	char sendbuf[1024] = {0};
	char recvbuf[1024] = {0};
	//fgets 自動加上\n
	while(fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
	{
		////sendto第一次發送的時候,會綁定地址
		ret = sendto(sock, sendbuf, strlen(sendbuf), 0, (struct sockaddr*)&servaddr, sizeof(servaddr));
		if(ret != -1)
			printf("1-發送成功:%d\n", ret);
		//ret = sendto(sock, sendbuf, strlen(sendbuf), 0, NULL, 0);
		
		ret = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, NULL, NULL);
		///printf("--step2--\n");
		if (-1 == ret) 
		{
			if (errno == EINTR) 
				continue;
			ERR_EXIT("recvfrom");
		}
		fputs(recvbuf, stdout);
		memset(sendbuf, 0, sizeof(sendbuf));
		memset(recvbuf, 0, sizeof(recvbuf));
	}
	close(sock);
}

int main()
{
	int sock;
	if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0) 
		ERR_EXIT("socket");
	
	echo_cli(sock);
	return 0;
	ICMP異步錯誤
觀察現象: 關閉udp服務端,若啓動udp客戶端,從鍵盤接受數據後,再發送數據
。udp客戶端阻塞在recvfrom位置;

說明1:
udp發送報文的時,只把數據copy到發送緩衝區。在服務器沒有起來的
情況下,可以發送成功。

說明2:
     所謂ICMP異步錯誤是指:發送的報文的時候,沒有錯誤,
     接受報文recvfrom的時候,回收到ICMP應答。
     這個ICMP錯誤是異步錯誤,由sendto引起的,但是sendto本身卻返回成功!
     但是該錯誤是不返回給用戶進程的!!因爲根本沒有連接,
說明3:
     異步的錯誤,是無法返回未連接的套接字。udp也可以調用connect

運行結果:第一次發送數據顯示成功,再次發送數據kkk時,程序已經阻塞在recvfrom上了,等待一個永遠不會到達的服務器應答
在這裏插入圖片描述

例1 不啓動服務端,運行客戶端也發送成功,此時使用connect,對等方會返回一個ICMP包,注意
沒有三路握手過程,內核只是檢查是否存在立即可知的錯誤

/*
	結論:客戶端調用connet和不調connet的區別。
	1)	udp也可以調用connet
	2)	udp客戶端調用了connect以後,不會阻塞在recvfrom函數這裏。
	3)	一但調用connect,就可以使用send函數
*/
void echo_cli(int sock)
{
	struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(8002);
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

	//3 udp 也可以 調用connet
	//udp調用connet,並沒有三次握手,只是維護了一個狀態信息(和對等方的)。。。
	connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr));

	int ret;
	char sendbuf[1024] = {0};
	char recvbuf[1024] = {0};
	while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
	{	
		//2如果	connect 已經指定了對方的地址。
		//send可以這樣寫 sendto(sock, sendbuf, strlen(sendbuf), 0, NULL, 0);
		
		//1sendto第一次發送的時候,會綁定地址
		sendto(sock, sendbuf, strlen(sendbuf), 0, (struct sockaddr*)&servaddr, sizeof(servaddr));
		/*sendto(sock, sendbuf, strlen(sendbuf), 0, NULL, 0);*/
		ret = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, NULL, NULL);
		if (ret == -1)	
		{
			if (errno == EINTR) 
				continue;
			ERR_EXIT("recvfrom");
		}

		fputs(recvbuf, stdout);
		memset(sendbuf, 0, sizeof(sendbuf));
		memset(recvbuf, 0, sizeof(recvbuf));
	}

	close(sock);
}

運行結果:
此時只運行客戶端
在這裏插入圖片描述

例3 測試發送的數據長度爲0

/*
	udp是無連接的,
	udp發送長度爲0的包,此時會形成
	ip首部(ipv4是20字節,ipv6是40字節) + 8字節的udp首部
	而沒有數據的IP數據報
	接收數據長度爲0也是可以的,不像tcp那樣表示對端關閉	
*/
void echo_cli3(int sock)
{
	struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(8002);
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    
	connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr));

	int ret;
	char sendbuf[1024] = {0};
	char recvbuf[1024] = {0};
	while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
	{	
		//發送長度爲0的包
		ret = sendto(sock, sendbuf, 0, 0, (struct sockaddr*)&servaddr, sizeof(servaddr));
		printf("sendto發送的長度爲0的數據報:%d\n", ret);
		
		ret = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, NULL, NULL);
		printf("recvfrom接收的數據長度爲:%d\n", ret);
		
		if (ret == -1)	
		{
			if (errno == EINTR) 
				continue;
			ERR_EXIT("recvfrom");
		}

		fputs(recvbuf, stdout);
		memset(sendbuf, 0, sizeof(sendbuf));
		memset(recvbuf, 0, sizeof(recvbuf));
	}

	close(sock);
}

運行結果:
在這裏插入圖片描述

在這裏插入圖片描述

例4 測試數據包不可靠的,缺乏流量控制

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
/*
	UDP協議數據報文截斷
		如果接收到的數據報,大於緩衝區;報文可以被截斷;後面的部分會丟失
	
	UDP缺乏流量控制
		udp緩衝區寫滿以後,沒有流量控制機制,會覆蓋緩衝區。
	
	UDP協議數據報文截斷
		如果接收到的數據報,大於緩衝區;報文可以被截斷;後面的部分會丟失。
		
	

*/

#define ERR_EXIT(m) \
        do \
        { \
                perror(m); \
                exit(EXIT_FAILURE); \
        } while(0)

int main(void)
{
	int sock;
	if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
		ERR_EXIT("socket");

	struct sockaddr_in servaddr;
	memset(&servaddr, 0, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(8003);
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

	if (bind(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
		ERR_EXIT("bind");

	
	sendto(sock, "ABCD", 4, 0, (struct sockaddr*)&servaddr, sizeof(servaddr));
	
	//數據報方式。。。。不是字節流
	//如果接受數據時,指定的緩衝區的大小,較小;
	//剩餘部分將要截斷,扔掉
	char recvbuf[1];
	int n;
	int i;
	for (i=0; i<4; i++)
	{
		n = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, NULL, NULL);
		if (n == -1)
		{
			if (errno == EINTR)
				continue;
			ERR_EXIT("recvfrom");
		}
		else if(n > 0)
			printf("n=%d %c\n", n, recvbuf[0]);
	}
	return 0;
}

運行結果
在這裏插入圖片描述

測試代碼

udpcl2.c

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

#define ERR_EXIT(m) \
        do \
        { \
                perror(m); \
                exit(EXIT_FAILURE); \
        } while(0)

/*
	結論:客戶端調用connet和不調connet的區別。
	1)	udp也可以調用connet
	2)	udp客戶端調用了connect以後,不會阻塞在recvfrom函數這裏。
	3)	一但調用connect,就可以使用send函數
*/
void echo_cli(int sock)
{
	struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(8002);
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

	//3 udp 也可以 調用connet
	//udp調用connet,並沒有三次握手,只是維護了一個狀態信息(和對等方的)。。。
	connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr));

	int ret;
	char sendbuf[1024] = {0};
	char recvbuf[1024] = {0};
	while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
	{	
		//2如果	connect 已經指定了對方的地址。
		//send可以這樣寫 sendto(sock, sendbuf, strlen(sendbuf), 0, NULL, 0);
		
		//1sendto第一次發送的時候,會綁定地址
		sendto(sock, sendbuf, strlen(sendbuf), 0, (struct sockaddr*)&servaddr, sizeof(servaddr));
		/*sendto(sock, sendbuf, strlen(sendbuf), 0, NULL, 0);*/
		ret = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, NULL, NULL);
		if (ret == -1)	
		{
			if (errno == EINTR) 
				continue;
			ERR_EXIT("recvfrom");
		}

		fputs(recvbuf, stdout);
		memset(sendbuf, 0, sizeof(sendbuf));
		memset(recvbuf, 0, sizeof(recvbuf));
	}

	close(sock);
}


void echo_cli2(int sock)
{
	struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(8002);
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

	//3 udp 也可以 調用connet
	//udp調用connet,並沒有三次握手,只是維護了一個狀態信息(和對等方的)。。。
	connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr));

	int ret;
	char sendbuf[1024] = {0};
	char recvbuf[1024] = {0};
	while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
	{
		
#if 0
		//2如果	connect 已經指定了對方的地址。	
		sendto(sock, sendbuf, strlen(sendbuf), 0, NULL, 0);
#else
		//一但調用connect,就可以使用send函數
		send(sock, sendbuf, strlen(sendbuf), 0);
#endif
		
		ret = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, NULL, NULL);
		if (ret == -1)	
		{
			if (errno == EINTR) 
				continue;
			ERR_EXIT("recvfrom");
		}

		fputs(recvbuf, stdout);
		memset(sendbuf, 0, sizeof(sendbuf));
		memset(recvbuf, 0, sizeof(recvbuf));
	}

	close(sock);


}

/*
	udp是無連接的,
	udp發送長度爲0的包,此時會形成
	ip首部(ipv4是20字節,ipv6是40字節) + 8字節的udp首部
	而沒有數據的IP數據報
	
	接收數據長度爲0也是可以的,不像tcp那樣表示對端關閉
	
	
*/
void echo_cli3(int sock)
{
	struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(8002);
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    
	connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr));

	int ret;
	char sendbuf[1024] = {0};
	char recvbuf[1024] = {0};
	while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
	{	
		//發送長度爲0的包
		ret = sendto(sock, sendbuf, 0, 0, (struct sockaddr*)&servaddr, sizeof(servaddr));
		printf("sendto發送的長度爲0的數據報:%d\n", ret);
		
		ret = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, NULL, NULL);
		printf("recvfrom接收的數據長度爲:%d\n", ret);
		
		if (ret == -1)	
		{
			if (errno == EINTR) 
				continue;
			ERR_EXIT("recvfrom");
		}

		fputs(recvbuf, stdout);
		memset(sendbuf, 0, sizeof(sendbuf));
		memset(recvbuf, 0, sizeof(recvbuf));
	}

	close(sock);
}
int main(void)
{
	int sock;
	if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
		ERR_EXIT("socket");

	//echo_cli(sock);
	//echo_cli2(sock);
	echo_cli3(sock);
	return 0;
}

udpsrv3.c

#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/wait.h>


#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include "commsocket.h"
#define ERR_EXIT(m) \
        do \
        { \
                perror(m); \
                exit(EXIT_FAILURE); \
        } while(0);
        
        
#include <signal.h>

void handle(int signum)
{
	int pid = 0;
	printf("recv signum:%d \n", signum);
	
	//避免殭屍進程
	while ((pid = waitpid(-1, NULL, WNOHANG) ) > 0)
	{
		printf("退出子進程 pid:%d \n", pid);
		fflush(stdout);
	} 
}

int main(void)
{
	int		ret = 0;
	int 	listenfd;
	
	
	signal(SIGCHLD, handle);
	signal(SIGPIPE, SIG_IGN);
	

	ret = sckServer_init(8001, &listenfd);
	if (ret != 0)
	{
		printf("sckServer_init() err:%d \n", ret);
		return ret;
	}
	
	while(1)
	{
		int ret = 0;
		int wait_seconds = 5;
		int connfd = 0;

		ret = sckServer_accept(listenfd, &connfd,  wait_seconds);
		if (ret == Sck_ErrTimeOut)
		{
			continue;
		}
	
		int pid = fork();
		if (pid == 0)
		{
			unsigned char 	recvbuf[1024];
			int 	recvbuflen = 12;
				
			close(listenfd);
			while(1)
			{
				memset(recvbuf, 0, sizeof(recvbuf));
				ret = sckServer_recv(connfd, recvbuf, &recvbuflen,  5);
				if (ret != 0)
				{
					printf("%s line %d func sckServer_rev() err:%d \n", __FILE__, __LINE__, ret);
					break;
				}
				printf("recvbuflen = %d recvbuf = %s\n ",recvbuflen, recvbuf );
				#if 1
				ret = sckServer_send(connfd,  recvbuf, recvbuflen, 6);
				if (ret != 0)
				{
					printf("%s line %d func sckServer_send() err:%d \n",  __FILE__, __LINE__, ret);
					break;
				}
				#endif
			}
			close(connfd);
			exit(ret);
		}
		else if (pid > 0)
		{
			close(connfd);
		}
	}
	

	return 0;
}

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