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;
}

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