一、UDP的概念
1.1、UDP——面向无连接(无连接,是因为UDP里有了对方的地址,直接发就好)
特点:
1、邮件系统服务模式的抽象
2、每个分组都携带完整的目的地址
3、不能保证分组的先后顺序
4、不进行分组出错的恢复和重传
5、不保证数据传输的可靠性
1.2、UDP协议
面向无连接的用户数据报协议,在传输数据前不需要先建立连接;目地主机的运输层收到 UDP 报文后,不需要给出任何确认。
UDP 其它特点
1、相比 TCP 速度稍快些
2、简单的请求/应答应用程序可以使用 UDP
3、对于海量数据传输不应该使用 UDP
4、广播和多播应用必须使用 UDP
UDP 应用
DNS(域名解析)、NFS(网络文件系统)、RTP(流媒体)等
二、网络编程接口socket
2.1、socket 作用
提供不同主机上的进程之间的通信
2.2、socket 特点
1、socket 也称“套接字”
2、是一种文件描述符,代表了一个通信管道的一个端点
3、类似对文件的操作一样,可以使用 read、write、close 等函数对 socket 套接字进行网络 数据的收取和发送等操作。
4、得到 socket 套接字(描述符)的方法调用 socket()
三、UDP编程C/S架构
四、创建 socket 套接字
头文件:
#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:套接字的类型
SOCK_STREAM 流式套接字(TCP)
SOCK_DGRAM 数据报套接字(UDP)本章学习UDP
SOCK_RAW 原始套接字(用于链路层)
protocol:附加协议,如果不需要,则设置为0
返回值:
成功:文件描述符
失败:‐1
特点:
创建套接字时,系统不会分配端口
创建的套接字默认属性是主动的,即主动发起服务的请求;当作为服务器时,往往需要修改为被动的。
五、创建UDP套接字
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
//使用socket函数创建套接字
//创建一个用于UDP网络编程的套接字
int sockfd;
if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == ‐1)
{
perror("fail to socket");
exit(1);
}
printf("sockfd = %d\n", sockfd);
return 0;
}
运行结果
sockfd = 3
六、UDP编程——发送、绑定、接收数据
6.1、发送数据——sendto()函数
头文件
#include <sys/types.h>
#include <sys/socket.h>
函数:ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
功能:发送数据
参数:
sockfd:文件描述符,socket的返回值
buf:要发送的数据
len:buf的长度
flags:标志位
0 阻塞 (基本都设置为0)
MSG_DONTWAIT 非阻塞
dest_addr:目的网络信息结构体(需要自己指定要给谁发送)
addrlen:dest_addr的长度
返回值:
成功:发送的字节数
失败:‐1
此函数,重点 :const struct sockaddr *dest_addr 如何构建
通过man手册查询:sockaddr通用结构体
头文件:
#include <netinet/in.h>
函数结构体:
struct sockaddr
{
sa_family_t sa_family; // 2字节
char sa_data[14] //14字节
};
但是,在网络编程中经常使用的结构体sockaddr_in
6.2、sockaddr_in(重点)
头文件:
#include <netinet/in.h>
函数结构体:
struct sockaddr_in
{
sa_family_t sin_family; //协议族 2字节
in_port_t sin_port; //端口号 2字节
struct in_addr sin_addr; //ip地址 4字节
char sin_zero[8] //填充,不起什么作用 8字节
};struct in_addr
{
in_addr_t s_addr; //ip地址 4字节
};
6.2.1、sockaddr_in使用说明
在定义源地址和目的地址结构的时候,选用struct sockaddr_in;
例:
struct sockaddr_in my_addr;
当调用编程接口函数,且该函数需要传入地址结构时需要用struct sockaddr进行强制转换
例:
bind(sockfd,(struct sockaddr*)&my_addr,sizeof(my_addr));
七、向"网络调试助手"发送消息(作为服务器)
设置网络调试助手(在windows下运行的软件)中的属性
注意:ip地址不能随意设置,必须是当前windows的ip地址
八、unbantu下客户端发送给windows下运行"网络调试助手"
#include<stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include<stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include<string.h>
#define N 128
int main(int argc, char **argv)
{
if(argc<3)
{
fprintf(stderr,"%s ip post",argv[0]);
exit(1);
}
int sockfd;
if((sockfd=socket(AF_INET, SOCK_DGRAM,0))==-1)
{
perror("fail to sockfd");
exit(1);
}
printf("sockfd = %d \n",sockfd);
struct sockaddr_in myaddr;
myaddr.sin_family=AF_INET;
myaddr.sin_port =htons(atoi(argv[2]));
myaddr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t addrlen=sizeof(myaddr);
char buf[N]="";
while(1)
{
fgets(buf,N,stdin);
// buf[strlen(buf)-1]='\0'; //有这句话,不会换行
if(sendto(sockfd,buf,N,0,(struct sockaddr*)&myaddr,addrlen)==-1)
{
perror("sendto fail");
exit(1);
}
}
close(sockfd);
return 0;
}
运行结果
九、unbantu作为服务器接收数据,网络调试助手作为客户端发送数据
9.1、服务器为客户端服务,因此需要有固定的ip和端口号——bind函数绑定
头文件:
#include <sys/types.h>
#include <sys/socket.h>
函数:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:将套接字与网络信息结构体绑定
参数:
sockfd:文件描述符,socket的返回值
addr:网络信息结构体
struct sockaddr:通用结构体(一般不用)
使用网络信息结构体 sockaddr_in结构体头文件
#include <netinet/in.h>
结构体函数struct sockaddr_in
addrlen:addr的长度
返回值:
成功:0
失败:‐1
9.2、sockaddr_in使用说明
头文件:
#include <netinet/in.h>
函数结构体:
struct sockaddr_in
{
sa_family_t sin_family; //协议族 2字节
in_port_t sin_port; //端口号 2字节
struct in_addr sin_addr; //ip地址 4字节
char sin_zero[8] //填充,不起什么作用 8字节
};struct in_addr
{
in_addr_t s_addr; //ip地址 4字节
};
9.3、bind的绑定示例
#include<stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include<stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc , char **argv[])
{
int sockfd;
if((sockfd=socket(AF_INET,SOCK_DGRAM,0))==-1)
{
perror("fail to sockfd");
exit(1);
}
printf("sockfd= %d\n",sockfd);
//bind的函数
struct sockaddr_in mysockaddr;
mysockaddr.sin_family=AF_INET;
mysockaddr.sin_port =htons(8080);
mysockaddr.sin_addr.s_addr = inet_addr("10.152.203.29");
socklen_t addrlen = sizeof(mysockaddr);
if(bind(sockfd,(struct sockaddr *)&mysockaddr,addrlen)==-1)
{
perror("fail to bind");
exit(1);
}
}
补充知识:
inet_addr
头文件:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
函数:
in_addr_t inet_addr(const char *cp);
功能:将点分十进制ip地址转化为整形数据
此转换的整形数据也是网络字节序
参数:
cp:点分十进制的IP地址
返回值:
成功:整形数据
atoi函数(字符串转化成整数)
头文件
#include<stdlib.h>
函数int atoi(const char *nptr);
功能
把字符串转化为int类型
返回返回转换后的整型数。
端口号,在我们输入时候,是以字符串形式表达,而我们的结构sockaddr_in 接收的是 整形
因此需要: atoi(端口号) 变为整形数。
只是转为整形数,还不行,由于不同计算机之间通讯,需要转换自己的字节序为网络字节序
因此需要: htons(atoi(端口号));
十、接收到数据——recvfrom函数
头文件
#include <sys/types.h>
#include <sys/socket.h>
函数
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,5 struct sockaddr *src_addr, socklen_t *addrlen);
功能:接收数据
参数:sockfd:文件描述符,socket的返回值
buf:保存接收的数据
len:buf的长度
flags:标志位
0 阻塞(基本都设置0)
MSG_DONTWAIT 非阻塞
src_addr:源的网络信息结构体(自动填充,定义变量传参即可)会自动接收对方的ip地址,和端口号,存放此结构体中
addrlen:src_addr的长度
返回值:
成功:接收的字节数
失败:‐1
10.1、recvfrom函数示例
#include<stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include<stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define N 128
int main()
{
char buf[N]="";
struct sockaddr_in recvfrom_addr;
socklen_t recvfrom_addrlen = sizeof(recvfrom_addr);
while(1)
{
if(recvfrom(sockfd,buf,N,0,(struct sockaddr *)&recvfrom_addr,&recvfrom_addrlen)==-1)
{
perror("fail to recvfrom");
exit(1);
}
printf("%s\n",buf);
}
return 0;
}
十一、unbantu作为服务端 (代码示例)
#include<stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include<stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define N 128
int main(int argc , char *argv[])
{
if(argc<3)
{
fprintf(stderr, "%s ip port ",argv[0]);
exit(1);
}
int sockfd;
if((sockfd=socket(AF_INET,SOCK_DGRAM,0))==-1)
{
perror("fail to sockfd");
exit(1);
}
printf("sockfd= %d\n",sockfd);
struct sockaddr_in mysockaddr;
mysockaddr.sin_family=AF_INET;
mysockaddr.sin_port =htons(atoi(argv[2]));
mysockaddr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t addrlen = sizeof(mysockaddr);
if(bind(sockfd,(struct sockaddr*)&mysockaddr,addrlen)<0)
{
perror("fail to bind");
exit(1);
}
char buf[N]="";
struct sockaddr_in recvfrom_addr;
socklen_t recvfrom_addrlen = sizeof(recvfrom_addr);
while(1)
{
if(recvfrom(sockfd,buf,N,0,(struct sockaddr *)&recvfrom_addr,&recvfrom_addrlen)==-1)
{
perror("fail to recvfrom");
exit(1);
}
printf("%s\n",buf);
}
return 0;
}
运行结果
注意:当我们关掉unbantu服务端程序时,如果在运行同一个端口号,会遇到运行错误。需要换一个端口号才可以运行成功。
当我们发送信息时,先把发送的内容写进buf里,然后再传输给对方。当我们再继续发信息时,原先buf里面装的内容不会清除,如果发的信息多,能覆盖之前buf内容,则输出正常。如果发的信息少,不能覆盖原先的buf的内容,只覆盖前面几个数据,后面原先的数据,照常发送给对方。
十二、客户端发信息并能接收服务端回信息(更完善)
12.1、客户端示例代码
#include<stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include<stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include<string.h>
#define N 128
int main(int argc, char **argv)
{
if(argc<3)
{
fprintf(stderr,"%s ip post",argv[0]);
exit(1);
}
int sockfd;
if((sockfd=socket(AF_INET, SOCK_DGRAM,0))==-1)
{
perror("fail to sockfd");
exit(1);
}
printf("sockfd = %d \n",sockfd);
struct sockaddr_in myaddr;
myaddr.sin_family=AF_INET;
myaddr.sin_port =htons(atoi(argv[2]));
myaddr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t addrlen=sizeof(myaddr);
char buf[N]="";
while(1)
{
char context[50]="";
fgets(buf,N,stdin);
// buf[strlen(buf)-1]='\0';
if(sendto(sockfd,buf,N,0,(struct sockaddr*)&myaddr,addrlen)==-1)
{
perror("sendto fail");
exit(1);
}
if(recvfrom(sockfd,context,sizeof(context),0,(struct sockaddr*)&myaddr,&addrlen)==-1)
{
perror("fail to recvfrom");
exit(1);
}
printf("from server: %s\n", context);
}
close(sockfd);
return 0;
}
12.2、服务端示例代码
#include<stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include<stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include<string.h>
#define N 128
int main(int argc , char *argv[])
{
if(argc<3)
{
fprintf(stderr, "%s ip port ",argv[0]);
exit(1);
}
int sockfd;
if((sockfd=socket(AF_INET,SOCK_DGRAM,0))==-1)
{
perror("fail to sockfd");
exit(1);
}
printf("sockfd= %d\n",sockfd);
struct sockaddr_in mysockaddr;
mysockaddr.sin_family=AF_INET;
mysockaddr.sin_port =htons(atoi(argv[2]));
mysockaddr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t addrlen = sizeof(mysockaddr);
if(bind(sockfd,(struct sockaddr*)&mysockaddr,addrlen)<0)
{
perror("fail to bind");
exit(1);
}
char buf[N]="";
struct sockaddr_in recvfrom_addr;
socklen_t recvfrom_addrlen = sizeof(recvfrom_addr);
while(1)
{
char text[50]="";
if(recvfrom(sockfd,buf,N,0,(struct sockaddr *)&recvfrom_addr,&recvfrom_addrlen)==-1)
{
perror("fail to recvfrom");
exit(1);
}
printf("ip:%s ,port:%d\n", inet_ntoa(recvfrom_addr.sin_addr), ntohs(recvfrom_addr.sin_port));
printf("%s\n",buf);
strcat(text, " *_*");
if(sendto(sockfd, text, sizeof(text), 0, (struct sockaddr *)&recvfrom_addr, addrlen) < 0)
{
perror("fail to sendto");
exit(1);
}
}
close(sockfd);
return 0;
}
运行结果
可以实现多并发 文章来源:https://www.toymoban.com/news/detail-523106.html
文章来源地址https://www.toymoban.com/news/detail-523106.html
到了这里,关于UDP网络编程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!