前言:

UDP 是一个面向无连接的,不安全的,报式传输层协议,udp 的通信过程默认也是阻塞的。

  • UDP通信不需要建立连接 ,因此不需要进行 connect () 操作
  • UDP通信过程中,每次都需要指定数据接收端的IP和端口,和发快递差不多
  • UDP不对收到的数据进行排序,在UDP报文的首部中并没有关于数据顺序的信息
  • UDP对接收到的数据报不回复确认信息,发送端不知道数据是否被正确接收,也不会重发数据。

流程说明:

使用 UDP 进行通信,服务器和客户端的处理步骤比 TCP 要简单很多,并且两端是对等的 ,也就是说并没有严格意义上的客户端和服务器端。UDP 的通信流程如下:

相关函数说明:

  • 服务器端:
// 第二个参数是 SOCK_DGRAM, 第三个参数0表示使用报式协议中的udp
int fd = socket(AF_INET, SOCK_DGRAM, 0);
bind();
// 接收数据
recvfrom();
// 发送数据
sendto();
// 接收数据
recvfrom();
// 发送数据
sendto();
  • 客户端:
// 第二个参数是 SOCK_DGRAM, 第三个参数0表示使用报式协议中的udp
int fd = socket(AF_INET, SOCK_DGRAM, 0);
// 接收数据
recvfrom();
// 发送数据
sendto();
close(fd);

在 UDP 通信过程中,哪一端是接收数据的角色,那么这个接收端就必须绑定一个固定的端口,如果某一端不需要接收数据,这个绑定操作就可以省略不写了,通信的套接字会自动绑定一个随机端口。

代码实例:

service.cpp

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>int main()
{// 1. 创建通信的套接字int fd = socket(AF_INET, SOCK_DGRAM, 0);if(fd == -1){perror("socket");exit(0);}// 2. 通信的套接字和本地的IP与端口绑定struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(8080);    // addr.sin_addr.s_addr = INADDR_ANY;  // 0.0.0.0int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));if(ret == -1){perror("bind");exit(0);}char buf[1024];char ipbuf[64];struct sockaddr_in cliaddr;int len = sizeof(cliaddr);// 3. 通信while(1){// 接收数据memset(buf, 0, sizeof(buf));int rlen = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr*)&cliaddr, &len);printf("客户端的IP地址: %s, 端口: %d\n",inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ipbuf, sizeof(ipbuf)),ntohs(cliaddr.sin_port));printf("客户端说: %s\n", buf);// 回复数据// 数据回复给了发送数据的客户端sendto(fd, buf, rlen, 0, (struct sockaddr*)&cliaddr, sizeof(cliaddr));}close(fd);return 0;
}

服务器端通过 bind() 函数绑定了固定的端口,然后基于这个固定的端口通过 recvfrom() 函数接收客户端发送的数据,同时通过这个函数也得到了数据发送端的地址信息(recvfrom 的第三个参数),这样就可以通过得到的地址信息通过 sendto() 函数给客户端回复数据了。
client.cpp

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>int main()
{// 1. 创建通信的套接字int fd = socket(AF_INET, SOCK_DGRAM, 0);if(fd == -1){perror("socket");exit(0);}// 初始化服务器地址信息struct sockaddr_in seraddr;seraddr.sin_family = AF_INET;seraddr.sin_port = htons(8080);    // inet_pton(AF_INET, "192.168.1.100", &seraddr.sin_addr.s_addr);char buf[1024];char ipbuf[64];struct sockaddr_in cliaddr;int len = sizeof(cliaddr);int num = 0;// 2. 通信while(1){sprintf(buf, "hello, udp %d....\n", num++);// 发送数据, 数据发送给了服务器sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&seraddr, sizeof(seraddr));// 接收数据memset(buf, 0, sizeof(buf));recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL);printf("服务器说: %s\n", buf);sleep(1);}close(fd);return 0;
}

作为数据发送端,客户端不需要绑定固定端口,客户端使用的端口是随机绑定的(也可以调用 bind () 函数手动进行绑定)。客户端在接收服务器端回复的数据的时候需要调用 recvfrom() 函数,因为客户端在发送数据之前就已经知道服务器绑定的固定的 IP 和端口信息了,所以接收服务器数据的时候就可以不保存服务器端的地址信息,直接将函数的最后两个参数指定为 NULL 即可。

UDP特性之一广播:

通过广播可以向子网中多台计算机发送消息,并且子网中所有的计算机都可以接收到发送方发送的消息,每个广播消息都包含一个特殊的 IP 地址,这个 IP 中子网内主机标志部分的二进制全部为 1 (即点分十进制 IP 的最后一部分是 255)。点分十进制的 IP 地址每一部分是 1 字节,最大值为 255,比如:192.168.10.1

  • 前两部分 192.168 表示当前网络是局域网
  • 第三部分 10 表示局域网中的某一个网段,最大值为 255
  • 第四部分 1 用于标记当前网段中的某一台主机,最大值为 255
  • 每个网段都有一个特殊的广播地址,即:192.168.xxx.255
    广播属性:
