UDP协议&&UDP广播通信

发布时间 2023-09-09 09:01:26作者: 若达萨罗

UDP协议

  1. 概念
    传输层主要的应用协议模型有,TCP,UDP两种。TCP协议占主导地位,绝大多数网络都是借助TCP协议完成数据传输,但UDP也是不了或缺的重要通信手段
    相较于TCP,UDP通信形式像发短信。不需要建立连接。只需要专心获取数据就可以,省去了三次握手,通信速度可以大大提高,伴随着通信的稳定性与正确得不到保障。因此就称为无连接的不可靠的传输
    UDP无需连接,开销小,数据传输快,实时性较强,多用于视频会议,电话会议,短视频直播。由于数据的准确率得不到保证,为了保证准确性,在应用层添加辅助的校验协议来弥补UDP的不足,以达到可靠传输的目的

  2. C/S模型--UDP通信流程

    1. 客户端
      image
      由于UDP不需要维护连接,程序逻辑简单明了许多,但UDP是不可靠的,保证通讯可靠性需要在应用层实现
    • 建立套接字
      #include <sys/types.h>
      #include <sys/socket.h>
      
      	// 函数作用:建立套接字,返回套接字文件描述符
      	int socket(
      		int domain,      // 参数1:选择地址族   AF_INET:IPV4    AF_INET6:IPV6
      				// 两者在windows下没有任何区别,在linux/unix有细微差别
      				// PF_INET---Protocol--->在创建套接字的使用
      				// AF_INET---Address--->结构体定义地址的时候
      		int type,       // 参数2:选择哪一种协议   SOCK_STREAM:TCP流式   SOCK_DGRAM:UDP数据报套接字
      		int protocol   // 参数3:0:表示默认协议
      	);
      	返回值:成功:套接字描述符   失败:-1
      
    • 直接发送
      #include <sys/socket.h>
      
      	ssize_t sendto(
      		int socket,             //参数1:套接字文件描述符
      		const void *message,   // 参数2:需要发送的数据
      		size_t length,        // 参数3:数据的长度
      		int flags,           // 参数4:一般设置为0
      		const struct sockaddr *dest_addr,   // 参数5:对方的IP地址和端口号
      		socklen_t dest_len                 // 参数6:结构体的大小
      	);
      	返回值:成功返回发送的字节数   失败返回-1
      
    • 关闭连接---->close(sockfd);
    1. 服务端
    • 绑定自己的IP和端口号
      #include <sys/types.h>
      #include <sys/socket.h>
      	// 作用:绑定自己的IP地址和端口号
      	int bind(
      		int sockfd,   // 参数1:sockfd--->套接字文件描述符
      		const struct sockaddr *addr,   //参数2:自己的IP地址和端口号
      		socklen_t addrlen   // 参数3:地址的大小长度
      	);
      	// 返回值:成功返回0 失败返回-1
      
    • 接收数据
      #include <sys/socket.h>
      
      	ssize_t recvfrom(
      		int socket,   // 参数1:sockfd--->套接字文件描述符
      		void *restrict buffer,   // 参数2:接受的数据存储在数组
      		size_t length,   // 参数3:数据长度
      		int flags,   // 参数4:一般设置为0
      		struct sockaddr *restrict address,   // 参数5:客户端的IP和端口号
      		socklen_t *restrict address_len   // 参数6:结构体的大小
      	);
      

    UDP互相通信

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

struct sockaddr_in clientAddr;
void* func(void* arg)
{
	int sock_fd = *((int*)arg);
	while(1)
	{
		printf("data: ");
		char buf[1024] = "";
		scanf("%s", buf);
		sendto(sock_fd, buf, strlen(buf), 0, (struct sockaddr *)&clientAddr, sizeof(clientAddr));
	}
}

