基于TCP的多路复用

这篇具有很好参考价值的文章主要介绍了基于TCP的多路复用。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1. 知识点

目前支持I/O多路复用的系统调用有select,pselect,poll,epoll。与多进程和多线程技术相

比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进

程/线程,从而大大减小了系统的开销。

I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般

是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都

是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是

阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用

户空间

2. select 函数

0 表示标准输入   STDIN_FILENO

1 表示标准输出     STDOUT_FILENO

2 表示标准错误输出 STDERR_FILENO

2.1 select存在三个问题

[1] 每次调用select,都需要把被监控的fds集合从用户态空间拷贝到内核态空间,高并发场景

下这样的拷贝会使得消耗的资源是很大的。

[2] 能监听端口的数量有限,单个进程所能打开的最大连接数由FD_SETSIZE宏定义,监听上

限就等于fds_bits位数组中所有元素的二进制位总数,32位机默认1024个,64位默2048。

[3] 被监控的fds集合中,只要有一个有数据可读,整个socket集合就会被遍历一次,用户线程并不知道哪些 fds 收到数据只能挨个遍历每个socket来收集可读事件了。

2.2 函数接口

1)用户进程需要监控某些资源 fds,在调用 select 函数后会阻塞,操作系统会将用户线程加入这些资源的等待队列中。

2)直到有描述符就绪(有数据可读、可写或有 except)或超时(timeout 指定等待时间,如果立即返回设为 null 即可),函数返回。

3)select 函数返回后,中断程序唤起用户线程。用户可以遍历 fds,通过 FD_ISSET 判断具体哪个 fd 收到数据,并做出相应处理。

select 函数优点明显,实现起来简单有效,且几乎所有操作系统都有对应的实现。

2.2.1 接口

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

2.2.2 参数:

int nfds:管理的最⼤的⽂件描述符+1

fd_set *readfds:⽂件描述符表(集合),监视管理⽂件描述符的读操作是否就绪

fd_set *writefds:⽂件描述符表(集合),监视管理⽂件描述符的写操作是否就绪,没有就 写NULL

fd_set *exceptfds:⽂件描述符表(集合),监视管理⽂件描述符的异常

struct timeval *timeout:timeout:超时设置。

Null:一直阻塞直到有文件描述符就绪或出错

时间值为0:仅仅检测文件描述符集的状态,然后立即返回

时间值不为0:在指定时间内,如果没有事件发生,则超时返回。

超时设置过后,如果select超时了,那么返回值是0, 并且超时时间的结构体会变成 0s

2.2.3 返回值:

成功:返回监视到就绪的⽂件描述符的个数,会把监视的表修改为只剩下就绪的⽂件描述符

失败:返回-1

基于TCP的多路复用,网络编程,服务器,c语言,网络,udp

2.2.4 操作⽂件描述符表:

void FD_CLR(int fd, fd_set *set);//把⽂件描述符fd从set表删除

int   FD_ISSET(int fd, fd_set *set);//判断fd是否在set集合中,返回值描述符存在集合里返回真1,不存在返回假0

void FD_SET(int fd, fd_set *set);//把fd 加⼊到set表,将 fd_set 结构中对应的位设置为1,表示便通过 select 函数对文件描述符 fd进行监视。

void FD_ZERO(fd_set *set);//清空表,将其所有位都设置为0。

2.3 多路复用实现通信

服务端select_serve.c

#include <stdio.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/time.h>
//基于TCP 的IO多路复用通信
//服务端
int main()
{

	//创建套接字
	int sock_fd = socket(PF_INET,SOCK_STREAM,0);
	
	//初始化本机地址和端口
	struct sockaddr_in srvaddr;
	srvaddr.sin_family = PF_INET;
	srvaddr.sin_port = htons(10000);
	srvaddr.sin_addr.s_addr = inet_addr("192.168.124.151");
	socklen_t srvaddr_len = sizeof(srvaddr);

	//绑定套接字
	bind(sock_fd,(struct sockaddr *)&srvaddr,srvaddr_len);
	//监听
	listen(sock_fd,4);
	
	//等待连接
	printf("等待连接中......\n");
	int conn_fd = accept(sock_fd,NULL,NULL);
	printf("连接成功!!\n");
	
	//定义一个集合
	fd_set jihe;

	while(1)
	{

		FD_ZERO(&jihe);//清空集合,将其所有位都设置为0。
	
		FD_SET(conn_fd,&jihe);//把conn_fd套接字(套接字说白了也是文件描述符)添加进集合

		//标准输入文件STDIN_FILENO-------0
		FD_SET(STDIN_FILENO,&jihe);//把标准输入添加进集合
		
		//多路复用的系统调用,这里监控读操作,
		//一旦有操作select就会被select监控到,接着配合后面的FD_ISSET()判断出具体时集合中的哪个文件描述符
		int ret = select(conn_fd+1,&jihe,NULL,NULL,NULL);
		if(-1 == ret)//监控失败
		{
			perror("select failed");
			continue;
		}
		if(0 == ret)//超时
		{
			continue;
		}
		
		//判断文件描述符是否在集合中 如果已连接套接字在集合,则进行读操作,读取客户端发来的信息
		if(FD_ISSET(conn_fd,&jihe) == 1)
		{
			char rbuf[128]={0};
			read(conn_fd,rbuf,sizeof(rbuf));
			printf("from cli:%s\n",rbuf);
			
			//FD_CLR(conn_fd,&jihe);
		}
		
		//如果我们有标准输入在集合中,则写入,即通过已连接套接字发送给客户端
		if(FD_ISSET(STDIN_FILENO,&jihe) == 1)//判断出是STDIN_FILENO标准输入
		{
			char wbuf[128]={0};//定义缓冲区
			fgets(wbuf,sizeof(wbuf),stdin);//键盘输入
			write(conn_fd,wbuf,sizeof(wbuf));
			
			//FD_CLR(STDIN_FILENO,&jihe);
		}
		
	}
	
}



