Programación UDP detallada

1.1 Preparación de la programación: orden de bytes, conversión de direcciones

1.1.1 Descripción general de Endian

Concepto de orden de bytes: se refiere al orden de almacenamiento de datos de varios bytes

Categoría:
Formato big-endian: almacenar datos de bytes de orden bajo en una dirección baja
Formato Little-endian: almacenar datos de bytes de orden alto en una dirección baja

Nota:
LSB: dirección baja
MSB: dirección alta

Cómo determinar el orden de bytes del sistema actual:

#include <stdio.h>

union un
{
    
    
	int a;
	char b;
}

int main()
{
    
    
	union un myun;
	myun.a = 0x12345678;
	printf("a = %#x\n",myun.a);
	printf("b = %#x\n",myun.b);
	
	if(myun.b == 0x78){
    
    
		printf("小端存储\n");
	}
	else{
    
    
		printf("大端存储\n");
	}
	
	return 0;
}

La pantalla de resultados:

1.1.2 Función de conversión de orden de bytes
  1. El protocolo de red ha desarrollado un orden de bytes de comunicación - big endian
  2. Endianness solo debe tenerse en cuenta cuando se trata de datos de varios bytes
  3. Cuando los procesos que se ejecutan en la misma computadora se comunican entre sí, generalmente no se considera el orden de los bytes.
  4. Para comunicarse entre computadoras heterogéneas, debe convertir su propio orden de bytes en el orden de bytes de la red.

Cuando se requiere la conversión de orden de bytes, generalmente se llama a una función específica de conversión de orden de bytes.

host --> network
1 -- htonl
#include<arpa/inet.h>
uint32_t htonl(uint32_t hostint32);
功能:
	将32位主机字节序数据转换成网络字节序数据
参数:
	hostint32:	待转换的32位主机字节序数据
返回值:
	成功:返回网络字节序的值

2 -- htons
#include<arpa/inet.h>
uint16_t htons(uint16_t hostint16);
功能:
	将16位主机字节序数据转换成网络字节序数据
参数:
	hostint16:	待转换的16位主机字节序数据
返回值:
	成功:返回网络字节序的值

network --> host
3 -- ntohl
#include<arpa/inet.h>
uint32_t ntohl(uint32_t netint32);
功能:
	将32位网络字节序数据转换成主机字节序数据
参数:
	netint32:	待转换的32位网络字节序数据
返回值:
	成功:返回主机字节序数据

4 -- ntohs
#include<arpa/inet.h>
uint16_t ntohs(uint16_t netint16);
功能:
	将16位网络字节序数据转换成主机字节序数据
参数:
	netint16:	待转换的16位网络字节序数据
返回值:
	成功:返回主机字节序数据

caso:

#include <stdio.h>
#include <arpa/inet.h>

int main()
{
    
    
	int a = 0x12345678;
	short a = 0x1234;
	
	printf("%#x\n",htonl(a));

	printf("%#x\n",htons(b));

	return 0;
}
1.1.3 Función de conversión de direcciones

La dirección IP reconocida por los humanos tiene la forma de una cadena decimal punteada, pero la dirección IP reconocida en la computadora o la red son datos enteros, por lo que debe convertirse

1.1.3.1 función inet_pton
#include <arpa/inet.h>
int inet_pton(int family,const char *strptr,void *addrptr);
功能:
	将点分十进制数串转换成32位无符号整数
参数:
	family	协议族
			AF_INET			ipv4网络协议
			AF_INET6		iPV6网络协议
	strptr	点分十进制数串
	addrptr	32位无符号整数的地址
返回值:
	成功返回1、失败返回其他

caso:

#include <stdio.h>
#include <arpa/inet.h>

