标签 Linux 下的文章 - 酷游博客
首页
关于
友链
Search
1
阿里的简历多久可以投递一次?次数多了有没有影响?可以同时进行吗?
45 阅读
2
Java中泛型的理解
40 阅读
3
Java 14 发布了,再也不怕 NullPointerException 了!
38 阅读
4
Java中的可变参数
37 阅读
5
该如何创建字符串,使用" "还是构造函数?
29 阅读
技术
登录
/
注册
找到
4
篇与
Linux
相关的结果
2025-01-22
Linux中RPM文件操作常用命令
在Linux操作系统中,有一个系统软件包,它的功能类似于Windows里面的“添加/删除程序”,但是功能又比“添加/删除程序”强很多,它就是Red Hat Package Manager(简称RPM)。 本文简单介绍rpm操作的常用命令 RPM 安装操作 命令: rpm -i 需要安装的包文件名 举例如下: rpm -i example.rpm 安装 example.rpm 包; rpm -iv example.rpm 安装 example.rpm 包并在安装过程中显示正在安装的文件信息; rpm -ivh example.rpm 安装 example.rpm 包并在安装过程中显示正在安装的文件信息及安装进度; RPM 查询操作 命令: rpm -q … 附加查询命令: a 查询所有已经安装的包以下两个附加命令用于查询安装包的信息; i 显示安装包的信息; l 显示安装包中的所有文件被安装到哪些目录下; s 显示安装版中的所有文件状态及被安装到哪些目录下;以下两个附加命令用于指定需要查询的是安装包还是已安装后的文件; p 查询的是安装包的信息; f 查询的是已安装的某文件信息; 举例如下: rpm -qa | grep tomcat4 查看 tomcat4 是否被安装; rpm -qip example.rpm 查看 example.rpm 安装包的信息; rpm -qif /bin/df 查看/bin/df 文件所在安装包的信息; rpm -qlf /bin/df 查看/bin/df 文件所在安装包中的各个文件分别被安装到哪个目录下; RPM 卸载操作 命令: rpm -e 需要卸载的安装包 在卸载之前,通常需要使用rpm -q …命令查出需要卸载的安装包名称。 举例如下: rpm -e tomcat4 卸载 tomcat4 软件包 RPM 升级操作 命令: rpm -U 需要升级的包 举例如下: rpm -Uvh example.rpm 升级 example.rpm 软件包 RPM 验证操作 命令: rpm -V 需要验证的包 举例如下: rpm -Vf /etc/tomcat4/tomcat4.conf 输出信息类似如下: S.5....T c /etc/tomcat4/tomcat4.conf 其中,S 表示文件大小修改过,T 表示文件日期修改过。限于篇幅,更多的验证信息请您参考rpm 帮助文件:man rpm RPM 的其他附加命令 --force 强制操作 如强制安装删除等; --requires 显示该包的依赖关系; --nodeps 忽略依赖关系并继续操作
技术
# Linux
酷游
1月22日
0
12
0
2025-01-22
ubuntu搭建开发环境:ubuntu+jdk+jboss+apache+maven+svn+eclipse
一、首先安装ubuntu操作系统 准备工作 一台普通电脑(装没装操作系统无所谓),保证电脑硬盘上有10G的空闲空间。 一个U盘,保证U盘上有2G的空闲空间。 步骤: 下载最新版的Ubuntu桌面操作系统,下载地址 下载USB Installer的工具,下载地址下载完后无需安装,直接运行。在下面这个界面的Step1里选你的镜像包的版本,Step2里选你下载的iso文件,Step3里选你的U盘,其他保持默认。点击create按钮。 完成以上步骤,就可以从优盘启动安装ubuntu了。关机从优盘启动(开机时按f12进入bios,然后选择从优盘启动),进入之后就会看到ubuntu安装界面,选择安装即可。 ubuntu安装好之后,默认输入法是英文的。可以安装搜狗输入法。download urlinstall url 安装完搜狗输入法之后,在桌面右上角小键盘,配置当前输入法,然后把搜狗输入法添加上就可以了,使用ctrl+空格键切换输入法。 二、安装jdk 到oracle官网下载jdk进行安装,值得注意的是:最好下载.tar.gz文件,这种类型的文件可以直接解压使用 另外查看自己本机的系统环境,是64位还是32位,下载对应版本的jdk。 查看本机信息方式: 桌面右上角的功能键--->About This Computer即可查看系统信息。 例如下载: jdk-7u71-linux-x64.tar.gz文件下载以后,复制到安装目录下,例如:~/tools/java然后解压文件:sudo tar -zxvf jdk-7u71-linux-x64.tar.gz 解压之后,会在目录下看到jdk1.7.0_71目录,这个时候,理论上已经安装好了jdk, 接下来为了可以使用jdk提供的命令,我们配置环境变量: 以root身份打开并编辑profile文件sudo vim /etc/profile (如果提示没有权限,就更改该文件的读写权限,如果提示vim命令不可用,就使用命令 sudo apt-get install vim 安装vim) 在文件中添加如下内容:其中的~/tools/java/jdk1.7.0_71改为你安装jdk的全路径\#set java environmentexport JAVA_HOME=/home/hollis/tools/java/jdk1.7.0_71export JRE_HOME=/home/hollis/tools/java/jdk1.7.0_71/jreexport CLASSPATH=$JAVA_HOME/lib:$JRE_HOME/lib:$CLASSPATHexport PATH=$JAVA_HOME/bin:$JRE_HOME/bin:$PATH这个时候,理论上我们已经安装并配置好了jdk,使用java -version命令即可查看安装好的java的版本信息, 但是由于ubuntu默认有openjdk,系统会提示有多个地方包含java命令,那么我们就修改默认配置,使我们安装好的jdk成为默认的:sudo update-alternatives --install /usr/bin/java java ~/tools/java/jdk1.7.0_71/bin/java300sudo update-alternatives --install /usr/bin/javac javac ~/tools/java/jdk1.7.0_71/bin/javac300sudo update-alternatives --config java至此,jdk已经全部安装并配置完成,使用命令 java -version即可查看到一下信息:“`java version “1.7.0_71” Java(TM) SE Runtime Environment (build 1.7.0_71-b14) Java HotSpot(TM) 64-Bit Server VM (build 24.71-b01, mixed mode) “` 三、安装apache apache:下载地址从官网上下载apache2,然后解压(tar -xzvf)编译 (configure / make / make install) 四、jboss 安装 下载安装包:http://jbossas.jboss.org/downloads.html下载后解压即可 五、安装svn sudo apt-get install subversion 六、安装maven http://maven.apache.org/download.cgi 解压:sudo tar -zxvf apache-maven-3.2.3-bin.tar.gz 解压完之后,同样设置环境变量,还是打开/etc/profile sudo vim /etc/profile在文件中加入:“` set maven environment M2_HOME=/home/hollis/tools/maven/apache-maven-3.2.3export MAVEN_OPTS=”-Xms256m -Xmx512m”export PATH=$M2_HOME/bin:$PATH“`保存退出,然后使用source /etc/profile命令使配置生效。 使用mvn -version,即可查看maven是否安装成功 安装并配置好maven之后,我们还要配置setting文件,使用命令 mvn clean 系统会自动生成settings.xml文件。将文件拷贝到~/.m2下。接下来就是修改.m2文件夹下的settings.xml文件 七、安装eclipse: eclipse官网下载eclispe下载.tar.gz文件后解压,即可使用 八、安装eclipse下的svn插件 插件下载地址 下载之后解压,讲plugins文件夹下面的文件复制到eclipse安装目录下的plugins文件夹下。将features文件夹下面的文件复制到eclipse文件夹下面的features文件夹下 然后执行命令:apt-get install libsvn-java # Use sudo in Ubuntu安装javaHL 修改eclipse.ini文件,在-vmargs之后添加:- Djava.library.path=/usr/lib/jni:/usr/lib/x86_64-linux-gnu/jni 九、安装eclipse下的findbugs插件 点击Help->InstallNew Software点击Add,然后在弹出框Name输入findBugs,Location输入http://findbugs.cs.umd.edu/eclipse,点击“OK” @璞尧
技术
# Linux
酷游
1月22日
0
14
0
2025-01-22
[转]Linux IO模式及 select、poll、epoll详解
转载自:https://segmentfault.com/a/1190000003063859 最近在看Linux的IO模型,看到这样一篇文章,写的很好,转载过来经常看下。 同步IO和异步IO,阻塞IO和非阻塞IO分别是什么,到底有什么区别?不同的人在不同的上下文下给出的答案是不同的。所以先限定一下本文的上下文。 本文讨论的背景是Linux环境下的network IO。 一 概念说明 在进行解释之前,首先要说明几个概念: 用户空间和内核空间 进程切换 进程的阻塞 文件描述符 缓存 I/O 用户空间与内核空间 现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方)。操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操心系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。针对linux操作系统而言,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为内核空间,而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为用户空间。 进程切换 为了控制进程的执行,内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起的某个进程的执行。这种行为被称为进程切换。因此可以说,任何进程都是在操作系统内核的支持下运行的,是与内核紧密相关的。 从一个进程的运行转到另一个进程上运行,这个过程中经过下面这些变化: 保存处理机上下文,包括程序计数器和其他寄存器。 更新PCB信息。 把进程的PCB移入相应的队列,如就绪、在某事件阻塞等队列。 选择另一个进程执行,并更新其PCB。 更新内存管理的数据结构。 恢复处理机上下文。 注:总而言之就是很耗资源,具体的可以参考这篇文章:进程切换 进程的阻塞 正在执行的进程,由于期待的某些事件未发生,如请求系统资源失败、等待某种操作的完成、新数据尚未到达或无新工作做等,则由系统自动执行阻塞原语(Block),使自己由运行状态变为阻塞状态。可见,进程的阻塞是进程自身的一种主动行为,也因此只有处于运行态的进程(获得CPU),才可能将其转为阻塞状态。当进程进入阻塞状态,是不占用CPU资源的。 文件描述符fd 文件描述符(File descriptor)是计算机科学中的一个术语,是一个用于表述指向文件的引用的抽象化概念。 文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。但是文件描述符这一概念往往只适用于UNIX、Linux这样的操作系统。 缓存 I/O 缓存 I/O 又被称作标准 I/O,大多数文件系统的默认 I/O 操作都是缓存 I/O。在 Linux 的缓存 I/O 机制中,操作系统会将 I/O 的数据缓存在文件系统的页缓存( page cache )中,也就是说,数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。 缓存 I/O 的缺点: 数据在传输过程中需要在应用程序地址空间和内核进行多次数据拷贝操作,这些数据拷贝操作所带来的 CPU 以及内存开销是非常大的。 二 IO模式 刚才说了,对于一次IO访问(以read举例),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。所以说,当一个read操作发生时,它会经历两个阶段: 等待数据准备 (Waiting for the data to be ready) 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process) 正式因为这两个阶段,linux系统产生了下面五种网络模式的方案。 阻塞 I/O(blocking IO) 非阻塞 I/O(nonblocking IO) I/O 多路复用( IO multiplexing) 信号驱动 I/O( signal driven IO) 异步 I/O(asynchronous IO) 注:由于signal driven IO在实际中并不常用,所以我这只提及剩下的四种IO Model。 阻塞 I/O(blocking IO) 在linux中,默认情况下所有的socket都是blocking,一个典型的读操作流程大概是这样: 当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据(对于网络IO来说,很多时候数据在一开始还没有到达。比如,还没有收到一个完整的UDP包。这个时候kernel就要等待足够的数据到来)。这个过程需要等待,也就是说数据被拷贝到操作系统内核的缓冲区中是需要一个过程的。而在用户进程这边,整个进程会被阻塞(当然,是进程自己选择的阻塞)。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。 所以,blocking IO的特点就是在IO执行的两个阶段都被block了。 非阻塞 I/O(nonblocking IO) linux下,可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时,流程是这个样子: 当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。 所以,nonblocking IO的特点是用户进程需要不断的主动询问kernel数据好了没有。 I/O 多路复用( IO multiplexing) IO multiplexing就是我们说的select,poll,epoll,有些地方也称这种IO方式为event driven IO。select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select,poll,epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。 当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。 所以,I/O 多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select()函数就可以返回。 这个图和blocking IO的图其实并没有太大的不同,事实上,还更差一些。因为这里需要使用两个system call (select 和 recvfrom),而blocking IO只调用了一个system call (recvfrom)。但是,用select的优势在于它可以同时处理多个connection。 所以,如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。) 在IO multiplexing Model中,实际中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。 异步 I/O(asynchronous IO) Linux下的asynchronous IO其实用得很少。先看一下它的流程: 用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。 总结 blocking和non-blocking的区别 调用blocking IO会一直block住对应的进程直到操作完成,而non-blocking IO在kernel还准备数据的情况下会立刻返回。 synchronous IO和asynchronous IO的区别 在说明synchronous IO和asynchronous IO的区别之前,需要先给出两者的定义。POSIX的定义是这样子的: A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes; An asynchronous I/O operation does not cause the requesting process to be blocked; 两者的区别就在于synchronous IO做”IO operation”的时候会将process阻塞。按照这个定义,之前所述的blocking IO,non-blocking IO,IO multiplexing都属于synchronous IO。 有人会说,non-blocking IO并没有被block啊。这里有个非常“狡猾”的地方,定义中所指的”IO operation”是指真实的IO操作,就是例子中的recvfrom这个system call。non-blocking IO在执行recvfrom这个system call的时候,如果kernel的数据没有准备好,这时候不会block进程。但是,当kernel中数据准备好的时候,recvfrom会将数据从kernel拷贝到用户内存中,这个时候进程是被block了,在这段时间内,进程是被block的。 而asynchronous IO则不一样,当进程发起IO 操作之后,就直接返回再也不理睬了,直到kernel发送一个信号,告诉进程说IO完成。在这整个过程中,进程完全没有被block。 各个IO Model的比较如图所示: 通过上面的图片,可以发现non-blocking IO和asynchronous IO的区别还是很明显的。在non-blocking IO中,虽然进程大部分时间都不会被block,但是它仍然要求进程去主动的check,并且当数据准备完成以后,也需要进程主动的再次调用recvfrom来将数据拷贝到用户内存。而asynchronous IO则完全不同。它就像是用户进程将整个IO操作交给了他人(kernel)完成,然后他人做完后发信号通知。在此期间,用户进程不需要去检查IO操作的状态,也不需要主动的去拷贝数据。 三 I/O 多路复用之select、poll、epoll详解 select,poll,epoll都是IO多路复用的机制。I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。(这里啰嗦下) select int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); select 函数监视的文件描述符分3类,分别是writefds、readfds、和exceptfds。调用后select函数会阻塞,直到有描述副就绪(有数据 可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。当select函数返回后,可以 通过遍历fdset,来找到就绪的描述符。 select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点。select的一 个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但 是这样也会造成效率的降低。 poll int poll (struct pollfd *fds, unsigned int nfds, int timeout); 不同与select使用三个位图来表示三个fdset的方式,poll使用一个 pollfd的指针实现。 struct pollfd { int fd; /* file descriptor */ short events; /* requested events to watch */ short revents; /* returned events witnessed */ }; pollfd结构包含了要监视的event和发生的event,不再使用select“参数-值”传递的方式。同时,pollfd并没有最大数量限制(但是数量过大后性能也是会下降)。 和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符。 从上面看,select和poll都需要在返回后,通过遍历文件描述符来获取已经就绪的socket。事实上,同时连接的大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降。 epoll epoll是在2.6内核中提出的,是之前的select和poll的增强版本。相对于select和poll来说,epoll更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。 一 epoll操作过程 epoll操作过程需要三个接口,分别如下: int epoll_create(int size);//创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout); 1. int epoll_create(int size); 创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大,这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值,参数size并不是限制了epoll所能监听的描述符最大个数,只是对内核初始分配内部数据结构的一个建议。 当创建好epoll句柄后,它就会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。 2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 函数是对指定描述符fd执行op操作。 epfd:是epoll_create()的返回值。 op:表示op操作,用三个宏来表示:添加EPOLL_CTL_ADD,删除EPOLL_CTL_DEL,修改EPOLL_CTL_MOD。分别添加、删除和修改对fd的监听事件。 fd:是需要监听的fd(文件描述符) epoll_event:是告诉内核需要监听什么事,struct epoll_event结构如下: struct epoll_event { __uint32_t events; /* Epoll events / epoll_data_t data; / User data variable */ }; //events可以是以下几个宏的集合: EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭); EPOLLOUT:表示对应的文件描述符可以写; EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来); EPOLLERR:表示对应的文件描述符发生错误; EPOLLHUP:表示对应的文件描述符被挂断; EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。 EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里 3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout); 等待epfd上的io事件,最多返回maxevents个事件。 参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。 二 工作模式 epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默认模式,LT模式与ET模式的区别如下: LT模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。 ET模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。 1. LT模式 LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的。 2. ET模式 ET(edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once) ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。 3. 总结 假如有这样一个例子: 1. 我们已经把一个用来从管道中读取数据的文件句柄(RFD)添加到epoll描述符 2. 这个时候从管道的另一端被写入了2KB的数据 3. 调用epoll_wait(2),并且它会返回RFD,说明它已经准备好读取操作 4. 然后我们读取了1KB的数据 5. 调用epoll_wait(2)…… LT模式: 如果是LT模式,那么在第5步调用epoll_wait(2)之后,仍然能受到通知。 ET模式: 如果我们在第1步将RFD添加到epoll描述符的时候使用了EPOLLET标志,那么在第5步调用epoll_wait(2)之后将有可能会挂起,因为剩余的数据还存在于文件的输入缓冲区内,而且数据发出端还在等待一个针对已经发出数据的反馈信息。只有在监视的文件句柄上发生了某个事件的时候 ET 工作模式才会汇报事件。因此在第5步的时候,调用者可能会放弃等待仍在存在于文件输入缓冲区内的剩余数据。 当使用epoll的ET模型来工作时,当产生了一个EPOLLIN事件后, 读数据的时候需要考虑的是当recv()返回的大小如果等于请求的大小,那么很有可能是缓冲区还有数据未读完,也意味着该次事件还没有处理完,所以还需要再次读取: while(rs){ buflen = recv(activeevents[i].data.fd, buf, sizeof(buf), 0); if(buflen < 0){ // 由于是非阻塞的模式,所以当errno为EAGAIN时,表示当前缓冲区已无数据可读 // 在这里就当作是该次事件已处理处. if(errno == EAGAIN){ break; } else{ return; } } else if(buflen == 0){ // 这里表示对端的socket已正常关闭. } if(buflen == sizeof(buf){ rs = 1; // 需要再次读取 } else{ rs = 0; } } Linux中的EAGAIN含义 Linux环境下开发经常会碰到很多错误(设置errno),其中EAGAIN是其中比较常见的一个错误(比如用在非阻塞操作中)。 从字面上来看,是提示再试一次。这个错误经常出现在当应用程序进行一些非阻塞(non-blocking)操作(对文件或socket)的时候。 例如,以 O_NONBLOCK的标志打开文件/socket/FIFO,如果你连续做read操作而没有数据可读。此时程序不会阻塞起来等待数据准备就绪返回,read函数会返回一个错误EAGAIN,提示你的应用程序现在没有数据可读请稍后再试。 又例如,当一个系统调用(比如fork)因为没有足够的资源(比如虚拟内存)而执行失败,返回EAGAIN提示其再调用一次(也许下次就能成功)。 三 代码演示 下面是一段不完整的代码且格式不对,意在表述上面的过程,去掉了一些模板代码。 #define IPADDRESS "127.0.0.1" #define PORT 8787 #define MAXSIZE 1024 #define LISTENQ 5 #define FDSIZE 1000 #define EPOLLEVENTS 100 listenfd = socket_bind(IPADDRESS,PORT); struct epoll_event events[EPOLLEVENTS]; //创建一个描述符 epollfd = epoll_create(FDSIZE); //添加监听描述符事件 add_event(epollfd,listenfd,EPOLLIN); //循环等待 for ( ; ; ){ //该函数返回已经准备好的描述符事件数目 ret = epoll_wait(epollfd,events,EPOLLEVENTS,-1); //处理接收到的连接 handle_events(epollfd,events,ret,listenfd,buf); } //事件处理函数 static void handle_events(int epollfd,struct epoll_event *events,int num,int listenfd,char *buf) { int i; int fd; //进行遍历;这里只要遍历已经准备好的io事件。num并不是当初epoll_create时的FDSIZE。 for (i = 0;i < num;i++) { fd = events[i].data.fd; //根据描述符的类型和事件类型进行处理 if ((fd == listenfd) &&(events[i].events & EPOLLIN)) handle_accpet(epollfd,listenfd); else if (events[i].events & EPOLLIN) do_read(epollfd,fd,buf); else if (events[i].events & EPOLLOUT) do_write(epollfd,fd,buf); } } //添加事件 static void add_event(int epollfd,int fd,int state){ struct epoll_event ev; ev.events = state; ev.data.fd = fd; epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&ev); } //处理接收到的连接 static void handle_accpet(int epollfd,int listenfd){ int clifd; struct sockaddr_in cliaddr; socklen_t cliaddrlen; clifd = accept(listenfd,(struct sockaddr*)&cliaddr,&cliaddrlen); if (clifd == -1) perror("accpet error:"); else { printf("accept a new client: %s:%d\n",inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port); //添加一个客户描述符和事件 add_event(epollfd,clifd,EPOLLIN); } } //读处理 static void do_read(int epollfd,int fd,char *buf){ int nread; nread = read(fd,buf,MAXSIZE); if (nread == -1) { perror("read error:"); close(fd); //记住close fd delete_event(epollfd,fd,EPOLLIN); //删除监听 } else if (nread == 0) { fprintf(stderr,"client close.\n"); close(fd); //记住close fd delete_event(epollfd,fd,EPOLLIN); //删除监听 } else { printf("read message is : %s",buf); //修改描述符对应的事件,由读改为写 modify_event(epollfd,fd,EPOLLOUT); } } //写处理 static void do_write(int epollfd,int fd,char *buf) { int nwrite; nwrite = write(fd,buf,strlen(buf)); if (nwrite == -1){ perror("write error:"); close(fd); //记住close fd delete_event(epollfd,fd,EPOLLOUT); //删除监听 }else{ modify_event(epollfd,fd,EPOLLIN); } memset(buf,0,MAXSIZE); } //删除事件 static void delete_event(int epollfd,int fd,int state) { struct epoll_event ev; ev.events = state; ev.data.fd = fd; epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,&ev); } //修改事件 static void modify_event(int epollfd,int fd,int state){ struct epoll_event ev; ev.events = state; ev.data.fd = fd; epoll_ctl(epollfd,EPOLL_CTL_MOD,fd,&ev); } //注:另外一端我就省了 四 epoll总结 在 select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一 个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait() 时便得到通知。(此处去掉了遍历文件描述符,而是通过监听回调的的机制。这正是epoll的魅力所在。) epoll的优点主要是一下几个方面: 1. 监视的描述符数量不受限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左 右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。select的最大缺点就是进程打开的fd是有数量限制的。这对 于连接数量比较大的服务器来说根本不能满足。虽然也可以选择多进程的解决方案( Apache就是这样实现的),不过虽然linux上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以也不是一种完美的方案。 IO的效率不会随着监视fd的数量的增长而下降。epoll不同于select和poll轮询的方式,而是通过每个fd定义的回调函数来实现的。只有就绪的fd才会执行回调函数。 如果没有大量的idle -connection或者dead-connection,epoll的效率并不会比select/poll高很多,但是当遇到大量的idle- connection,就会发现epoll的效率大大高于select/poll。
技术
# Linux
酷游
1月22日
0
8
0
2025-01-22
Linux基本操作——Linux常用命令
为了高效的使用Linux系统,使用Linux命令是必不可少的,有很多常用的Linux命令更是要必须掌握的。 一,日期和时间1,date 查看和设置系统的日期和时间 2,-u,来查看UTC时间(格林威治时间) 3,date +%y--%m--%d 其中“–”可以自定义更改,这是更改时间的显示格式 4,date -s 设置时间,这个需要切到root用户才能有权限更改 5,date查看的是操作系统的时间,他是从硬件的时间中来的,可以直接用clock(hclcok)来查看 6,cal来打印日历 7,uptime,用来查看系统运行了多久,系统的用户,系统的负载 注:可以用 man uptime来查看对uptime的解释 二,输出,查看命令 1,echo:显示输入的内容,你输的啥就给你输出啥 2,cat:来显示文件的内容,它直接显示出所有的文件内容,很不人性化,麻烦 3,为了方便查看,避免cat的不便,可以用已下几个命令 4,more:用于翻页显示内容,但是不能向下翻页 5,less:相对于more 可以上下来回翻页 6,head:显示文件的头几行(默认10行) -n:来指定显示开头的n行 7,tail:显示末尾的几行(默认10行) -n:来指定显示末尾的n行 -f:追踪显示文件的更新,当我们用-f指令后,命令行就会卡在这里,等待文件更新再显示出新内容。一般用于查看日志,持续显示新加入的内容 三,查看硬件信息 1,lspci 用于查看PCI设备(如声卡网卡等) -v查看详细信息 这几条命令相当与Windows中点属性点设备管理器一样 2,lsusb 查看USB设备,如摄像头等 -v也是显示详细信息 3,lsmod 查看当前加载的所有模块(模块即windows中相当于驱动) 四,关机重启 格式:shutdown【关机/重启】时间 立即关机:shutdown -h now=poweroff 立即重启:shutdown -r now=reboot n分钟后关机:shutdown -h +n 定时关机:shutdown -h xx:xx 五,归档和压缩 1,zip用以压缩:zip xxx.zip file (把file这个源文件压缩成xxx.zip是目标文件) 2,unzip xxx.zip 是解压文件 3,gzip:也是一种压缩方式 4,tar:一个归档命令,就是把许多文件打包成一个文件 -cvf out.tar liunxfile :把Linuxfile这个文件夹中的文件打包归档成一个输出out.tar格式的文件,可以用来备份,但是他没有压缩哦 -cvf out.tar 把一个归档的tar文件释放到当前文件下 -cvzf: 在cvf命令下多了个z命令,就是归档并压缩一个文件。这里调用了一次gzip命令 格式:tar -cvzf xxx.tar.gz/要保存的目录下 其实用的最多的还是归档并压缩命令 解压:tar -zxvf FileName.tar.gz 压缩:tar -zcvf FileName.tar.gz DirName 六,查找命令(查找文件、文件夹) 在我们的操作系统中寻找文件 1,lacate 关键字 快速查找 它需要预先建立数据库,比如你新建立了一个文件,然后用locate去查找这个命令,是找不到的(数据库每天更新一次)。这个时候你可以用“updatedb”命令先更新数据库,然后在查找。 2,find 支持很多查找条件,所以叫高级查找 格式:find 查找位置 查找参数 find .-name*linuxcast* “.”表示在当前文件夹 *代表0个或多个字符 -name 表示查找文件、文件夹的名字 xxx表示关键字这句话表示在当前文件夹下所有包含“Linuxcast”关键字的文件 find / -name *.conf 在/根分区下查找所有.conf结尾的文件 find / -perm 777查找硬盘中所有权限是777的文件 find / -type d 查找目录类型文件。d 表示目录,也可以跟l ,表示查找所有的连接 find还可以使用查找的结果去执行一些命令。 find .-name "a *" -exec ls -l {} \; 这句话表示查找所有以“a开头的文件,然后传送给“ls -l”这个命令去执行,显示详细信息。 其中{} \;是固定格式。 -exec也是固定格式,执行的意思; find的后缀参数还有很多,可以直接在网络查找或者help文件
技术
# Linux
酷游
1月22日
0
14
0
易航博客