客户端select_cilent.c

#include <stdio.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/time.h>
//基于TCP 的IO多路复用通信
//客户端
int main()
{
	//创建套接字
	int sock_fd = socket(PF_INET,SOCK_STREAM,0);
	
	//初始化服务端网络地址
	struct sockaddr_in srvaddr;
	srvaddr.sin_family = PF_INET;
	srvaddr.sin_port = htons(10000);
	srvaddr.sin_addr.s_addr = inet_addr("192.168.124.151");
	
	socklen_t srvaddr_len = sizeof(srvaddr);
	
	//这个绑定可有可无
	// bind(sock_fd,(struct sockaddr *)&srvaddr,srvaddr_len);
	
	//listen(sock_fd,4);
	
	//请求连接
	connect(sock_fd,(struct sockaddr *)&srvaddr,srvaddr_len);
	
	//定义一个集合
	fd_set jihe;

	
	while(1)
	{
		FD_ZERO(&jihe);//清空集合
	
		FD_SET(sock_fd,&jihe);//把sock_fd套接字(套接字说白了也是文件描述符)添加进集合
		//标准输入文件STDIN_FILENO-------0
		FD_SET(STDIN_FILENO,&jihe);//把标准输入添加进集合
		
		//多路复用的系统调用,这里监控读操作
		int ret = select(sock_fd+1,&jihe,NULL,NULL,NULL);
		if(-1 == ret)
		{
			perror("select failed");
			continue;
		}
		if(0 == ret)
		{
			continue;
		}
		
		//判断文件描述符是否在集合中 如果已连接套接字在集合,则进行读操作,读取客户端发来的信息
		if(FD_ISSET(sock_fd,&jihe) == 1)
		{
			char rbuf[128]={0};
			read(sock_fd,rbuf,sizeof(rbuf));
			printf("from srv:%s\n",rbuf);
			
			//FD_CLR(sock_fd,&jihe);
		}
		//如果我们有标准输入文件描述符在集合中,则写入,即通过已连接套接字发送给服务端
		if(FD_ISSET(STDIN_FILENO,&jihe) == 1)
		{
			char wbuf[128]={0};
			fgets(wbuf,sizeof(wbuf),stdin);
			write(sock_fd,wbuf,sizeof(wbuf));
			
			//FD_CLR(STDIN_FILENO,&jihe);
		}
		
	}
	
}



基于TCP的多路复用,网络编程,服务器,c语言,网络,udp

3. poll

3.1 优点

poll 函数与 select 原理相似,都需要来回拷贝全部监听的文件描述符,不同的是:

1)poll 函数采用链表的方式替代原来 select 中 fd_set 结构,因此可监听文件描述符数量不受限。

2)poll 函数返回后,可以通过 pollfd 结构中的内容进行处理就绪文件描述符,相比 select 效率要高。

3)新增水平触发:也就是通知程序 fd 就绪后,这次没有被处理,那么下次 poll 的时候会再次通知同个 fd 已经就绪。

3.2 缺点

和 select 函数一样,poll 返回后,需要轮询 pollfd 来获取就绪的描述符。poll和select同样

存在一个性能缺点就是包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,以及个别描述符就绪触发整体描述符集合的遍历的低效问题。而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。

4. epoll

epoll 使用一个文件描述符管理多个描述符,将用户进程监控的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间只需拷贝一次。

创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

4.1 优点

1)没有最大并发连接的限制,能打开的 FD 的上限远大于 1024。

2)效率提升,不是轮询的方式,不会随着 FD 数目的增加效率下降。

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

4)新增 ET 模式。

5. 总结

5.1 select、poll、epoll区别

三种函数在的 Linux 内核里有都能够支持,其中 epoll 是 Linux 所特有,而 select 则应该是 POSIX 所规定,一般操作系统均有实现。

基于TCP的多路复用,网络编程,服务器,c语言,网络,udp

5.2 工作模式

1)LT模式

LT(level triggered)模式:也是默认模式,即当 epoll_wait 检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件,并且下次调用 epoll_wait 时,会再次响应应用程序并通知此事件。

2)ET模式