int main()
{
    
    
	char ip_str[] = "198.168.3.103";
	unsigned int ip_int = 0;
	unsigned char *ip_p = NULL;
	
	//将点分十进制ip地址转化为32位无符号整形数据
	inet_pton(AF_INET,ip_str,&ip_int);
	printf("ip_int = %d\n",ip_int;

	ip_p = (char *)&ip_int;
	printf("in_uint = %d,%d,%d,%d\n",*ip_p,*(ip_p+1),*(ip_p+2),*(ip_p+3));

	return 0;
}
1.1.3.2 función inet_ntop()
#include <arpa/inet.h>
const char * inet_ntop(int family,const void *addrptr,char *strptr, size_t len);
功能:
	将32位无符号整数转换成点分十进制数串的ip地址
参数:
	family	协议族
			AF_INET			ipv4网络协议
			AF_INET6		iPV6网络协议
	addrptr	32位无符号整数的地址
	strptr	点分十进制数串
	len		strptr缓存区长度
			len的宏定义
				#define INET_ADDRSTRLEN  16  //for ipv4
				#define INET6_ADDRSTRLEN 46	 //for ipv6
返回值:
	成功返回1、失败返回其他

caso:

#include <stdio.h>
#include <arpa/inet.h>

int main()
{
    
    
	unsigned char ip_int[] = {
    
    198,168,3,103};
	char ip_str[16] = ""; //"198.168.3.103"刚好16个字节
	inet_ntop(AF_INET,&ip_int,ip_str,16);
	printf("ip_s = %s\n",ip_str);
	return 0;
}
1.1.3.3 inet_addr() e inet_ntoa()

Estas dos funciones solo se pueden usar en la conversión de direcciones ipv4

#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

in_addr_t inet_addr(const char *cp);
功能:
	将点分十进制ip地址转化为整型数据
参数:
	cp:点分十进制的ip地址
返回值:
	成功:整型数据
	
char *inet_ntoa(struct in_addr in);
功能:将整型数据转化位点分十进制的ip地址
参数:
	in:	保存ip地址的结构体
返回值:
	成功:点分十进制的ip地址

1.2 Introducción UDP, proceso de programación

1.2.1 Descripción general de UDP

El protocolo UDP
es un protocolo de datagrama de usuario sin conexión que no necesita establecer una conexión antes de transmitir datos; la capa de transporte del host de destino no necesita dar ninguna confirmación después de recibir el mensaje UDP.

Características UDP

  1. Relativamente más rápido que TCP;
  2. Las aplicaciones simples de solicitud/respuesta pueden usar UDP;
  3. UDP no debe usarse para transferencias masivas de datos;
  4. Las aplicaciones de difusión y multidifusión deben utilizar UDP.

Aplicación UDP
DNS (resolución de nombre de dominio), NFS (sistema de archivos de red), RTP (transmisión de medios), etc. Los
cuentos de hadas de voz y video en general usan comunicación UDP.

1.2.2 Conector de interfaz de programación de red

Rol de socket: proporciona comunicación entre procesos en diferentes hosts;

Características del zócalo:

  1. Socket también se llama "socket";
  2. Es un descriptor de archivo que representa un punto final de una canalización de comunicación;
  3. De manera similar a las operaciones en archivos, las funciones como leer, escribir y cerrar se pueden usar para recopilar y enviar datos de red a los sockets;
  4. El método para obtener el socket socket (descriptor) llama a socket().

Clasificación de socket:
SOCK_STREAM, socket de flujo, utilizado para TCP;
SOCK_DGRAM, socket de datagrama, utilizado para UDP;
SOCK_RAW, socket sin formato, este tipo es necesario para otras operaciones de protocolo de capa

1.2.3 Arquitectura C/S de programación UDP

Proceso de programación de la red UDP:

Servidor:
​ Crear socket socket();
​ Vincular la dirección IP del servidor, número de puerto y socket;
​ Recibir datos recvfrom()
​ Enviar datos sendto()

Cliente:
Crear socket socket()
Enviar datos sendto()
Recibir datos recvfrom()
Cerrar socket close()

1.2.4 Programación UDP - crear socket
1.2.4.1 Crear zócalo
#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain, int type, int protocol);
功能:
	创建一个套接字,返回一个文件描述符;
参数:
	domain:通信域,协议族;
			AF_UNIX		本地通信
			AF_INET		ipv4网络协议
			AF_INET6	ipv6网络协议
			AF_PACKET	底层接口
	type:	套接字的类型
			SOCKET_STREAM	流式套接字(TCP)
			SOCKET_DGRAM	数据报套接字(UDP)
			SOCKET_RAW		原始套接字(用于链路层)
	特点:
			创建套接字时,系统不会分配端口
			创建的套接字默认属性是主动的,即主动发起服务的请求,当作为服务器时,往往需要修改为被动的。
1.2.4.2 Crear una demostración de socket UDP
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types>
int main(int argc, char const *argv[]){
    
    
	//使用socket函数创建套接字
	//创建一个用于UDP网络编程的套接字
	int sockfd = 0;
	sockfd = socket(AF_INET,SOCKE_DGRAM,0);
	if(sockfd < 0){
    
    
		perror("socket");
		exit(-1);
	}
	printf("sockfd = %d\n",sockfd);//任何一个进程在开启或创建的时候会分配三个文件描述符(0,1,2)
}
1.2.5 Programación UDP - envío, vinculación, recepción de datos
1.2.5.1 estructura de dirección de socket ipv4

Estructuras de uso frecuente en la programación de redes.

#include <netinet/in.h>

struct in_addr
{
    
    
	in_addr_t s_addr; // 4字节
}

struct sockaddr_in
{
    
    
	sa_family_t sinfamily; //2字节;
	in_port_t 	sin_port;  //2字节;
	struct in_addr	sin_addr; //4字节;
	char sin_zero[8]		//8字节
}



Para permitir que las direcciones en diferentes formatos se pasen a la función de socket, la dirección debe convertirse en una estructura de dirección de socket común. La razón es que la estructura utilizada en diferentes ocasiones es diferente, pero la función que se llamará es la mismo, por lo tanto, defina una estructura general, cuando se usa en una ocasión específica, simplemente pase la estructura especificada de acuerdo con los requisitos.

Estructura general sockaddr

头文件#include <netinet/in.h>
struct sockaddr
{
    
    
	sa_family_t sa_family;	//2字节
	char sa_data[14]		//14字节
}
//注意,以上3个结构在Linux系统中已经定义
1.2.5.2 Ocasiones de aplicación de dos estructuras de dirección

Al definir la estructura de la dirección de origen y la dirección de destino, elija struct sockaddr_in;
ejemplo:

struct sockaddr_in my_addr;

当调用编程接口函数,且该函数需要从传入地址结构时需要用struct sockaddr进行强制转换
例:

bind(sockfd,struct sockaddr*)&my_addr,sizeof(my_addr));
1.2.5.3 发送数据—sendto函数
ssize_t sendto(int sockfd,const void *buf,size_t nbytes,int flags,const struct sockaddr *to, socklen_t addrlen);
功能:
	向to结构体指针中指定的ip,发送UDP数据
