unix网络编程3.1——UDP(一)UDP入门

系列文章

阅读本文需要先阅读下面的文章:

unix网络编程1.1——TCP协议详解(一)

unix网络编程2.1——高并发服务器(一)基础——io与文件描述符、socket编程与单进程服务端客户端实现

unix网络编程2.2——高并发服务器(二)多进程与多线程实现

unix网络编程2.3——高并发服务器(三)多路IO复用之select

unix网络编程2.4——高并发服务器(四)epoll基础篇

unix网络编程2.5——高并发服务器(五)epoll进阶篇——基于epoll实现reactor

unix网络编程2.6——高并发服务器(六)基于epoll&&reactor实现http服务器

unix网络编程2.7——高并发服务器(七)基于epoll&&reactor实现web_socket服务器

unix网络编程2.8——高并发服务器(八)unix网络编程系统调用与网络协议栈

UDP数据格式

image

  • 通常接收端的UDP协议层将收到的数据放在一个固定大小的缓冲区中等待应用程序来提取和处理,如果应用程序提取和处理的速度很慢,而发送端发送的速度很快,就会丢失数据包,UDP协议层并不报告这种错误。

  • UDP是面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据,这一点和TCP是很不同的。

  • 相较于TCP而言,UDP通信的形式更像是发短信。不需要在数据传输之前建立、维护连接。只专心获取数据就好。省去了三次握手的过程,通信速度可以大大提高,但与之伴随的通信的稳定性和正确率便得不到保证。因此,我们称UDP为“无连接的不可靠报文传递”。

  • UDP有哪些优点和不足呢?由于无需创建连接,所以UDP开销较小,数据传输速度快,实时性较强。多用于对实时性要求较高的通信场合,如视频会议、电话会议等。但随之也伴随着数据传输不可靠,传输数据的正确率、传输顺序和流量都得不到控制和保证。所以,通常情况下,使用UDP协议进行数据传输,为保证数据的正确性,我们需要在应用层添加辅助校验协议来弥补UDP的不足,以达到数据可靠传输的目的。

缓冲区满,接收数据丢包

  • 与TCP类似的,UDP也有可能出现缓冲区被填满后,再接收数据时丢包的现象。由于它没有TCP滑动窗口的机制,通常采用如下两种方法解决:
  1. 服务器应用层设计流量控制,控制发送数据速度。
  2. 借助setsockopt函数改变接收缓冲区大小。如:
  #include <sys/socket.h>
  int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
  int n = 220x1024
  setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n));

UDP CS模型

image

server.c

  #include <string.h>
  #include <netinet/in.h>
  #include <stdio.h>
  #include <unistd.h>
  #include <strings.h>
  #include <arpa/inet.h>
  #include <ctype.h>
  
  #define MAXLINE 80
  #define SERV_PORT 6666
  
  int main(void)
  {
  	struct sockaddr_in servaddr, cliaddr;
  	socklen_t cliaddr_len;
  	int sockfd;
  	char buf[MAXLINE];
  	char str[INET_ADDRSTRLEN];
  	int i, n;
  
  	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(SERV_PORT);
  
  	bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
  	printf("Accepting connections ...\n");
  
  	while (1) {
  		cliaddr_len = sizeof(cliaddr);
  		n = recvfrom(sockfd, buf, MAXLINE, 0, (struct sockaddr *)&cliaddr, &cliaddr_len);
  		if (n == -1) {
  			perror("recvfrom error");
        }   
  		printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port));
  		for (i = 0; i < n; i++) {
            buf[i] = toupper(buf[i]);
        }
  
  		n = sendto(sockfd, buf, n, 0, (struct sockaddr *)&cliaddr, sizeof(cliaddr));
  		if (n == -1) {
  			perror("sendto error");
        }
  	}
  	close(sockfd);
  	return 0;
  }

client.c

  #include <stdio.h>
  #include <string.h>
  #include <unistd.h>
  #include <netinet/in.h>
  #include <arpa/inet.h>
  #include <strings.h>
  #include <ctype.h>
  
  #define MAXLINE 80
  #define SERV_PORT 6666
  
  int main(int argc, char *argv[])
  {
  	struct sockaddr_in servaddr;
  	int sockfd, n;
  	char buf[MAXLINE];
  
  	sockfd = socket(AF_INET, SOCK_DGRAM, 0);
  
  	bzero(&servaddr, sizeof(servaddr));
  	servaddr.sin_family = AF_INET;
  	inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
  	servaddr.sin_port = htons(SERV_PORT);
  
  	while (fgets(buf, MAXLINE, stdin) != NULL) {
  		n = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
  		if (n == -1)
  			perror("sendto error");
  		n = recvfrom(sockfd, buf, MAXLINE, 0, NULL, 0);
  		if (n == -1)
  			perror("recvfrom error");
  		write(STDOUT_FILENO, buf, n);
  	}
  	close(sockfd);
  	return 0;
  }

sendto、recvfrom的坑

  • 结论:
  1. udp sendto 函数发送消息的最大值是 65507
  2. 实际使用udp发送数据,需要考虑MTU分包,因此实际发送时需要使发送包大小 小于 1500字节(实际更小);基于udp在应用层实现可靠性传输时,更需要对包进行分片,标记,因此更需要考虑这个情况
    image
posted @ 2022-12-26 22:03  胖白白  阅读(111)  评论(0编辑  收藏  举报