ET(edge-triggered)模式:当 epoll_wait 检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。

ET 是一种高速工作方式,很大程度上减少了 epoll 事件被重复触发的次数。epoll 工作在 ET 模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。文章来源地址https://www.toymoban.com/news/detail-769469.html

到了这里,关于基于TCP的多路复用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • BIO、NIO、IO多路复用模型详细介绍&Java NIO 网络编程

    上文介绍了网络编程的基础知识,并基于 Java 编写了 BIO 的网络编程。我们知道 BIO 模型是存在巨大问题的,比如 C10K 问题,其本质就是因其阻塞原因,导致如果想要承受更多的请求就必须有足够多的线程,但是足够多的线程会带来内存占用问题、CPU上下文切换带来的性能问题

    2024年02月14日
    浏览(47)
  • TCP IP网络编程(四) 基于TCP的服务器端、客户端

    TCP/IP协议栈 ​ TCP/IP协议栈 TCP/IP协议栈共分为4层,可以理解为数据收发分成了4个层次化过程。 ​ TCP协议栈 ​ UDP协议栈 链路层 链路层是物理连接领域标准化的结果,也是最基本的领域,专门定义LAN、WAN、MAN等网络标准。两台主机通过网络进行数据交换,这需要像下图所示

    2024年01月16日
    浏览(51)
  • TCP IP网络编程(五) 基于TCP的服务器端、客户端 (补充)

    回声客户端出现的问题 在上一节基于TCP的服务器端、回声客户端中,存在问题: 如果数据太大,操作系统就有可能把数据分成多个数据包发送到客户端,客户端有可能在尚未收到全部数据包时就调用read函数 问题出在客户端,而不是服务器端,先来对比一下客户端与服务器端

    2024年02月09日
    浏览(63)
  • 《TCP/IP网络编程》阅读笔记--基于TCP的服务器端/客户端

    目录 1--TCP/IP协议栈 2--TCP服务器端默认函数调用顺序 3--TCP客户端的默认函数调用顺序 4--Linux实现迭代回声服务器端/客户端 5--Windows实现迭代回声服务器端/客户端 6--TCP原理 7--Windows实现计算器服务器端/客户端         TCP/IP协议栈共分 4 层,可以理解为数据收发分成了 4 个层

    2024年02月10日
    浏览(59)
  • 【网络编程】——基于TCP协议实现回显服务器及客户端

    个人主页:兜里有颗棉花糖 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创 收录于专栏【网络编程】【Java系列】 本专栏旨在分享学习网络编程的一点学习心得,欢迎大家在评论区交流讨论💌 TCP提供的API主要有两个类 Socket ( 既会给服务器使用也会给客

    2024年02月03日
    浏览(62)
  • 《TCP/IP网络编程》阅读笔记--基于UDP的服务器端/客户端

    目录 1--TCP和UDP的主要区别 2--基于 UDP 的数据 I/O 函数 3--基于 UDP 的回声服务器端/客户端 4--UDP客户端Socket的地址分配 5--UDP存在数据边界 6--UDP已连接与未连接的设置 ① TCP 提供的是可靠数据传输服务,而 UDP 提供的是不可靠数据传输服务; ② UDP 在结构上比 TCP 更简洁,其不会

    2024年02月09日
    浏览(58)
  • 计算机网络编程 | 多路I/O转接服务器

    欢迎关注博主 Mindtechnist 或加入【Linux C/C++/Python社区】一起学习和分享Linux、C、C++、Python、Matlab,机器人运动控制、多机器人协作,智能优化算法,滤波估计、多传感器信息融合,机器学习,人工智能等相关领域的知识和技术。 专栏:《网络编程》 多路IO转接服务器也叫做多

    2024年02月12日
    浏览(49)
  • Linux网络编程:多路I/O转接服务器(select poll epoll)

    文章目录: 一:select 1.基础API  select函数 思路分析 select优缺点 2.server.c 3.client.c 二:poll 1.基础API  poll函数  poll优缺点 read函数返回值 突破1024 文件描述符限制 2.server.c 3.client.c 三:epoll 1.基础API epoll_create创建   epoll_ctl操作  epoll_wait阻塞 epoll实现多路IO转接思路 epoll优缺点

    2024年02月11日
    浏览(52)
  • 《TCP/IP网络编程》阅读笔记--基于Windows实现Hello Word服务器端和客户端

    目录 1--Hello Word服务器端 2--客户端 3--编译运行 3-1--编译服务器端 3-2--编译客户端 3-3--运行 运行结果:

    2024年02月10日
    浏览(61)
  • 【Linux网络编程】网络编程套接字(TCP服务器)

    作者:爱写代码的刚子 时间:2024.4.4 前言:本篇博客主要介绍TCP及其服务器编码 只介绍基于IPv4的socket网络编程,sockaddr_in中的成员struct in_addr sin_addr表示32位 的IP地址 但是我们通常用点分十进制的字符串表示IP地址,以下函数可以在字符串表示和in_addr表示之间转换 字符串转in

    2024年04月14日
    浏览(74)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包