Linux轮询操作

article/2025/9/8 18:15:42

Linux设备之非阻塞I/O操作


文章目录

  • Linux设备之非阻塞I/O操作
  • 前言
  • 一、接口简介
    • 1、select
    • 2、poll
    • 3、epoll
    • 4、总结
  • 二、接口介绍
  • 三、代码样例


前言

上一篇讲解了Linux设备的阻塞I/O操作,其原理是利用了把进程挂到等待队列中,等条件满足时再唤醒此进程。本片所讲解的是轮询操作,也就是非阻塞的I/O操作。弄清轮询操作主要要弄清select、poll、epoll这三个接口的作用与差别,下面就是对它们的介绍

一、接口简介

1、select

select==>时间复杂度O(n)

它仅仅知道了,有I/O事件发生了,却并不知道是哪那几个流(可能有一个,多个,甚至全部),我们只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。所以select具有O(n)的无差别轮询复杂度,同时处理的流越多,无差别轮询时间就越长。

select本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理。这样所带来的缺点是:

1、 单个进程可监视的fd数量被限制,即能监听端口的大小有限。一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/file-max察看。32位机默认是1024个。64位机默认是2048.

2、 对socket进行扫描时是线性扫描,即采用轮询的方法,效率较低。当套接字比较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍。这会浪费很多CPU时间。

3、需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大。

2、poll

poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd。这个过程经历了多次无谓的遍历。

它没有最大连接数的限制,原因是它是基于链表来存储的,但是同样有一个缺点:

1、大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义。

2、poll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。

3、epoll

epoll有EPOLLLT和EPOLLET两种触发模式,LT是默认的模式,ET是“高速”模式。LT模式下,只要这个fd还有数据可读,每次 epoll_wait都会返回它的事件,提醒用户程序去操作,而在ET(边缘触发)模式中,它只会提示一次,直到下次再有数据流入之前都不会再提示了,无 论fd中是否还有数据可读。所以在ET模式下,read一个fd的时候一定要把它的buffer读光,也就是说一直读到read的返回值小于请求值,或者 遇到EAGAIN错误。还有一个特点是,epoll使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知。

epoll为什么要有EPOLLET触发模式?

如果采用EPOLLLT模式的话,系统中一旦有大量你不需要读写的就绪文件描述符,它们每次调用epoll_wait都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率.。而采用EPOLLET这种边沿触发模式的话,当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你!!!这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符

epoll的优点:

1、没有最大并发连接的限制,能打开的FD的上限远大于1024(1G的内存上能监听约10万个端口);
2、效率提升,不是轮询的方式,不会随着FD数目的增加效率下降。只有活跃可用的FD才会调用callback函数;
即Epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,Epoll的效率就会远远高于select和poll。

3、 内存拷贝,利用mmap()文件映射内存加速与内核空间的消息传递;即epoll使用mmap减少复制开销。

4、总结

综上,在选择select,poll,epoll时要根据具体的使用场合以及这三种方式的自身特点。

1、表面上看epoll的性能最好,但是在连接数少并且连接都十分活跃的情况下,select和poll的性能可能比epoll好,毕竟epoll的通知机制需要很多函数回调。

2、select低效是因为每次它都需要轮询。但低效也是相对的,视情况而定,也可通过良好的设计改善

二、接口介绍

下面分别介绍select与poll两个函数对应接口:

1、select

int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

参数readfds,writefds,exceptfds分别是被select()监视的读,写和异常处理的文件描述符集合,numfds代表的是需要检查的号码最高的文件描述符加1。timeout参数是一个指向struct timeval类型的指针,它可以使select()在等待timeout时间后若没有文件描述符准备好则返回。struct timeval定义如下:

struct timeval{int tv_sec;		//秒int tv_usec;	//微秒
}

对于一个文件描述符集合有以下几种操作方法

FD_ZERO(fd_set *set)			//清除一个文件描述符集合
FD_SET(int fd, fd_set *set)		//将一个文件描述符加入文件描述符集合中
FD_CLR(int fd, fd_set *set)		//将一个文件描述符从文件描述符集合中取出
FD_ISSET(int fd, fd_set *set)	//判断文件描述符是否被值位