int setsockopt(int sockfd, int level, int optname,   const void *optval, socklen_t optlen);
**参数:**
sockfd:进行 UDP 通信的文件描述符
level: 套接字级别,需要设置为 SOL_SOCKET
optname:选项名,此处要设置 udp 的广播属性,该参数需要指定为:SO_BROADCAST
optval:如果是设置广播属性,该指针实际指向一块 int 类型的内存
该整型值为 0:关闭广播属性
该整形值为 1:打开广播属性
optlen:optval 指针指向的内存大小,即:sizeof(int)
**返回值:**函数调用成功返回 0,失败返回 - 1

// 第二个参数是 SOCK_DGRAM, 第三个参数0表示使用报式协议中的udp
int fd = socket(AF_INET, SOCK_DGRAM, 0);
//主动发送数据不需要手动绑定固定端口(自动随机分配就可以了),因此直接设置广播属性
int opt  = 1;
setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt));
sendto();
close(fd);

数据接收端:

// 第二个参数是 SOCK_DGRAM, 第三个参数0表示使用报式协议中的udp
int fd = socket(AF_INET, SOCK_DGRAM, 0);
bind();
recvfrom();
close(fd);

实例代码:

广播端:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>int main()
{// 1. 创建通信的套接字int fd = socket(AF_INET, SOCK_DGRAM, 0);if(fd == -1){perror("socket");exit(0);}// 2. 设置广播属性int opt  = 1;setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt));char buf[1024];struct sockaddr_in cliaddr;int len = sizeof(cliaddr);cliaddr.sin_family = AF_INET;cliaddr.sin_port = htons(8080); // 接收端需要绑定8080端口// 只要主机在10网段, 并且绑定了8080端口, 这个接收端就能收到广播消息inet_pton(AF_INET, "192.168.10.255", &cliaddr.sin_addr.s_addr);// 3. 通信int num = 0;while(1){sprintf(buf, "hello, client...%d\n", num++);// 数据广播sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&cliaddr, len);printf("发送的广播的数据: %s\n", buf);sleep(1);}close(fd);return 0;
}
发送广播消息一端必须要开启 UDP 的广播属性,并且发送消息的地址必须是当前发送端所在网段的广播地址,这样才能通过调用一个消息发送函数将消息同时发送 N 台接收端主机上。

接收端

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>int main()
{// 1. 创建通信的套接字int fd = socket(AF_INET, SOCK_DGRAM, 0);if(fd == -1){perror("socket");exit(0);}// 2. 通信的套接字和本地的IP与端口绑定struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(8080);    addr.sin_addr.s_addr = INADDR_ANY;  // 0.0.0.0int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));if(ret == -1){perror("bind");exit(0);}char buf[1024];// 3. 通信while(1){// 接收广播消息memset(buf, 0, sizeof(buf));// 阻塞等待数据达到recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL);printf("接收到的广播消息: %s\n", buf);}close(fd);return 0;
}
对于接收广播消息的一端,必须要绑定固定的端口,并由广播端将广播消息发送到这个端口上,因此所有接收端都应绑定相同的端口,这样才能同时收到广播数据。

UDP特性之一组播:

组播也可以称之为多播这也是 UDP 的特性之一。组播是主机间一对多的通讯模式,是一种允许一个或多个组播源发送同一报文到多个接收者的技术。组播源将一份报文发送到特定的组播地址,组播地址不同于单播地址,它并不属于特定某个主机,而是属于一组主机。一个组播地址表示一个群组,需要接收组播报文的接收者都加入这个群组。

  • 广播只能在局域网访问内使用,组播既可以在局域网中使用,也可以用于广域网

  • 在发送广播消息的时候,连接到局域网的客户端不管想不想都会接收到广播数据,组播可以控制发送端的消息能够被哪些接收端接收,更灵活和人性化。

  • 广播使用的是广播地址,组播需要使用组播地址。

  • 广播和组播属性默认都是关闭的,如果使用需要通过 setsockopt () 函数进行设置。

    组播需要使用组播地址,在 IPv4 中它的范围从 224.0.0.0 到 239.255.255.255,并被划分为局部链接多播地址、预留多播地址和管理权限多播地址三类:

  • 224.0.0.0~224.0.0.255 局部链接多播地址:是为路由协议和其它用途保留的地址, 只能用于局域网中,路由器是不会转发的地址 224.0.0.0 不能用,是保留地址

  • 224.0.1.0~224.0.1.255 为用户可用的组播地址(临时组地址),可以用于 Internet 上的。

  • 224.0.2.0~238.255.255.255 用户可用的组播地址(临时组地址),全网范围内有效

  • 239.0.0.0~239.255.255.255 为本地管理组播地址,仅在特定的本地范围内有效