int main(int argc, char const *argv[])
{
	// 建立套接字
	int sock_fd = socket(PF_INET, SOCK_DGRAM, 0);


	// 作用:绑定自己的IP地址和端口号
	struct sockaddr_in ownAdd;
	ownAdd.sin_family = AF_INET;
	ownAdd.sin_port = htons(5000);
	ownAdd.sin_addr.s_addr = inet_addr("192.168.1.153");
	bind(sock_fd, (struct sockaddr *)&ownAdd, sizeof(ownAdd));



	//3.定义一个结构体,存储对方的IP地址与端口号
	int len=sizeof(clientAddr);

    pthread_t tid;
    pthread_create(&tid, NULL, func, &sock_fd);

	while(1)
	{
		char buf[1024] = "";
		recvfrom(sock_fd, buf, sizeof(buf), 0, (struct sockaddr *)&clientAddr, &len);
		printf("来自[%s]:[%u]: %s\n", inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port),buf);
	}

	close(sock_fd);
	pthread_join(tid, NULL);

	return 0;
}


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

struct sockaddr_in serverAdd;
int len=sizeof(serverAdd);
void* func(void* arg)
{
	int sock_fd = *((int*)arg);
	while(1)
	{
		char buf[1024] = "";		
		recvfrom(sock_fd, buf, sizeof(buf), 0, (struct sockaddr *)&serverAdd, &len);
		printf("来自[%s]:[%u]: %s\n", inet_ntoa(serverAdd.sin_addr),ntohs(serverAdd.sin_port),buf);
	}
}


int main(int argc, char const *argv[])
{
	// 建立套接字
	int sock_fd = socket(PF_INET, SOCK_DGRAM, 0);

	// 存储对方的IP和端口号
	serverAdd.sin_family = AF_INET;
	serverAdd.sin_port = htons(5000);
	serverAdd.sin_addr.s_addr = inet_addr("192.168.1.153");

	// 绑定自己的IP和端口号
	struct sockaddr_in clientAddr;
	clientAddr.sin_family = AF_INET;
	clientAddr.sin_port = htons(5000);
	clientAddr.sin_addr.s_addr = inet_addr("192.168.1.153");

	pthread_t tid;
    pthread_create(&tid, NULL, func, &sock_fd);
	
	while(1)
	{
		printf("data: ");
		char buf[1024] = "";
		scanf("%s", buf);
		sendto(sock_fd, buf, strlen(buf), 0, (struct sockaddr *)&serverAdd, sizeof(serverAdd));
	}

	close(sock_fd);
	pthread_join(tid, NULL);

	return 0;
}

UDP广播通信

  1. 什么是广播

    • 单播:数据包的发送方式是有一个接收方
    • 广播:同时发给局域网中的所有的主机
  2. 特点
    只有用户数据报套接字(使用UDP协议)才能广播

  3. 广播地址
    以192.168.1.0网段为例,192.168.1.255代表该网段的广播地址。发送给该地址的数据包被所有的主机接收

  4. 实现广播的过程
    广播的发送端

    1. 创建数据报套接字
      int sock_fd = socket(PF_INET, SOCK_DGRAM, 0);

    2. 设置sock_fd套接字文件的属性为广播:SO_BROADCAST

      #include <sys/socket.h>
      
      int setsockopt(
      	int socket,   //参数1:套接字文件描述符
      	int level,   //参数2:一般设置成SOL_SOCKET
      	int option_name,   //参数3:一般设置成SO_BROADCAST
      	const void *option_value,   //参数4:代表设置的值
      	socklen_t option_len   //参数5:设置的值的长度
      );
      
    3. 发送数据,指定接收方为广播地址

      struct sockaddr_in sendAddr;
      sendAddr.sin_family=AF_INET;
      sendAddr.sin_port=htons(10000);
      sendAddr.sin_addr.s_addr=inet_addr("192.168.1.255");
      sendto(sock_fd, buf, strlen(buf), 0, (struct sockaddr*)&sendAddr, sizeof(sendAddr));
      

    广播的接收端

    1. 创建数据报套接字
      int sock_fd = socket(PF_INET, SOCK_DGRAM, 0);
    2. 绑定自己的IP
      struct sockaddr_in ownAddr;
      ownAddr.sin_family=AF_INET;
      ownAddr.sin_port=htons(10000);
      ownAddr.sin_addr.s_addr=htonl(INADDR_ANY);
      bind(socketfd,(struct sockaddr *)&ownAddr,sizeof(struct sockaddr_in));
      
    3. 接收数据recvfrom()
    4. 关闭连接close()