2、poll

在设备驱动中,poll()函数的原型是:

unsigned int (*poll)(struct file *filp, struct poll_table_struct *wait);

第一个参数为file结构体指针,第二个参数为轮询表指针。

在poll()函数中主要进行两个操作:
(1)对可能引起设备文件状态变化的等待队列调用poll_wait()函数,将对应等待队列头添加到poll_table中。
(2)返回表示是否能对设备进行无阻塞读、写访问的掩码。

poll_wait()函数的原型如下:

void poll_wait(struct file *filp, wait_queue_heat_t *queue, struct poll_table_struct *wait);

三、代码样例

在驱动中的poll函数里,首先将设备结构体中的r_wait和w_wait等待队列头添加到等待队列表,然后通过判断dev->current_len是否等于0来获得设备的可读状态,通过判断dev->current_len是否等于MEM_SIZE来获得设备的可写状态。我们在应用程序中使用select机制监控globalfifo的可读写状态。

驱动代码如下:

#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/poll.h>
#include <linux/wait.h>#define MEM_SIZE 	0x1000		//全局内存最大4KB
#define MEM_CLEAR	0x1			//清零全局内存
#define GLOBALMEM_MAJOR	250	//预设的globalmem的主设备号static int globalmem_major = GLOBALMEM_MAJOR;/*整体思想:申请一块全局结构体变量,把它当作一个FIFO,只有当FIFO中有数据的时候,读进程才能把数据读出,而且读取后的数据会从FIFO的全局内存中被拿掉;只有当FIFO非满时,写进程才能往这个FIFO中写入数据。
*/
typedef struct gm_cdev{struct cdev cdev;					//字符设备结构体unsigned char mem[MEM_SIZE];		//全局内存struct semaphore sem;				//并发控制用的信号量unsigned int current_len;			//fifo有效数据长度wait_queue_head_t r_wait;			//阻塞读用的等待队列头wait_queue_head_t w_wait;			//阻塞写用的等待队列头
}GLOBALFILO;GLOBALFILO *globalmem_dev;			//设备结构体指针//文件打开参数
int  globalmem_open(struct inode *inode, struct file *file)
{//将设备结构体指针赋值给文件私有数据指针file->private_data = globalmem_dev;return 0;
}//文件释放函数
int globalmem_release(struct inode *inode, struct file *file)
{return 0;
}//ioctl设备控制函数
long globalmem_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{GLOBALFILO *v_dev = file->private_data;		//从文件结构体中取出设备结构体switch(cmd){case MEM_CLEAR:/*用信号量方式实现并发控制,不能用自旋锁,因为copy_to_user会引起进程调度,可能导致内核崩溃*///获得信号量if(down_interruptible(&globalmem_dev->sem)){return -ERESTARTSYS;		}memset(v_dev->mem, 0, MEM_SIZE);//释放信号量up(&globalmem_dev->sem);printk(KERN_INFO "globalmem is set to zero\n");break;default:return -EINVAL;}return 0;
}//读函数
static ssize_t globalmem_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{int ret = 0;GLOBALFILO *v_dev = file->private_data;		//从文件结构体中取出设备结构体DECLARE_WAITQUEUE(wait, current);	//定义等待队列/*用信号量方式实现并发控制,不能用自旋锁,因为copy_to_user会引起进程调度,可能导致内核崩溃*///获得信号量if(down_interruptible(&v_dev->sem)){return -ERESTARTSYS;		}//把wait等待队列挂到等待队列头上add_wait_queue(&v_dev->r_wait, &wait);//等待FIFO非空while(0 == v_dev->current_len){if(file->f_flags & O_NONBLOCK){//如果文件是非阻塞,则直接退出ret = -EAGAIN;goto out;}//改变进程状态为睡眠__set_current_state(TASK_INTERRUPTIBLE);//释放信号量up(&v_dev->sem);printk(KERN_INFO "%d,   In globalmem_read, Begin schedule!\n", __LINE__);//调用其他进程执行schedule();printk(KERN_INFO "%d,   In globalmem_read, End schedule!\n", __LINE__);//如果信号唤醒此进程if(signal_pending(current)){ret = -ERESTARTSYS;goto out2;}//获得信号量if(down_interruptible(&v_dev->sem)){return -ERESTARTSYS;		}}//把数据从内核空间拷贝到用户空间if(size > v_dev->current_len){size = v_dev->current_len;}if(copy_to_user(buf, (void *)v_dev->mem, size)){ret = -EFAULT;goto out;}else{//fifo有效数据前移memcpy(v_dev->mem, v_dev->mem+size, v_dev->current_len-size);//有效数据长度减少v_dev->current_len -= size;printk(KERN_INFO "read %u bytes, current_len is %u\n", size, v_dev->current_len);//唤醒写等待队列wake_up_interruptible(&v_dev->w_wait);ret = size;}out://释放信号量up(&v_dev->sem);out2://移除等待队列remove_wait_queue(&v_dev->r_wait, &wait);set_current_state(TASK_RUNNING);printk(KERN_INFO "%d,   It is end!\n", __LINE__);return ret;
}//写函数
static ssize_t globalmem_write(struct file *file, const char __user *buf, size_t size, loff_t *ppos)
{GLOBALFILO *v_dev = file->private_data;		//从文件结构体中取出设备结构体int ret = 0;DECLARE_WAITQUEUE(wait, current);	//定义等待队列/*用信号量方式实现并发控制,不能用自旋锁,因为copy_to_user会引起进程调度,可能导致内核崩溃*///获得信号量if(down_interruptible(&v_dev->sem)){return -ERESTARTSYS;		}//把wait等待队列挂到等待队列头上add_wait_queue(&v_dev->w_wait, &wait);	//等待fifo非满while(MEM_SIZE == v_dev->current_len){if(file->f_flags & O_NONBLOCK){//如果文件是非阻塞,则直接退出ret = -EAGAIN;goto out;}	//改变进程状态为睡眠__set_current_state(TASK_INTERRUPTIBLE);//释放信号量up(&v_dev->sem);printk(KERN_INFO "%d,   In globalmem_write, Begin schedule!\n", __LINE__);//调用其他进程执行schedule();printk(KERN_INFO "%d,   globalmem_write, End schedule!\n", __LINE__);//如果信号唤醒此进程if(signal_pending(current)){ret = -ERESTARTSYS;goto out2;}//获得信号量if(down_interruptible(&v_dev->sem)){return -ERESTARTSYS;		}}//把数据从用户空间拷贝到内核空间if(size > MEM_SIZE - v_dev->current_len){size = MEM_SIZE - v_dev->current_len;}if(copy_from_user( (void *)v_dev->mem+v_dev->current_len, buf, size)){ret = -EFAULT;goto out;}else{v_dev->current_len += size;printk(KERN_INFO "write %u bytes, current len is %u\n", size, v_dev->current_len);//唤醒读等待队列wake_up_interruptible(&v_dev->r_wait);ret = size;}out:	//释放信号量up(&v_dev->sem);out2:remove_wait_queue(&v_dev->w_wait, &wait);set_current_state(TASK_RUNNING);printk(KERN_INFO "%d,   It is end!\n", __LINE__);return ret;
}static unsigned int globalfifo_poll(struct file *filp, struct poll_table_struct *wait)
{unsigned int mask = 0;GLOBALFILO *v_dev = filp->private_data;		//获取设备结构体//获得信号量if(down_interruptible(&v_dev->sem)){return -ERESTARTSYS;		}//设备结构体中的r_wait和w_wait等待队列头添加到等待队列表poll_wait(filp, &v_dev->r_wait, wait);poll_wait(filp, &v_dev->w_wait, wait);//fifo非空if(0 != v_dev->current_len){mask |= POLLIN | POLLRDNORM;		//数据可读取}//fifo非满if(MEM_SIZE != v_dev->current_len){mask |= POLLOUT | POLLWRNORM;		//数据可写入}//释放信号量up(&v_dev->sem);return mask;
}
//文件操作结构体
static const struct file_operations global_fops = {.owner = THIS_MODULE,		//固定格式.llseek = NULL,.poll = globalfifo_poll,.read = globalmem_read,.write = globalmem_write,.unlocked_ioctl = globalmem_ioctl,.compat_ioctl = NULL,.open = globalmem_open,.release = globalmem_release,
};//初始化并注册cdev设备
static void globalmem_setup_cdev(GLOBALFILO *cdev, int index)
{int errno;int devno = MKDEV(globalmem_major, index);	//将主设备号和次设备号换成dev_t类型cdev_init(&cdev->cdev, &global_fops);		//静态内存定义初始化cdev->cdev.owner = THIS_MODULE;//把cdev设备加入到内核中去,devno为设备号,1为设备数量errno = cdev_add(&cdev->cdev, devno, 1);if(errno)printk(KERN_NOTICE "Add %d dev is error %d", index, errno);
}//设备驱动加载
int globalmem_init(void)
{int result;dev_t devno = MKDEV(globalmem_major, 0);//申请设备号if(globalmem_major){//静态申请设备号result = register_chrdev_region(devno, 1, "globalmem");}else{//动态申请设备号result = alloc_chrdev_region(&devno, 0 , 1, "globalmem");globalmem_major = MAJOR(devno);}if(result < 0){return result;}//申请设备结构体内存globalmem_dev = kmalloc(sizeof(GLOBALFILO), GFP_KERNEL);if(!globalmem_dev){//申请内存失败result = -ENOMEM;goto err;}memset(globalmem_dev, 0, sizeof(GLOBALFILO));globalmem_setup_cdev(globalmem_dev, 0);//初始化信号量//init_MUTEX(&globalmem_dev->sem);	//已被废除sema_init(&globalmem_dev->sem, 1);//初始化读等待队列头init_waitqueue_head(&globalmem_dev->r_wait);//初始化写等待队列头init_waitqueue_head(&globalmem_dev->w_wait);return 0;err:unregister_chrdev_region(devno, 1);return result;
}//设备驱动卸载
void globalmem_exit(void)
{cdev_del(&globalmem_dev->cdev);	//内核注销设备kfree(globalmem_dev);	//释放设备结构体内存unregister_chrdev_region(MKDEV(globalmem_major, 0), 1);	//释放设备号
}MODULE_LICENSE("Dual BSD/GPL");
module_param(globalmem_major, int, S_IRUGO);module_init(globalmem_init);
module_exit(globalmem_exit);