组播地址不属于任何服务器或个人,它有点类似一个群号,任何成员(组播源)往群(组播 IP)发送消息(组播数据),这个群里的成员(组播接收者)都会接收到此消息。

组播发送端:
函数说明:

// 第二个参数是 SOCK_DGRAM, 第三个参数0表示使用报式协议中的udp
int fd = socket(AF_INET, SOCK_DGRAM, 0);
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
参数:
sockfd:用于 UDP 通信的套接字
level:套接字级别,设置组播属性需要将该参数指定为:IPPTOTO_IP
optname: 套接字选项名,设置组播属性需要将该参数指定为:IP_MULTICAST_IF
optval:设置组播属性,这个指针需要指向一个 struct in_addr{} 类型的结构体地址,这个结构体地址用于存储组播地址,并且组播 IP 地址的存储方式是大端的。
struct in_addr
{in_addr_t s_addr;  // unsigned int
};
optlen:optval 指针指向的内存大小,即:sizeof(struct in_addr)
返回值:函数调用成功返回 0,调用失败返回 - 1
sendto();
close(fd);

组播接受端:
函数说明:

// 第二个参数是 SOCK_DGRAM, 第三个参数0表示使用报式协议中的udp
int fd = socket(AF_INET, SOCK_DGRAM, 0);
绑定固定的端口,发送端应该将数据发送到接收端绑定的端口上
bind();
// 加入到多播组
struct ip_mreqn opt;
// 要加入到哪个多播组, 通过组播地址来区分
inet_pton(AF_INET, "239.0.1.10", &opt.imr_multiaddr.s_addr);
opt.imr_address.s_addr = INADDR_ANY;
opt.imr_ifindex = if_nametoindex("ens33");
setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &opt, sizeof(opt));
recvfrom();
close(fd);

实例代码:
发送端:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>int main()
{// 1. 创建通信的套接字int fd = socket(AF_INET, SOCK_DGRAM, 0);if(fd == -1){perror("socket");exit(0);}// 2. 设置组播属性struct in_addr opt;// 将组播地址初始化到这个结构体成员中即可inet_pton(AF_INET, "238.0.1.10", &opt.s_addr);setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &opt, sizeof(opt));char buf[1024];struct sockaddr_in cliaddr;int len = sizeof(cliaddr);cliaddr.sin_family = AF_INET;cliaddr.sin_port = htons(8080); // 接收端需要绑定8080端口// 发送组播消息, 需要使用组播地址, 和设置组播属性使用的组播地址一致就可以inet_pton(AF_INET, "238.0.1.10", &cliaddr.sin_addr.s_addr);// 3. 通信int num = 0;while(1){sprintf(buf, "hello, client...%d\n", num++);// 数据广播sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&cliaddr, len);printf("发送的组播的数据: %s\n", buf);sleep(1);}close(fd);return 0;
}
注意事项:在组播数据的发送端,需要先设置组播属性,发送的数据是通过 sendto () 函数发送到某一个组播地址上,并且在程序中数据发送到了接收端的 9999 端口,因此接收端程序必须要绑定这个端口才能收到组播消息。

接收端:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <net/if.h>int main()
{// 1. 创建通信的套接字int fd = socket(AF_INET, SOCK_DGRAM, 0);if(fd == -1){perror("socket");exit(0);}// 2. 通信的套接字和本地的IP与端口绑定struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(7777);    // addr.sin_addr.s_addr = INADDR_ANY;  // 0.0.0.0int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));if(ret == -1){perror("bind");exit(0);}// 3. 加入到多播组struct ip_mreqn opt;// 要加入到哪个多播组, 通过组播地址来区分inet_pton(AF_INET, "238.0.1.10", &opt.imr_multiaddr.s_addr);opt.imr_address.s_addr = INADDR_ANY;opt.imr_ifindex = if_nametoindex("ens11");setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &opt, sizeof(opt));char buf[1024];// 3. 通信while(1){// 接收广播消息memset(buf, 0, sizeof(buf));// 阻塞等待数据达到recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL);printf("接收到的组播消息: %s\n", buf);}close(fd);return 0;
}
注意事项:作为组播消息的接收端,必须要先绑定一个固定端口(发送端就可以把数据发送到这个固定的端口上了),然后加入到组播的群组中(一个组播地址可以看做是一个群组),这样就可以接收到组播消息了。

