概述
提高通信的效率(对单个进程来说),在客观的环境下发送和接收是不可能同时接近并发的,
可以实现单进程(像同时)发送和接收
针对发送的文件描述符是:套接字write,标准输入:stdin /dev/stdin 往 /dev/stdin读数据
针对接收的文件描述符是:套接字 , 标准输出 printf("hello"); 把hello往 /dev/stdout文件写
① 多路复用的基本概念
最大的限度提高当进程的工作效率(模拟并发,但是因为是模拟,肯定不是完全意义上的并发,轮询机制监听多个文件描述符,实现多客户访问)
最基本的工作:
发信息
收信息
多路复用的接口:
select
poll
epoll
② select多路复用的基本理解
在没有多路复用:
如果我们想同时接受和发送消息,一般会创建俩条线程,一条去读,一条去写(用两条线程去监控两个文件描述符: 标准输入0 和 套接字文件描述符)
有多路复用:监听需要监听的文件描述符,轮询(循环)机制来监听文件描述符
文件描述符有三个模式:可不可读 ,可不可写,可不可执行
read(文件描述符,); 3
scanf();监视标准输入的文件描述符
scanf: stdin 监控可不可读 0
read: 套接字 监控可不可读
while(1)
{
select() 0 4 文件描述符的取值范围
{
4
while(1)
{
for(int n=0; n<要监控的文件描述符+1; n++)
{
里面的代码就是判断循环到的n是否可读、是否可写、是否可执行、
if(如果这个文件描述符可读了) break;
}
break;
}
return 告诉我现在找到了可读的文件描述符
}
if(是不是可以发送啦?)
{
scanf()
write
}
if(是不是可以读取)
{
我在外面知道了这个文件描述符可以读取,我再去调用 read 这个文件描述符
read
}
}
譬如该多路复用用于服务器,那么需要监听那两个文件描述符:_客户端套接字(读)12__ 和 __标准输入(发送)_0_
客户端TCP多路复用使用代码思路
创建套接字
连接服务器
-----多路复用----
创建文件描述符集
清空文件描述符集
添加要监听的文件描述符到 文件描述符集里面
while(1)
{
调用select进行监听集合中的文件描述符
select();
if(能不能去读键盘啊)
{
scanf()
write()
}
if(能不能去接收信息啊)
{
read();
printf("");
}
}
关闭套接字
服务器TCP多路复用使用代码思路
创建套接字
绑定套接字
监听
等待连接
创建文件描述符集
清空文件描述符集
把要监听的标准输入+套接字(客户端的对等套接字)添加到文件描述符集中
while(1)
{
调用select进行监听集合中的文件描述符
select();
if(能不能去读键盘啊)
{
scanf()
write()
}
if(能不能去接收信息啊)
{
read();
printf("");
}
}
关闭套接字
select函数描述
③ select多路复用服务器
/******************************************************************************************************
* @file name: : select多路复用服务器.c
* @brief : 多路复用
* @author : [email protected]
* @date : 2024/08/16
* @version : V1.0
* @property : 暂无
* @note : None
* CopyRight (c) 2023-2024 [email protected] All Right Reseverd
******************************************************************************************************/
/*********************************************头文件***************************************************/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
/**********************************************END*****************************************************/
int main()
{
//创建服务器套接字
int s_fd = socket(AF_INET,SOCK_STREAM,0);
if(s_fd == -1)
{
perror("socket");
return -1;
}
//定义结构体变量
struct sockaddr_in ser_addr;
memset(&ser_addr,0,sizeof(ser_addr));
ser_addr.sin_family = AF_INET;
ser_addr.sin_port = htons(8888);
ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);
//绑定
if(bind(s_fd,(struct sockaddr *)&ser_addr,sizeof(ser_addr)) == -1)
{
perror("bind");
return -1;
}
if(listen(s_fd,5) == -1)
{
perror("listen");
return -1;
}
int c_fd = accept(s_fd,NULL,NULL);
if(c_fd == -1)
{
perror("accept");
return -1;
}
//使用多路复用 进行监听而不是创建两条线程去双向通信
//创建一个文件描述符的集合,把要监视的文件描述符添加进这个集合里面
fd_set fs;
char msg[20];
while(1)
{
//清空这个集合
FD_ZERO(&fs);
//再把两个文件描述符添加进去
FD_SET(c_fd,&fs);
FD_SET(STDIN_FILENO,&fs);
//再调用select去监视 ---- 默认是堵塞的
memset(msg,0,20);
if(-1 == select(c_fd+1,&fs,NULL,NULL,NULL))
{
perror("select");
return -1;
}
if(FD_ISSET(c_fd,&fs))//判断是不是c_fd可以读了
{
read(c_fd,msg,20);
printf("%s\n",msg);
}
if(FD_ISSET(STDIN_FILENO,&fs))////判断是不是STDIN_FILENO可以读了
{
printf("请输入要发送的消息");
scanf("%s",msg);
write(c_fd,msg,strlen(msg));
}
}
close(s_fd);
return 0;
}
/******************************************************************************************************
* @file name: : tcp多路复用客户端.c
* @brief : 客户端的多路复用
* @author :[email protected]
* @date :2024/08/16
* @version :V1.0
* @property :暂无
* @note :None
* CopyRight (c) 2023-2024 [email protected] All Right Reseverd
******************************************************************************************************/
/*****************************************头文件********************************************************/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
/******************************************END*********************************************************/
int main()
{
int c_fd = socket(AF_INET,SOCK_STREAM,0);
if(c_fd == -1)
{
perror("socket");
return -1;
}
struct sockaddr_in ser_addr;
memset(&ser_addr,0,sizeof(ser_addr));
ser_addr.sin_family = AF_INET;
ser_addr.sin_port = htons(8888);
ser_addr.sin_addr.s_addr = inet_addr("192.168.12.99");
if(-1 == connect(c_fd,(struct sockaddr *)&ser_addr,sizeof(ser_addr)))
{
perror("connect");
return -1;
}
//使用多路复用 进行监听而不是创建两条线程去双向通信
//创建一个文件描述符的集合,把要监视的文件描述符添加进这个集合里面
fd_set fs;
char msg[20];
while(1)
{
//清空这个集合
FD_ZERO(&fs);
//再把两个文件描述符添加进去
FD_SET(c_fd,&fs);
FD_SET(STDIN_FILENO,&fs);
//再调用select去监视 ---- 默认是堵塞的
memset(msg,0,20);
if(-1 == select(c_fd+1,&fs,NULL,NULL,NULL))
{
perror("select");
return -1;
}
if(FD_ISSET(c_fd,&fs))//判断是不是c_fd可以读了
{
read(c_fd,msg,20);
printf("%s\n",msg);
}
if(FD_ISSET(STDIN_FILENO,&fs))////判断是不是STDIN_FILENO可以读了
{
printf("请输入要发送的消息");
scanf("%s",msg);
write(c_fd,msg,strlen(msg));
}
}
close(c_fd);
return 0;
}
⑤ 设置套接字为非堵塞状态
多路复用的时候,为什么要设置套接字为非堵塞状态?
在使用 poll()和select() 进行多路复用时,设置套接字为非阻塞是一个常见的做法,但并不是必须的。设置套接字为非阻塞的目的是为了确保 poll()和select(??,) 在等待事件时不会被阻塞,从而能够处理其他事件或执行其他任务。
如果套接字被设置为阻塞模式,并且没有数据可读或者写入操作无法立即完成,poll() 将会被阻塞,直到有事件发生或者超时。在这种情况下,poll()和select() 将无法实现真正的多路复用,因为它会一直等待某个套接字上的事件,而不会检查其他套接字。
因此,尽管不是绝对必须的,但为了实现有效的多路复用,通常建议将套接字设置为非阻塞模式。这样一来,poll()和select() 将能够同时监视多个套接字,当任何一个套接字上发生事件时都能及时响应,而不会被阻塞。
使用fcntl设置套接字为非阻塞:
fcntl的作用:
复制文件描述符(拓展非主要)、获取/设置文件描述符标志、获取/设置记录锁(拓展非主要)、获取/设置异步 I/O 执行状态(拓展非主要)、获取/设置套接字选项(拓展非主要)
示例:
基本流程思路:获取套接字标志,设置标志为非堵塞对,再把具有非堵塞的标志设置到套接字中
// 创建套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
// 设置非阻塞模式
flags = fcntl(sockfd, F_GETFL, 0);
if (flags == -1) {
perror("fcntl");
close(sockfd);
exit(EXIT_FAILURE);
}
flags |= O_NONBLOCK;
if (fcntl(sockfd, F_SETFL, flags) == -1) {
perror("fcntl");
close(sockfd);
exit(EXIT_FAILURE);
}
标签:addr,多路复用,描述符,fd,IO,接字,include
From: https://www.cnblogs.com/hhail08/p/18363465