UDP组播(群聊)

  1. 概念

    • 组播介于单播与广播之间,在一个局域网内,将某些主机添加到组中,并设置一个组地址。我们只需要将数据发给组播地址就可以了,加入到该组的所有主机都能接收到数据
  2. 组播的特点

    • 需要给组播设置IP地址,该IP必须是D类地址
    • 只有UDP才能设置组播
  3. IP地址分类

    • IP地址=网络号+主机号
      网络号:指的是不同的网络(不同的网段)
      主机号:在同一网段下用来识别不同的主机。也就是说,主机号占得位数越多,在该网段下的主机越多

      A类地址:保留给政府机构使用
      A类IP地址1字节的网络号+3字节主机号(网络地址最高位必须是0)
      A类IP地址范围1.0.0.1-126.255.255.254

      B类地址:分配给中等规模公司
      B类IP地址2字节的网络号+2字节主机号(网络地址最高位必须是10)
      B类IP地址范围:128.0.0.1-191.255.255.254

      C类地址:分配给任何需要的人
      C类IP地址3字节的网络号+1字节主机号(网络地址最高位必须是110)
      C类IP地址范围192.0.0.1-223.255.255.254

      D类地址:用于组播
      D类IP地址范围224.0.0.1-239.255.255.254

      E类地址:用于实验
      E类IP地址范围240.0.0.1-255.255.255.254

    • 特殊地址
      每一个字节都为0的地址:(0.0.0.0)对应当前主机
      INADDR_ANY:代表当前主机所有的地址
      127.0.0.1:回环地址(在当前主机内部自动形成闭环的网络)主要用于主机内部不同的应用程序通信
      如果你确定当前的客户端和服务器都在同一主机上运行,那么就可以使用这个地址

  4. 接收端怎么接收组播消息?--->需要加入组播属性的套接字
    image

  5. 组播的通信过程
    发送端:

    • 创建UDP套接字
    • 发送数据到组播地址(224.0.0.10)
    • 关闭

    接收端(要把接收端IP地址加入到组播里面)

    • 创建UDP数据报套接字

    • 定义组播结构体
      struct ip_mreq vmreq

    • 设置组播IP(初始化组播结构体)

      vmreq.imr_multiaddr=224.0.0.10 (错误写法)
      vmreq.imr_interface=192.168.1.143 (错误写法)
      inet_pton(AF_INET, "224.0.0.10", &vmreq.imr_multiaddr);
      inet_pton(AF_INET, "192.168.1.143", &vmreq.imr_interface);
      
      #include <arpa/inet.h>
      
      	int inet_pton(int af, const char *src, void *dst);
      
    • 加入组播属性(设置这个套接字可以接收组播信息)
      setsockopt(sock_fd, IPPROTO_IP, IP_)

    • 绑定地址

      struct sockaddr_in saddr;
      saddr.sin_family = AF_INET;
      saar.sin_port(atoi(argv[1]))
      saddr.sin_addr.s_addr = htonl(INADDR_ANY);
      bind(sock_fd, (struct sockaddr*)&saddr, sizeof(saddr));
      

多进程并发服务器

使用多进程并发服务器时要考虑以下几点

  1. 父进程最大文件描述符(父进程需要close关闭,accept返回最新的文件描述符)
  2. 系统内创建进程的个数(与内存大小有关)
  3. 进程创建过多是否会降低整体的服务性能(进程调度)

多线程并发服务器

在使用线程模型开发服务器时需要考虑以下问题:
- 调整进程内最大的文件描述符上限
- 线程如有共享数据,考虑线程同步
- 服务器客户端线程退出,退出处理(分离属性)
- 系统负载,随着连接客户端增加,导致其他线程及时得到CPU

端口复用

  1. 端口复用的作用
    • 端口复用真正用处是主要在服务器编程:当服务器需要重启的时候,经常会碰到端口尚未关闭,这个时候如果不设置端口复用,则无法完成绑定,因为端口可能还处于被别的套接口绑定的状态中