🎁个人主页:我们的五年
🔍系列专栏:Linux网络编程
🌷追光的人,终会万丈光芒
🎉欢迎大家点赞👍评论📝收藏⭐文章
Linux网络系列文章 | 计算机网络(Linux网络编程)_我们的五年的博客-CSDN博客 |
目录
怎么理解IO?
IO就是input,output,参照物是计算机本身,是计算机系统内部和外部设备进行交互的过程。
input,output参考的对象是计算机本身,input就是相对计算机来说就是向计算机输入,output就是相对计算机来说是向外部设备输出。
IO执行的行为差不多就是等待+拷贝。
IO=等待+拷贝(等待是主要矛盾)
等待外部设备就绪,当外部设备准备好了以后,通过CPU的针脚发送中断信号告知操作系统。操作系统转入内核态,进行拷贝工作。
所以IO基本上就是等待+拷贝这个两个动作。
高效IO
上面说的IO=等待+拷贝
在大多数情况下,时间都浪费在等待上面,因为和等待相比,拷贝要花的时间比等待的时间少的多。
高效的IO就是等待时间变少,拷贝的时间的比重增加。
第一种IO模型:阻塞IO模型
理解阻塞IO
阻塞IO相当于当上层进行系统调用,当外部设备没有准备好的时候,就会一直在等待数据准备好,如果没有准备好,就会一直在等待,不会做任何事情。准备好了以后,就把数据从内核拷贝到用户空间。然后向上层返回成功标识符。
在钓鱼的例子中:
张三去钓鱼,大部分时间在等待鱼上钩(是外界的准备条件,和张三没有关系,也就是说外部设备多久准备好是和计算机无关的)(Like us,we are not meet again,meeting is something l can't determine)。
张三在等待鱼上钩的这段时间,一直看着鱼漂,不做其他的事情。
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
第二种IO模型:非阻塞IO模型
非阻塞IO和阻塞IO最大额区别就是,非阻塞IO在没有数据的时候,直接进行返回,不会等待,并且返回EWOULDBLOCK错误码。
非阻塞IO减少
在man手册中,也有说明。
#include <iostream>
#include <fcntl.h>
#include <unistd.h>
using namespace std;
void SetNoBlock(int fd)
{
int info=fcntl(fd,F_GETFL);
if(info<0)
{
perror("fcntl error");
return;
}
fcntl(fd,F_SETFL,info|O_NONBLOCK);
return;
}
int main()
{
SetNoBlock(0);
while(1)
{
sleep(3);
char buff[1024]={0};
ssize_t n=read(0,buff,sizeof(buff)-1);
if(n<0)
{
perror("read error");
continue;
}
buff[n]=0;
cout<<buff<<endl;
}
return 0;
}
在钓鱼例子中:
李四在把鱼竿抛出去以后,就去干了一会其他的事情,然后就看一下鱼漂有没有动,看一下有没有鱼,然后发现没有鱼,又去干其他的事情。过了一会又来看有没有鱼,又没有鱼,又去干其他的事情。一直这么循环(轮询),到有鱼的时候,就起竿钓上鱼。
第三种IO模型:信号驱动IO模型
内核将数据准备好的时候, 使用 SIGIO 信号通知应用程序进行 IO操作。
和非阻塞IO的区别就是,不会去循环检测(不会去做无用工),而是真正有数据的时候,会发SIGIO信号,这样再次IO的时候,就会成功了。
在钓鱼例子中:
王五不再和李四一样,时不时就去看一下鱼漂有没有动,而是在鱼漂上直接挂了一个铃铛🛎🛎🛎,当鱼来了的时候,数据有的时候,就去起竿。
第四种IO模型:IO多路转接模型
select,poll,epoll都是多路转接,后面就有文章进行详细讲解。
它通过允许单个线程同时监控多个文件描述符的I/O状态,避免了为每个I/O操作创建单独线程所带来的资源消耗和上下文切换开销。
select:适用于小规模并发场景,但效率较低,因为它需要遍历所有文件描述符。
poll:类似于select,但没有文件描述符数量的限制,适用于更大的并发量。
epoll(仅限Linux):通过事件驱动机制高效地处理大量文件描述符,性能优于select和poll。
kqueue(仅限BSD系统):类似于epoll,支持高效的事件通知机制。
看下面的图好像和阻塞IO没有区别,但是重点就是多路转接可以一次等待几个文件,等待几个文件的状态(一切皆文件嘛)。
在钓鱼例子中:
赵六拿了很多的鱼竿, 一个人就管理多个鱼竿,调起鱼的概率更大,也就是拷贝的时间的比重增加,就是高效的IO。
第五种IO模型:异步IO模型
上面的四种都是同步IO。参与了等待和拷贝的任意一个动作就是同步IO。
他们参与了等待或者拷贝这两个动作的其中一个或者两个。在阻塞IO中,参与了等待和拷贝,在非阻塞IO中,最起码参与了拷贝,所以也是同步IO。多路转接管理多个文件,也可以设置为非阻塞多路转接。
异步IO就直接返回,等待+拷贝都让系统来做。弄好以后就通过事件,信号等告知已经处理好了。
在钓鱼例子中:
田七钓鱼,但是不自己调,不自己等待,也不自己起竿(拷贝过程),全交给他的朋友。当鱼钓好了,就打电话通知田七,这就是异步IO。
同步通信和异步通信
同步通信就是要参与等待和拷贝的过程,参与了一个就是同步。
异步通信是也要进行拷贝和等待,但是两个过程都交给系统去做了。调用者不需要关心这两个过程就可以了。等系统处理好,就可以通知调用者。
另外,线程同步和线程互斥不是这样的概念.
线程同步和线程互斥是为了保护临界资源,保证资源的一致性。
阻塞和非阻塞
阻塞调用是指,在调用成功之前,线程会被挂起,在得到结果以后才会被返回。
而非阻塞直接进行返回,该调用不会阻塞线程。
fcntl函数
一个文件描述符,默认是阻塞的。
函数原型
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
cmd是命令,是要操作的类型。主要的操作类型有:
1.获取,设置文件状态信息:cmd=F_GETFL,F_SETFL。
2.复制现有的描述符,cmd=F_DUPFD。
3.获取,设置文件描述符标识,cmd=F_GETFD,F_SETFD。
4.获取,设置异步IO所有权,cmd=F_GETOWN,F_SETOWN。
5.获取、设置记录锁,cmd=F_GETLK,F_SETLK,F_SETLKW。
cmd | 功能 |
F_GETFL,F_SETFL | 获取,设置文件状态信息 |
F_DUPFD | 复制现有的描述符 |
F_GETFD,F_SETFD | 获取,设置文件描述符标识 |
F_GETOWN,F_SETOWN | 获取,设置异步IO所有权 |
F_GETLK,F_SETLK,F_SETLKW | 获取、设置记录锁 |
根据cmd的不同,可能需要传递额外的参数,比如set类的,就要加入文件状态信息。比如O_NONBLOCK,就是设置非阻塞信息。
F_DUPFD功能
在F_DUPFD中,返回的是新的文件描述符,后面可以设置新的文章描述符的最小值。如果这个值被占用,就是生成比这个值更大的了。
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
using namespace std;
int main() {
int fd = open("example.txt", O_RDONLY|O_CREAT);
if (fd == -1) {
perror("open");
return 1;
}
int new_fd = fcntl(fd, F_DUPFD, 20); // 复制文件描述符,新描述符值大于等于10
if (new_fd == -1) {
perror("fcntl");
close(fd);
return 1;
}
printf("Original fd: %d, New fd: %d\n", fd, new_fd);
cout<<"fd:"<<fcntl(fd,F_GETFL)<<",New fd:"<<fcntl(new_fd,F_GETFL)<<endl;
close(new_fd);
close(fd);
return 0;
}
F_GETFL和F_SETFL功能
使用 F_GETFL
和 F_SETFL
命令可以获取和设置文件状态标志。文件状态标志包括 O_APPEND
、O_NONBLOCK
。
非阻塞:O_NONBLOCK。
#include <iostream>
#include <fcntl.h>
#include <unistd.h>
using namespace std;
void SetNoBlock(int fd)
{
int info=fcntl(fd,F_GETFL);
if(info<0)
{
perror("fcntl error");
return;
}
fcntl(fd,F_SETFL,info|O_NONBLOCK);
return;
}
int main()
{
SetNoBlock(0);
while(1)
{
sleep(3);
char buff[1024]={0};
ssize_t n=read(0,buff,sizeof(buff)-1);
if(n<0)
{
perror("read error");
continue;
}
buff[n]=0;
cout<<buff<<endl;
}
return 0;
}
结束语
至此,我们已经深入探讨了计算机科学中的诸多重要概念,包括I/O操作、线程同步与互斥、文件描述符管理等。理解这些概念对于编写高效、可靠的程序至关重要。希望本文能够为您的学习和实践提供有益的参考。如果您在实际编程中遇到任何问题,建议查阅相关文档或进一步研究具体案例。感谢您的阅读,祝您在编程的道路上不断进步!