Socket 套接字之UDP通信相关推荐

  1. socket(套接字)实现udp通信

    udp通信 储备知识 网络字节序 udp使用的接口 sockaddr结构 简单的udp通信 优化服务器 储备知识 源ip地址和目的ip地址 我们先来看个例子: 如果当女儿国国王问你上一站从何而来,下一 ...

  2. TCP与UDP协议,socket套接字编程,通信相关操作

    文章目录 TCP与UDP协议 TCP协议 ==三次握手== ==四次挥手== UDP协议 TCP与UDP的区别 应用层 socket套接字 代码优化 循环通信 半连接池 粘包问题 TCP与UDP协议 ...

  3. 【网络编程】Socket套接字;UDP数据报套接字编程;TCP流套接字编程

    文章目录 1. 什么是网络编程 2. 网络编程中的基本概念 3. Socket套接字 4 UDP数据报套接字编程 4.1 客户端服务器交互流程 4.2 UDP版本的回显服务 4.3 英译汉服务 5. ...

  4. TCP与UDP协议、socket套接字编程、通信相关操作(cs架构软件)、TCP黏包问题及解决思路

    OSI七层协议 传输层 1.PORT协议:前面讲过 2.TCP协议与UDP协议:规定了数据传输所遵循的规则(数据传输能够遵循的协议有很多,TCP和UDP是较为常见的两个) TCP协议 基于TCP传输数 ...

  5. Python网络编程——socket套接字实现UDP/TCP信息传输

    socket套接字 socket(简称 套接字) ,是支持TCP/IP的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来 ...

  6. 计算机网络(二) | 网络编程基础、Socket套接字、UDP和TCP套接字编程

    目录 一.网络编程基础 1.1 为什么需要网络编程 1.2 什么是网络编程 1.3 网络编程中的基本概念 二.Socket套接字 2.1 概念 2.2 分类 2.3 Java数据报套接字通信模型 2. ...

  7. 套接字、UDP通信、TCP通信、TCP/IP协议簇

    一.套接字(socket) 1.英语单词socket:n.插座:穴:v.插入插座 2.套接字就是源IP地址和目的IP地址.源端口号和目的端口号的组合,是通过传输层进行通信的.IP指定电脑,端口指定某一 ...

  8. 套接字、UDP通信、TCP通信、TCP\IP协议簇

    一.套接字(socket) 1.英语单词socket:n.插座:穴:v.插入插座 2.套接字就是源IP地址和目的IP地址.源端口号和目的端口号的组合,是通过传输层进行通信的.IP指定电脑,端口指定某一 ...

  9. 网络编程-Socket套接字(TCP、UDP、广播和组播通信)

    socket套接字 socket是一个编程接口(网络编程接口) 作用是用来实现网络上不同的主机的应用进程之间进行双向通信 套接字是一种特殊的文件描述符 也就意味着我们使用套接字实现网络通信的时候可以用 ...

最新文章

  1. 三数之和(三数不重复)
  2. 动捕技术是拯救VR体验的关键,但如何落地却已成为世界难题
  3. [日常] Go语言圣经-基于select的多路复用习题
  4. python手机版ios-iOS 项目中如何使用 Python
  5. maven The method must override a superclass method
  6. 经典C语言程序100例之一零零
  7. 生产中的12种容器镜像扫描最佳实践
  8. MySQL使用二进制日志恢复数据库
  9. centos 配置mysql环境变量_Centos7.1部署mysql-5.6.34(笔记)
  10. php基础知识 书写格式
  11. bzoj 1008: [HNOI2008]越狱
  12. Timus 1005. Stone pile
  13. java调用萤石对讲_海康萤石摄像头SDK Java(一)java本地调用摄像头
  14. 线程----code
  15. 每个程序猿都有个黑客小宇宙,自敲代码的时候就爆发了一发不可收拾
  16. lesson 20 one man in a boat 独坐孤舟-for hours数小时做时间状语,having done于句首非谓语做状语,its wasteof time,do nothing
  17. 计算机组织活动的意义,信息学院计算机09-1班团支部关于“向榜样学习,向优秀看齐”主题班团会活动总结...
  18. 行内元素和块元素的区别
  19. 5G学习总结:RRM(无线资源管理)
  20. 为什么VPS会被云服务器取代?

热门文章

  1. 放大的X(打印问题)
  2. extern “C” 陷阱
  3. html中qq的符号,特殊符号大全|QQ特殊符号|HTML特殊符号|常用特殊符号|QQ表情符号_特殊符号图案_万能查询网...
  4. Android自定义键盘,仿招商银行
  5. 机器学习入门级实例——针对葡萄酒质量进行建模
  6. 来自苹果的编程语言——Swift简介
  7. 如何使用Vue.js中的set设置对象属性值
  8. 常见电路分析十一:y型连接和三角形连接
  9. HCIP-H12-222练习题
  10. 豆瓣电影,电视剧DM实战