参数:
	sockfd:		套接字
	buf:		发送数据缓存区
	nbytes:		发送数据缓存区的大小
	flags:		一般为0
	to:			指向目的主机地址结构体的指针
	addrlen:	to指向内容的长度
注意:
	通过to和addrlen确定目的地址
	可以发送0长度的UDP数据包
返回值:
	成功:发送数据的字符数
	失败:-1
1.2.5.4 向“网络调试助手”发送消息

设置网络调试助手(在windows下运行的软件)中的属性
注意:ip地址不能随意设置,必须是当前windows的ip地址

ubuntu下客户段的代码编写:

#include <stdio.h>			//printf
#include <stdlib.h>			//exit
#include <sys/types.h>
#include <sys/socket.h>		//socket
#include <netinet/in.h>		//sockaddr_in
#include <arpa/inet.h>		//htons	inet_addr
#include <unisted.h>		//close
#include <string.h>

int main()
{
    
    
	int sockfd;
	if((sockfd = socket(AF_INET,SOCK_DGRAM,0)) == -1)
	{
    
    
		perrpr("fail to socket");
		exit(1);
	}
	printf("sockfd = %d",sockfd);
	
	//第二步:填充服务器网络信息结构体	sockaddr_in(一般不需要,系统会自动分配)
	struct sockaddr_in	severaddr;
	serveraddr.sin_family = AF_INET;	//协议族	AF_INET:ipv4网络协议
	serveraddr.sin_addr.s_addr = inet_addr("192.168.3.78");	//ip地址
	serveraddr.sin_port = htons(8080);
	socklen_t addrlen = sizeof(serveraddr)
	
	//第三步:发送数据
	char buf[128] = "";
	while(1)
	{
    
    
		fgets(buf,128,stdin);
		buf[strlen(buf) - 1] = '\0'; 	//把buf字符串中的\n转化为\0
		if(sendto(sockfd,buf,N,0,(struct sockaddr *)&serveraddr,addrlen));
		{
    
    
			perror("fail to sendto");
			exit(1);
		}
	}
	
	//第四步:关闭套接字文件描述符
	close(socketfd);
	
	return 0;
}
1.2.5.5 绑定bind函数

