Linux中让终端输入变为非阻塞的三种方法
介绍
在linux下每打开一个终端,系统自动的就打开了三个文件,它们的文件描述符分别为0,1,2,功能分别是“标准输入”、“标准输出”和“标准错误输出”,同时对应了三个文件流指针,分别是stdin,stdout和stderr。三个文件描述符定义了对应的宏,分别为STDIN_FILENO,STDOUT_FILENO和STDERR_FILENO下表为他们的对应关系:
| 标准输入 | 0 | STDIN_FILENO | stdin |
| 标准输出 | 1 | STDOUT_FILENO | stdout |
| 标准错误输出 | 2 | STDERR_FILENO | stderr |
在程序中通过read从STDIN_FILENO这个文件描述符中读取数据,实际上就是读取终端上键盘输入的数据,当然也可以通过fread从stdin这个文件流指针中读取数据。通过write往STDOUT_FILENO这个文件描述符中写数据,自然就是往终端输出数据了。标准错误输出也是往终端输出,它与标准输出的区别就是标准输出是有缓冲的,而标准错误输出是无缓冲的。
在程序中可以通过各种办法从终端读取键盘输入的数据,有时候会希望调用的函数不阻塞,这里有三种方法可以实现。
1、通过ioctl清除非阻塞标志
函数原型:
int ioctl(int d, int request, ...);
参数:
d:文件描述符
request:功能码。根据填写的功能码选择第三个参数。
返回:
成功返回0,失败返回-1。但是也有一部分功能返回的是一个数值。
ioctl函数是一个万能函数,这里不详细解释。
传入的第二个参数为FIONBIO表示“设置/清除非阻塞标志”,那么第三个参数要传入一个int类型的指针,指针指向的值为1表示设置非阻塞标志,那么对应的文件描述符为非阻塞,指针指向的值为0表示清除非阻塞标志,那么对应的文件描述符为阻塞,简单的说就是“0就阻塞,1就非阻塞”
因此调用如下代码即可实现非阻塞:
int attr = 1;
ioctl(STDIN_FILENO, FIONBIO, &attr); /* 清除非阻塞标志 */
通过该方法设置完成之后,在没有数据的情况下调用read函数将返回-1。
完整测试代码如下:
1 #include <stdio.h>
2 #include <netinet/in.h>
3 #include <unistd.h>
4 #include <fcntl.h>
5 #include <termios.h>
6 #include <string.h>
7 #include <sys/ioctl.h>
8
9 int main(int argc, const char *argv[])
10 {
11 char buf[128] = { 0 };
12 int len = 0;
13 int total = 0;
14 int attr = 1;
15
16 ioctl(STDIN_FILENO, FIONBIO, &attr); /* 清除非阻塞标志 */
17
18 while (1) {
19 len = read(STDIN_FILENO, &buf[total], sizeof(buf) - total);
20 //printf("len = %d\n", len); /* 如果不相信read变成了非阻塞可以去掉本行注释 */
21 if (len > 0) {
22 total += len;
23 if (buf[total - 1] == '\n') {
24 printf("total = %d\n", total);
25 printf("buf = %s\n", buf);
26 total = 0;
27 memset(buf, 0, sizeof(buf));
28 }
29 }
30 }
31
32 return 0;
33 }
2、通过fcntl设置非阻塞标志
函数原型:
int fcntl(int fd, int cmd, ... /* arg */ );
参数:
fd:文件描述符。
cmd:功能码。根据填写的功能码选择第三个参数。
返回:
根据不同的功能决定。
fcntl函数与ioctl函数看上去有很多相似性,实际上这两个函数确实有很多功能是重叠的。
如果要通过fcntl让文件不阻塞,那么需要知道两个功能码:
F_GETFL:取得fd的文件状态标志,如同下面的描述一样(arg被忽略)
F_SETFL:设置给arg描述符状态标志,可以更改的几个标志是:O_APPEND, O_NONBLOCK,O_SYNC和O_ASYNC。
很明显非阻塞标志是O_NONBLOCK,调用下列代码就可以实现非阻塞:
int attr = 0; attr = fcntl(STDIN_FILENO, F_GETFL);
attr |= O_NONBLOCK;
fcntl(STDIN_FILENO, F_SETFL, attr);
通过该方法设置完成之后,在没有数据的情况下调用read函数将返回-1。
完整测试代码如下:
1 #include <stdio.h>
2 #include <netinet/in.h>
3 #include <unistd.h>
4 #include <fcntl.h>
5 #include <termios.h>
6 #include <string.h>
7 #include <sys/ioctl.h>
8
9 int main(int argc, const char *argv[])
10 {
11 char buf[128] = { 0 };
12 int len = 0;
13 int total = 0;
14 int attr = 0;
15
16 /* 设置 O_NONBLOCK属性 */
17 attr = fcntl(STDIN_FILENO, F_GETFL);
18 attr |= O_NONBLOCK;
19 fcntl(STDIN_FILENO, F_SETFL, attr);
20
21 while (1) {
22 len = read(STDIN_FILENO, &buf[total], sizeof(buf) - total);
23 //printf("len = %d\n", len); /* 如果不相信read变成了非阻塞可以去掉本行注释 */
24 if (len > 0) {
25 total += len;
26 if (buf[total - 1] == '\n') {
27 printf("total = %d\n", total);
28 printf("buf = %s\n", buf);
29 total = 0;
30 memset(buf, 0, sizeof(buf));
31 }
32 }
33 }
34
35 return 0;
36 }
3、通过设置termios实现
termios是一个结构体,详细介绍可以参见这篇博客:https://www.cnblogs.com/dartagnan/archive/2013/04/25/3042417.html
需要用到的函数有tcgetattr和tcsetattr,这两个函数的用法在这篇博客中有介绍:https://www.cnblogs.com/Suzkfly/p/11055532.html
操作流程就是先用tcgetattr获取文件属性,修改属性之后再用tcsetattr设置进去,关键代码如下:
tcgetattr(STDIN_FILENO, &attr);
attr.c_cc[VTIME] = 0;
attr.c_cc[VMIN] = 0;
attr.c_lflag &= ~ICANON; /* 禁用规范输入模式 */
tcsetattr(STDIN_FILENO, TCSANOW, &attr);
经过测试,attr.c_lflag &= ~ICANON;这一句一定要写,原因我也不知道,但是写了这句话会带来某些问题,在最后进行分析。
将attr.c_cc[VTIME]和attr.c_cc[VMIN]的值都设置为0。attr.c_cc[VTIME]和attr.c_cc[VMIN]共同决定了read函数的返回时机。表示读阻塞时间,单位是1/10秒。
如果经过了c_cc[VTIME]这么长时间,缓冲区内有数据,但是还没达到c_cc[VMIN]个数据,read也会返回。而如果当缓冲区内有了c_cc[VMIN]个数据时,无论等待时间是否到了c_cc[VTIME],read都会返回,但返回值可能比c_cc[VMIN]还大,根据实际数据量而定。如果将c_cc[VMIN]的值设置为0,那么当经过c_cc[VTIME]时间后read也会返回,返回值为0。如果将c_cc[VTIME]和c_cc[VMIN]都设置为0,那么程序运行的效果与设置O_NONBLOCK类似,不同的是如果设置了O_NONBLOCK,那么在没有数据时read返回-1,而如果没有设置O_NONBLOCK,那么在没有数据时read返回的是0。
完整测试代码如下:
1 #include <stdio.h>
2 #include <netinet/in.h>
3 #include <unistd.h>
4 #include <fcntl.h>
5 #include <termios.h>
6 #include <string.h>
7 #include <sys/ioctl.h>
8
9 int main(int argc, const char *argv[])
10 {
11 int port = 0;
12 char buf[128] = { 0 };
13 int len = 0;
14 int total = 0;
15 struct termios attr;
16
17 tcgetattr(STDIN_FILENO, &attr);
18 attr.c_cc[VTIME] = 0;
19 attr.c_cc[VMIN] = 0;
20 attr.c_lflag &= ~ICANON; /* 禁用规范输入模式 */
21 tcsetattr(STDIN_FILENO, TCSANOW, &attr);
22
23 while (1) {
24 len = read(STDIN_FILENO, &buf[total], sizeof(buf) - total);
25 //printf("len = %d\n", len);
26 if (len > 0) {
27 total += len;
28 if (buf[total - 1] == '\n') {
29 printf("total = %d\n", total);
30 printf("buf = %s\n", buf);
31 total = 0;
32 memset(buf, 0, sizeof(buf));
33 }
34 }
35 }
36
37 return 0;
38 }
4、分析总结
特别要注意的一点是,在测试的时候,如果你不知道你的操作会产生什么结果,在下次测试之前一定要重新开一个终端,因为如果终端没有结束的话,就表示文件没有关闭,即使程序退出了,之前的设置也是会保存的。
通过这三种方法都实现了设置属性为非阻塞,其实第一和第二种方法是一样的,第三种方法由于禁用了规范输入模式,会带来某些问题。所谓规范输入模式就是对某些特殊的键会产生特殊的效果,比如退格键应该回删一个字符,但通过第三种方法会将退格键也当成一个字符读取进去了。比如,在终端输入“abc”再输入一个退格,然后按回车,通过第一和第二种方法会接收到“ab\n”三个字符,而通过第三种方法则会收到“abc”+“退格”+“\n”5个字符。
第一和第二种方法虽然read不会阻塞,但是并不是说只要通过键盘输入了数据,它就能读到数据,即使通过键盘输入了一些数据,read函数返回的还是-1,直到按下回车键为止。
Linux中让终端输入变为非阻塞的三种方法的更多相关文章
- 【转】Linux 中清空或删除大文件内容的五种方法(truncate 命令清空文件)
原文: http://www.jb51.net/article/100462.htm truncate -s 0 access.log -------------------------------- ...
- Linux系统下修改环境变量PATH路径的三种方法
这里介绍Linux的知识,比如把/etc/apache/bin目录添加到PATH中有三种方法,看完之后你将学会Linux系统下如何修改环境变量PATH路径,需要的朋友可以参考下 电脑中必不可少的就是操 ...
- [译]在Linux中清空或删除大文件内容的5种方法
原文来源: https://www.tecmint.com/empty-delete-file-content-linux/ 有时,在处理Linux终端中的文件时,您可能希望清除文件的内容,而无需使用 ...
- 针对Hbuilderx内置终端无法输入问题,总结了三种方法供大家参考
下图,是内置终端无法输入的现象(本人使用的第三种方案,解决了该问题) 第一种解决方案,也是网上推荐最多的方案: 打开Hbuilder安装路径下插件文件夹中的main.js文件:HBuilderX\pl ...
- vuex中怎么把‘库’中的状态对象赋值给内部对象(三种方法)
一.通过computed的计算属性直接赋值 import store from '@/store/store' export default{ name: 'count', data(){ retur ...
- Struts2 在Action中获取request、session、servletContext的三种方法
首页message.jsp: <body> ${requestScope.req }<br/> ${applicationScope.app }<br/> ${se ...
- 关于SQLServer数据库中字段值为NULL,取出来该字段放在DataTable中,判断datatable中该字段值是否为NULL的三种方法
1. DataTable dt; //假设字段为name, dt已经保存了数据dt.rows[0]["name"] == ...
- JavaScript中unicode编码与String互转(三种方法)
1.引言 JS本身就支持unicode转string功能,一共有三种方式和String单个字符转unicode编码. 2.方法 //unicode转String 1. eval("'&quo ...
- Win7系统与它的Virtualbox中安装的Ubuntu14.04共享信息的几种方法
虚拟机是每一个程序猿必备的工具.本文依据最新版VirtualBox用户手冊的提示,通过自己的亲自实践,给出了Win7系统与执行在当中的VirtualBox 5.0.2中的Ubuntu 14.04共享信 ...
随机推荐
- 我用 go-zero 一周实现了一个中台系统,已开源!
作者:Jack 最近发现golang社区里出了一个新星的微服务框架,来自好未来,光看这个名字,就很有奔头,之前,也只是玩过go-micro,其实真正的还没有在项目中运用过,只是觉得 微服务,grpc ...
- 工作3年,看啥资料能月薪30K?
作者:小傅哥 博客:https://bugstack.cn Github:https://github.com/fuzhengwei/CodeGuide/wiki 沉淀.分享.成长,让自己和他人都能有 ...
- (一)NumPy基础:数组和矢量计算
一.创建ndarray 1.各种创建函数的使用 import numpy as np #创建ndarray #1.array方法 data1 = [[6, 7.5, 8, 0, 1], [2, 8, ...
- matplotlib学习日记(八)----完善统计图
(一)再说legend() import matplotlib.pyplot as plt import numpy as np x = np.arange(0, 2.1, 0.1) y = np.p ...
- Redis底层数据结构实现
REDIS 较宽泛的支持5种数据结构 分别为 字符串 列表 集合 散列 有序集合 关于这几种数据结构的使用 相信网上有很多资料,查看官网API 也很详细了 读者可以自己随意翻阅 很方便 . 接下 ...
- 数据库1 --- > 数据库概念、安装、卸载
数据库概念 为什么学习数据库?1.web中的数据量非常大:2. 数据不方便存储和管理 什么是数据库: 用于存储和管理数据的仓库 数据库的特点: 数据可以实现持久化存储,其实数据库就是一个文件系统. ...
- android中VideoView播放sd卡上面的视频
(1)videoView组件只支持MP4和3gp格式的视屏播放,如果想播放其它视屏格式的文件,还得开发能够播放的视屏播放器 (2)videoView组件功能比较单一,如果想开发功能丰富的播放器,还得重 ...
- linux下 shell时间处理
一.hour #获取当前时间年月日时分秒current_create_time=`date +"%Y-%m-%d %H:%M:%S"` echo $current_create_t ...
- SpringCloud --服务调用Feign
介绍 服务间通信简介 一个系统可以由不同的微服务构成,比如一个电商系统可以由订单服务.商品服务.用户服务等共同组成. 这些服务相互独立,但又相互依赖.由于它们相互依赖,所以需要通过通信的方式来进行相互 ...
- Linux常用命令 | grep
作者简介 李先生(Lemon),高级运维工程师(自称),SRE专家(目标),梦想在35岁买一辆保时捷.喜欢钻研底层技术,认为底层基础才是王道.一切新技术都离不开操作系统(CPU.内存.磁盘).网络 ...