应用层测试程序如下

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>#define	FIFO_CLEAR		0x1
#define	BUFFER_LEN		20
#define	GLOBALFIFO		"/dev/globalfifo"
int main()
{int fd, num;char rd_ch[BUFFER_LEN] = {0};fd_set rfds, wfds;		//读写文件描述符集合//以非阻塞的方式打开/dev/globalfifo设备文件fd = open(GLOBALFIFO, O_RDONLY|O_NONBLOCK);if(-1 != fd){//FIFO清0if(ioctl(fd, FIFO_CLEAR, 0) < 0){printf("Can not clear FIFO!\n");}while(1){//清除文件描述符集合FD_ZERO(&rfds);FD_ZERO(&wfds);//将一个文件描述符加入文件描述符集合中FD_SET(fd, &rfds);FD_SET(fd, &wfds);select(fd+1, &rfds, &wfds, NULL, NULL);//如果数据可读出if(FD_ISSET(fd, &rfds)){printf("We can read msg now!\n");}//如果数据可写入if(FD_ISSET(fd, &wfds)){printf("We can write msg now!\n");}}}else{printf("Can not open device!\n");}}

现象是当/dev/globalfifo中为空时,执行应用层测试程序编译出的可执行文件,会发现一直打印“We can write msg now!”如下图所示:
在这里插入图片描述
当/dev/globalfifo中有数据但是没有占满全部空间时,执行应用层测试程序编译出的可执行文件,会发现交替打印“We can write msg now!”和“”We can read msg now!如下图所示:

在这里插入图片描述
当/dev/globalfifo被占满时,执行应用层测试程序编译出的可执行文件,会发现一直打印“We can read msg now!”如下图所示:

在这里插入图片描述



http://chatgpt.dhexx.cn/article/MGifyJIh.shtml

相关文章

短轮询和长轮询

轮询是由客户端每隔一段时间向服务器发出HTTP请求&#xff0c;服务端接收到请求后向客户端返回最新的数据。 客户端的轮询方式一般分为短轮询和长轮询。 短轮询&#xff1a; 一般是由客户端每隔一段时间向服务器发起一次普通HTTP请求。服务端查询当前接口是否有数据更新&#x…

轮询与长轮询

轮询&#xff1a;说白了就是客户端定时去请求服务端&#xff0c; 是客户端主动请求来促使数据更新&#xff1b; 长轮询&#xff1a;说白了 也是客户端请求服务端&#xff0c;但是服务端并不是即时返回&#xff0c;而是当有内容更新的时候才返回内容给客户端&#xff0c;从流程…

前端实现轮询

方法一&#xff1a;简单实现 componentDidMount() {this.props.countFxMissionByStatus();countSwiftMessage(); }componentWillReceiveProps(nextProps) {const {location} nextProps;// 判断页面然后在更新的周期中实现轮询const isSwiftManage location.pathname.indexOf…

NGINX轮询机制的几种形式

前言&#xff1a;总以为轮询就简单的next而已&#xff0c;实际还有几种不同的实现机制。某个客户的源站有几个不同的IP&#xff0c;回源的时候自然是采用的轮询的机制。客户业务上线前&#xff0c;检查源站的联通性发现一个漏网之鱼竟然差点滥竽充数。然而客户的想法确是&#…

事件轮询机制理解

进程与线程 首先简单了解下进程和线程的概念 进程&#xff1a;cpu资源分配的最小的单位&#xff0c;是拥有资源和独立运行的最小单位&#xff0c;程序执行时&#xff0c;会创建一个进程&#xff0c;cpu为其分配资源&#xff0c;并加入进程就绪队列。线程&#xff1a;cpu调度的…

事件轮询机制

事件循环(轮询)机制 js是单线程的所有js代码都是在主线程执行的同步任务进入主线程即会执行异步任务则会进入浏览器的管理模块 (有DOM事件管理模块、ajax请求管理模块、定时器管理模块等)管理模块一直监视异步任务是否满足条件。如果满足条件则会将对应的回调放入回调队列中(c…

IP多播(组播)

一 IP多播的基本概念 IP多播(multicast&#xff0c;也被译为组播)&#xff0c;它是一种一对多的通信方式。与单播相比&#xff0c;多播可以大大节约网络资源。 以视频流媒体服务为例说明单播和多播的区别&#xff0c;如图所示&#xff1a; 图1 单播与多播的比较 (a) 中使用的…

多播

19.1 概述 单播地址标识单个接口&#xff0c;广播地址标识子网上的所有接口&#xff0c;多播地址标识一组接口。单播和广播是编制方案的两个极端(要么一个要么全部)&#xff0c;多播的目的就在于提供一种折衷方案。多播数据报仅由对该数据报感兴趣的接口接收&#xff0c;也就是…

单播 、多播(组播)、广播

作者&#xff1a;yhthu 链接&#xff1a;https://www.jianshu.com/p/cc62e070a6d2#comments 来源&#xff1a;简书 著作权归作者所有。商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处。 目录 单播、多播(组播)、广播、任播单播组播广播任播 单播、多播(组播)、广…

UDP之多播/组播

目录 一.什么是多播(组播)&#xff1f;为什么出现多播(组播)&#xff1f;二.组播地址三.主机网卡对应的编号 ifconfig命令ip ad (ip adress)获取网卡对应的编号四.多播实现 一.什么是多播(组播)&#xff1f;为什么出现多播(组播)&#xff1f; 由上节课讲到的广播&#xff0c;可…

单播、多播和广播经典详解

1 什么是单播、多播和广播 “单播”&#xff08;Unicast&#xff09;、“多播”&#xff08;Multicast&#xff09;和“广播”&#xff08;Broadcast&#xff09;这三个术语都是用来描述网络节点之间通讯方式的术语。那么这些术语究竟是什么意思&#xff1f;区别何在…

多播--概念和编程

11.3 多播 单播用于两个主机之间的端对端通信&#xff0c;广播用于一个主机对整个局域网上所有主机上的数据通信。单播和广播是两个极端&#xff0c;要么对一个主机进行通信&#xff0c;要么对整个局域网上的主机进行通信。实际情况下&#xff0c;经常需要对一组特定的主机进…

单播、多播(主播)、广播简介

单播 简介 单播&#xff08;unicast&#xff09;是指封包在计算机网络的传输中&#xff0c;目的地址为单一目标的一种传输方式。每次只有两个实体相互通信&#xff0c;发送端和接收端都是唯一确定的。它是现今网络应用最为广泛&#xff0c;通常所使用的网络协议或服务大多采用…

IP多播

部分转载自&#xff1a;http://www.firewall.cx/networking-topics/general-networking/107-network-multicast.html 剩下的基本参考谢希仁计算机网络7th 1. 基本概念 IP多播直观上可以按照下图理解&#xff0c;源主机只需要发送一份数据&#xff0c;而网络中的路由器在转发…

多播(组播)、单播、任播和广播

定义 单播(unicast): 是指封包在计算机网络的传输中&#xff0c;目的地址为单一目标的一种传输方式。它是现今网络应用最为广泛&#xff0c;通常所使用的网络协议或服务大多采用单播传输&#xff0c;例如一切基于TCP的协议。组播(multicast): 也叫多播&#xff0c; 多点广播或…

IP多播(计算机网络-网络层)

目录 一对多通信的应用需求 单播 vs 多播 多播路由器&#xff08;Multicast Router&#xff09; IP 多播的一些特点 D 类 IP 地址与以太网多播地址的映射关系 IP多播需要两种协议 互联网组管理协议 IGMP 多播路由选择协议 两种多播路由选择方法 建议的IP多播路由选择协…

组播,多播

组播&#xff0c;多播&#xff1a;当网络中进行了组播网部署后&#xff0c;一个台设备仅需要基于一个流量进行一次封装及可将该流量转发到所有的组员处&#xff0c;这些组员可处在网络的任何位置&#xff1b;对非组员不产生影响。再未进行组播网络部署的环境下&#xff0c;以组…

网络-单播、多播(组播)和广播的区别

网络-单播、多播&#xff08;组播&#xff09;和广播的区别 转载声明 本文大量内容系转载自以下文章&#xff0c;有删改&#xff0c;并参考其他文档资料加入了一些内容&#xff1a; 单播、多播&#xff08;组播&#xff09;和广播的区别 作者&#xff1a;Roger Luocnblogs 带…

【TCP/IP】多播 - 定义、原理及编程实现 (TTL、多播组、多播消息)

目录 多播 多播的原理 多播的数据传输时的特点 TTL 的概念 TTL 和 多播组的配置方法 多播的编程与实现 发送者 接收者 多播 多播是一种介于单播和广播通信之间的技术方式&#xff0c;可以将发送者所需要发送的数据包分别发送给分散在不同子网中的一组接收者。 多播的原…

socket之UDP组播(多播)

1. 概述 1.1 单播用于两个主机间单对单的通信 1.2广播用于一个主机对整个局域网上所有主机上的数据通信 1.3单播和广播是两个极端&#xff0c;要么对一个主机进行通信&#xff0c;要么对整个局域网的主机进行通信 1.4实际情况下&#xff0c;经常需要对一组特定的主机进行通信&a…