UDP网络程序想要收取数据需要什么条件?
确定的ip地址
确定的port

怎么完成上面的条件呢?
​ 接收端:使用bind函数,来完成地址结构与socket套接字的绑定,这样ip、port就固定了;
​ 发送端:在sendto函数中指定接收端的ip、port,就可以发送数据了。

由于服务器是被动的,客户端是主动的,所以一般先运行服务器,后运行客户端,所以服务器需要固定自己的信息(ip地址和端口号),这样客户端才可以找到服务器并与之通信,但是客户端一般不需要bind绑定,因为系统会自动给客户端分配ip地址和端口号。

int bind(int sockfd,const struct sockaddr *myaddr,socklen_t addrlen);
功能:
	将本地协议地址与sockfd绑定
参数:
	sockfd:		文件描述符,socket的返回值
	addr:		网络信息结构体
					通用结构体(一般不用)
						struct sockaddr
					网络信息结构体	sockaddr_in
						#include<netinet/in.h>
						struct sockaddr_in
	addrlen:	addr的长度
返回值:
	成功:0
	失败:-1
	

示例:

#include <stdio.h>			//printf
#include <stdlib.h>			//exit
#include <sys/types.h>
#include <sys/socket.h>		//socket
#include <netinet/in.h>		//sockaddr_in
#include <arpa/inet.h>		//htons	inet_addr
#include <unisted.h>		//close
#include <string.h>

int main(int argc, char **argv)
{
    
    
	if(argc < 3){
    
    
		fprintf(stderr,"Useage: %s ip port\n",argv[0]);
		exit(1);
	}
	
	//第一步:创建套接字
	int sockfd;
	if((sockfd = socket(AF_INET,SOCK_DGRAM,0))== -1)
	{
    
    
		perror("fail to socket");
		exit(1);
	}
	//第二步:将服务器的网络信息结构绑定前进行填充
	struct sockaddr_in serveraddr;
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = inet_addr(agrv[1]);
	serveraddr.sinport = htons(atoi(argv[2]));
	
	//第三步:将网络信息结构体与套接字绑定
	if(bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr))==-1){
    
    
		perror("fail to bind");
		exit(1);
	}
	
	return 0;
}
1.2.5.6 接收数据—recvfrom函数
ssize_t recvfrom(int sockfd,void *buf,size_t nbytes,int flags, struct sockaddr *from,socklen_t *addrlen);
功能:
	接收UDP数据,并将源地址信息保存在from指向的结构中
