首页 » 排名链接 » UDP编程详解(字节地址数据网络结构)

UDP编程详解(字节地址数据网络结构)

落叶飘零 2024-11-01 21:15:04 0

扫一扫用手机浏览

文章目录 [+]

分类: 大端格式:将低位字节数据存储在低地址 ​ 小端格式:将高位字节数据存储在低地址

注意: ​ LSB:低地址 ​ MSB:高地址

如何判断当前系统的字节序:

UDP编程详解(字节地址数据网络结构) 排名链接
(图片来自网络侵删)

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

结果展示:

1.2、字节序转换函数网络协议制定了通讯字节序——大端只有在多字节数据处理时才需要考虑字节序运行在同一台计算上的进程相互通信时,一般不用考虑字节序异构计算机之间通讯,需要转换自己的字节序为网络字节序

在需要字节序转换的时候一般调用特定字节序转换函数

host --> network1 -- 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 --> host3 -- 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位网络字节序数据返回值: 成功:返回主机字节序数据

案例:

#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.3、地址转换函数

人为识别的ip地址是点分十进制的字符串形式,但是计算机或网络中识别的ip地址是整型数据,所以需要转化

1.3.1、inet_pton函数

QT开发交流+赀料君羊:714620761

#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、失败返回其他

案例:

#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.3.2、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、失败返回其他

案例:

#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.3.3、inet_addr()和inet_ntoa()

这两个函数只能用在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地址2、UDP介绍、编程流程2.1、UDP概述

UDP协议

面向无连接的用户数据报协议,在传输数据前不需要先建立连接;目的主机的运输层收到UDP报文后,不需要给出任何确认。

UDP特点

相对TCP速度稍快些;

简单的请求/应答应用程序可以使用UDP;

对于海量数据传输不应该使用UDP;

广播和多播应用必须使用UDP。

UDP应用

DNS(域名解析)、NFS(网络文件系统)、RTP(流媒体)等

一般语音和视频童话都是使用UDP通信的。

2.2、网络编程接口socket

Socket作用:提供不同主机上的进程之间的通信;

Socket特点:

socket也称”套接字“;

是一种文件描述符,代表了一个通信管道的一个端点;

类似对文件的操作一样,可以使用read、write、close等函数对socket套接字进行网络数据的收取和发送等操作;

得到socket套接字(描述符)的方法调用socket()。

socket分类:

SOCK_STREAM, 流式套接字,用于TCP;

SOCK_DGRAM, 数据报套接字,用于UDP;

SOCK_RAW, 原始套接字,对于其他层次的协议操作时需要使用这个类型

2.3、UDP编程C/S架构

UDP网络编程流程:

服务器:

创建套接字socket();

将服务器的ip地址、端口号与套接字进行绑定;

接收数据recvfrom()

发送数据sendto()

客户端:

创建套接字socket()

发送数据sendto()

接收数据recvfrom()

关闭套接字close()

2.4、UDP编程—创建套接字

2.4.1、创建套接字

#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 原始套接字(用于链路层) 特点: 创建套接字时,系统不会分配端口 创建的套接字默认属性是主动的,即主动发起服务的请求,当作为服务器时,往往需要修改为被动的。

2.4.2、创建UDP套接字demo

#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)}2.5、UDP编程—发送、绑定、接收数据

2.5.1、ipv4套接字地址结构

在网络编程中经常使用的结构体

#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字节}

为了使不同格式地址能被传入套接字函数,地址须要强制转换成通用套接字地址结构,原因是因为不同场合所使用的结构体不一样,但是调用的函数却是同一个,所以定义一个通用结构体,当在指定场合使用时,在根据要求传入指定的结构体即可。

通用结构体sockaddr

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

2.5.2、两种地址结构使用场合

在定义源地址和目的地址结构的时候,选用struct sockaddr_in; 例:

struct sockaddr_in my_addr;

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

bind(sockfd,(struct sockaddr)&my_addr,sizeof(my_addr));

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

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

2.5.5、绑定bind函数

UDP网络程序想要收取数据需要什么条件?

QT开发交流+赀料君羊:714620761

确定的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;}

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

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;}2.6、UDP编程—client、server

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

2.6.1、C/S架构回顾

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

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

执行结果:

相关文章