参数:
	sockfd:		套接字
	buf:		接收数据缓冲区
	nbytes:		接收数据缓冲区的大小
	flags:		套接字标志(常为0)
					0				阻塞
					MSG_DONTWAIT	非阻塞
	from:		源地址结构体指针,用来保存数据的来源
	addrlen:	from所指内容的长度
注意:
	通过from和addrlen参数存放数据来源信息
	from和addrlen可以为NULL,表示不保存数据来源
返回值:
	成功:接收到的字符数
	失败:-1
1.2.5.7 接收“网络调试助手”的数据

此时网络调试助手作为客户端,ubuntu的程序作为服务器

设置客户端(网络调试助手)

设置服务器(ubuntu程序)

#include <stdio.h>			//printf
#include <stdlib.h>			//exit
#include <sys/types.h>
#include <sys/socket.h>		//socket
#include <netinet/in.h>		//sockaddr_in
#include <arpa/inet.h>		//htons	inet_addr
#include <unisted.h>		//close
#include <string.h>

int main(int argc, char **argv)
{
    
    
	if(argc < 3){
    
    
		fprintf(stderr,"Useage: %s ip port\n",argv[0]);
		exit(1);
	}
	
	//第一步:创建套接字
	int sockfd;
	if((sockfd = socket(AF_INET,SOCK_DGRAM,0))== -1)
	{
    
    
		perror("fail to socket");
		exit(1);
	}
	//第二步:将服务器的网络信息结构绑定前进行填充
	struct sockaddr_in serveraddr;
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = inet_addr(agrv[1]);			//192.168.3.103
	serveraddr.sinport = htons(atoi(argv[2]));					//9999
	
	//第三步:将网络信息结构体与套接字绑定
	if(bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr))==-1){
    
    
		perror("fail to bind");
		exit(1);
	}
	
	//接收数据
	char buf[128] = "";
	struct sockaddr_in clientaddr;
	socklen_t addrlen = sizeof(struct sockaddr_in);
	while(1){
    
    
		if(recvfrom(sockfd,128,0,(struct sockaddr *)&clientaddr,&addrlen) == -1){
    
    
			perror("fail to recvfrom");
			exit(1);
		}
	}
	
	//打印数据
	//打印客户端的ip地址和端口号
	printf("ip:%s,port:%d\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port));
	//打印接收到数据
	printf("from client:%s\n",buf);
	
	
	return 0;
}
1.2.6 UDP编程—client、server

其中在网络编程开发中client和server双方既可以有发送数据还可以接收数据;一般认为提供服务的一方为server,而接受服务的另一方为client。

1.2.6.1 C/S架构回顾
1.2.6.2 UDP客户端注意点

1.本地IP、本地端口(我是谁)
2.目的IP、目的端口(发给谁)
3.在客户端的代码中,我们只设置了目的IP、目的端口

客户端的本地ip、本地port是我们调用sendto的时候linux系统底层自动给客户端分配的;分配端口的方式为随机分配,即每次运行系统给的port不一样

//UDP客户端的实现
#include <stdio.h>			//printf
#include <stdlib.h>			//exit
#include <sys/types.h>
#include <sys/socket.h>		//socket
#include <netinet/in.h>		//sockaddr_in
#include <arpa/inet.h>		//htons	inet_addr
#include <unisted.h>		//close
#include <string.h>

int main(int argc,char **argv)
{
    
    
	if(argc < 3){
    
    
		fprintf(stderr,"Usage: %s <ip> <port>\n",agrv[0]);
		exit(1);
	}
	
	int sockfd;		//文件描述符
	struct sockaddr_in serveraddr;		//服务器网络信息结构体
	socklen_t addrlen = sizeof(serveraddr);
	
	//第一步:创建套接字
	if((sockfd = socket(AF_INET,SOCK_DGRAM,0)) < 0){
    
    
		perror("fail to socket");
		exit(1);
	}
	
	//客户端自己指定自己的ip地址和端口号,一般不需要,系统会自动分配
	struct sockaddr_in clientaddr;
	clientaddr.sin_family = AF_INET;
	clientaddr.sin_addr.s_addr = inet_addr(argv[3]); 	//客户端的ip地址
	clientaddr.sin_port = htons(atoi(argv[4]));			//客户端的端口号
	
	//第二步:填充服务器网络信息结构体
	//inet_addr:将点分十进制字符串ip地址转换为整型数据
	//htons:将主机字节序转化为网络字节序
	//atoi:将数字型字符串转化为整型数据
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
	serveraddr.sin_port = htons(atoi(argv[2]));
	
	//第三步:进行通信
	char buf[32] = "";
	while(1){
    
    
		fgets(buf,sizeof(buf),stdin);
		buf[strlen(buf) - 1] = '\0';
		
		if(sendto(sockfd,buf,sizeof(buf),(struct sockaddr *)&serveraddr,sizeof(serveraddr)))
		{
    
    
			perror("fail to sendto");
			exit(1);
		}
		
		char text[32] = "";
		if(recvfrom(sockfd,text,sizeof(text),0,(struct sockaddr *)&serveraddr,&addrlen) == -1){
    
    
			perror("fail to recvfrom");
			exit(1);
		}
		printf("from server:%s\n",text);
	}
	
	//第四步:关闭文件描述符
	close(sockfd);
	
	return 0;
}
1.2.6.3 UDP服务器注意点

1.服务器之所以要bind是因为它的本地port需要是固定的,而不是随机的
2.服务器也可以主动地给客户端发送数据
3.客户端也可以用bind,这样客户端的本地端口就是固定的了,但一般不这样做。

//UDP服务器的实现
#include <stdio.h>			//printf
#include <stdlib.h>			//exit
#include <sys/types.h>
#include <sys/socket.h>		//socket
#include <netinet/in.h>		//sockaddr_in
#include <arpa/inet.h>		//htons	inet_addr
#include <unisted.h>		//close
#include <string.h>

int main(int argc,char **argv)
{
    
    
	if(argc < 3){
    
    
		fprintf(stderr,"Usage: %s <ip> <port>\n",agrv[0]);
		exit(1);
	}
	
	int sockfd;		//文件描述符
	struct sockaddr_in serveraddr;		//服务器网络信息结构体
	socklen_t addrlen = sizeof(serveraddr);
	
	//第一步:创建套接字
	if((sockfd = socket(AF_INET,SOCK_DGRAM,0)) < 0){
    
    
		perror("fail to socket");
		exit(1);
	}
	
	//第二步:填充服务器网络信息结构体
	//inet_addr:将点分十进制字符串ip地址转换为整型数据
	//htons:将主机字节序转化为网络字节序
	//atoi:将数字型字符串转化为整型数据
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
	serveraddr.sin_port = htons(atoi(argv[2]));
	
	//第三步:将套接字与服务器网络信息结构体绑定
	if(bind(sockfd,(struct sockaddr *)&serveraddr, addrlen) < 0){
    
    
		perror("fail to bind");
		exit(1);
	}
	
	while(1){
    
    
		//第四步:进行通信
		char text[32] = "";
		struct sockaddr_in clientaddr;
		if(recvfrom(sockfd,text,sizeof(text),0,(struct sockaddr *)&clientaddr,&addrlen) == -1){
    
    
			perror("fail to recvfrom");
			exit(1);
		}
		printf("[%s - %d]: %s\n",inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port),text);
		
		strcat(text,"*_*");
		
		if(sendto(sockfd,text,sizeof(text),0,(struct sockaddr *)&clientaddr,addrlen)){
    
    
			perror("fail to sendto");
			exit(1);
		}
	}
	
	//第四步:关闭文件描述符
	close(sockfd);
	
	return 0;
}

执行结果:

Supongo que te gusta

Origin blog.csdn.net/AAAA202012/article/details/127233159
Recomendado
Clasificación