原文出處:http://blog.chinaunix.net/space.php?uid=20558494&do=blog&id=2803003
read函數是Linux下不帶緩存的文件I/O操作函數之一,所謂的不帶緩存是指一個函數只調用系統中的一個函數。另外還有open、write、lseek、close,它們雖然不是ANSI C的組成部分,但是POSIX的組成部分。
在對read的使用過程中,發現對其返回值的處理比較重要,這里做一下總結。
read函數原型:
ssize_t read(int fd,void *buf,size_t count)
函數返回值分為下面幾種情況:
1、如果讀取成功,則返回實際讀到的字節數。這里又有兩種情況:一是如果在讀完count要求字節之前已經到達文件的末尾,那么實際返回的字節數將 小於count值,但是仍然大於0;二是在讀完count要求字節之前,仍然沒有到達文件的末尾,這是實際返回的字節數等於要求的count值。
2、如果讀取時已經到達文件的末尾,則返回0。
3、如果出錯,則返回-1。
這樣也就是說分為>0 <0 =0三種情況進行討論。在有的時候,<0 =0可以合為一種情況進行處理。這要根據程序的需要進行處理。
實例分析:
#include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #define MAXSIZE 35 int main(void) { int i,j,fd,size,len; char *buf="Hello!I`m writing to this file!"; char buf_r[MAXSIZE]; len=strlen(buf); //open if((fd=open("/tmp/hello.c",O_CREAT | O_TRUNC | O_RDWR,0666))<0) { perror("open:"); exit(1); } else printf("Open file:hello.c %d\n",fd); //write if((size=write(fd,buf,len))<0){ perror("write:"); exit(1); } else printf("Write:%s\n\n\n",buf); //test-read printf("Now test starts...\n\n"); for(i=0;i<20;i++){ lseek(fd,0,SEEK_SET); for(j=0;j<MAXSIZE;j++) buf_r[j]=0; if((size=read(fd,buf_r,MAXSIZE-i))<0){ perror("read:"); exit(1); } else { buf_r[MAXSIZE-i]='\0'; printf("string-len=%d,count=%d,size=%d\n",len,MAXSIZE-i,size); printf("read from file:%s \n",buf_r); } } printf("\n\nNow test stops...\n"); //close if(close(fd)<0){ perror("close:"); exit(1); } else printf("close hello.c\n"); exit(0); }
-------------------------------
結果如下:
-------------------------------
[armlinux@lqm test-read]$ ./write
Open file:hello.c 3
Write:Hello!I`m writing to this file!
Now test starts...
string-len=31,count=35,size=31
read from file:Hello!I`m writing to this file!
string-len=31,count=34,size=31
read from file:Hello!I`m writing to this file!
string-len=31,count=33,size=31
read from file:Hello!I`m writing to this file!
string-len=31,count=32,size=31
read from file:Hello!I`m writing to this file!
string-len=31,count=31,size=31
read from file:Hello!I`m writing to this file!
string-len=31,count=30,size=30
read from file:Hello!I`m writing to this file
string-len=31,count=29,size=29
read from file:Hello!I`m writing to this fil
string-len=31,count=28,size=28
read from file:Hello!I`m writing to this fi
string-len=31,count=27,size=27
read from file:Hello!I`m writing to this f
string-len=31,count=26,size=26
read from file:Hello!I`m writing to this
string-len=31,count=25,size=25
read from file:Hello!I`m writing to this
string-len=31,count=24,size=24
read from file:Hello!I`m writing to thi
string-len=31,count=23,size=23
read from file:Hello!I`m writing to th
string-len=31,count=22,size=22
read from file:Hello!I`m writing to t
string-len=31,count=21,size=21
read from file:Hello!I`m writing to
string-len=31,count=20,size=20
read from file:Hello!I`m writing to
string-len=31,count=19,size=19
read from file:Hello!I`m writing t
string-len=31,count=18,size=18
read from file:Hello!I`m writing
string-len=31,count=17,size=17
read from file:Hello!I`m writing
string-len=31,count=16,size=16
read from file:Hello!I`m writin
Now test stops...
close hello.c
-------------------------------
現象:
測試部分中,string-len是測試文件內容的長度,count是要求讀取的字節數,size是實際讀取的字節數。可以觀察出,開始 count>string-len,所以雖然讀取成功,但是返回的實際字節數要小於要求的字節數。從count=string-len之后,實際返 回的字節數等於要求的字節數。
問題分析:
1、每次執行read函數之前,保證指定好起始位置,並且對buf初始化。
如果將
lseek(fd,0,SEEK_SET);
for(j=0;j<MAXSIZE;j++)
buf_r[j]=0;
移到for循環外,那么只能保證i=0時完成了上述工作。這是已經讀到文件的末尾,所以后面的size應該全部為零。因為沒有對buf_r初始化,所以讀取內容沒有變化。具體結果如下:
-------------------------------
[armlinux@lqm test-read]$ ./write
Open file:hello.c 3
Write:Hello!I`m writing to this file!
Now test starts...
string-len=31,count=35,size=31
read from file:Hello!I`m writing to this file!
string-len=31,count=34,size=0
read from file:Hello!I`m writing to this file!
string-len=31,count=33,size=0
read from file:Hello!I`m writing to this file!
...
...
Now test stops...
close hello.c
-------------------------------
2、對於一個數組,總是要自動分配一個\0作為結束,所以實際有效的buf長度就成為buf_r-1了。在本例中,倘若把MAXSIZE設為31,即等於string-len的長度。那么當你讀取31個字符時,\0就沒有了地方。如果把buf_r[MAXSIZE-i]='\0';去掉,那么在顯示后面就會出現亂碼現象。
-------------------------------
[armlinux@lqm test-read]$ ./write
Open file:hello.c 3
Write:Hello!I`m writing to this file!
Now test starts...
string-len=31,count=31,size=31
read from file:Hello!I`m writing to this file?B
B
參考:http://blog.sina.com.cn/s/blog_5328beed0100zbob.html
http://blog.csdn.net/hjhcs121/article/details/7460738
linux網絡編程 readn,writen函數:
/* include readn */ #include "unp.h" ssize_t /* Read "n" bytes from a descriptor. */ readn(int fd, void *vptr, size_t n) { size_t nleft; ssize_t nread; char *ptr; ptr = vptr; nleft = n; while (nleft > 0) { if ( (nread = read(fd, ptr, nleft)) < 0) { if (errno == EINTR) nread = 0; /* and call read() again */ else return(-1); } else if (nread == 0) break; /* EOF */ nleft -= nread; ptr += nread; } return(n - nleft); /* return >= 0 */ } /* end readn */ ssize_t Readn(int fd, void *ptr, size_t nbytes) { ssize_t n; if ( (n = readn(fd, ptr, nbytes)) < 0) err_sys("readn error"); return(n); }
writen:
/* include writen */ #include "unp.h" ssize_t /* Write "n" bytes to a descriptor. */ writen(int fd, const void *vptr, size_t n) { size_t nleft; ssize_t nwritten; const char *ptr; ptr = vptr; nleft = n; while (nleft > 0) { if ( (nwritten = write(fd, ptr, nleft)) <= 0) { if (nwritten < 0 && errno == EINTR) nwritten = 0; /* and call write() again */ else return(-1); /* error */ } nleft -= nwritten; ptr += nwritten; } return(n); } /* end writen */ void Writen(int fd, void *ptr, size_t nbytes) { if (writen(fd, ptr, nbytes) != nbytes) err_sys("writen error"); }
readline函數,從一個描述字讀文本行,一次讀一個字節(test/readline1.c]
/* include readline */ #include "unp.h" /* PAINFULLY SLOW VERSION -- example only */ ssize_t readline(int fd, void *vptr, size_t maxlen) { ssize_t n, rc; char c, *ptr; ptr = vptr; for (n = 1; n < maxlen; n++) { again: if ( (rc = read(fd, &c, 1)) == 1) { *ptr++ = c; if (c == '\n') break; /* newline is stored, like fgets() */ } else if (rc == 0) { *ptr = 0; return(n - 1); /* EOF, n - 1 bytes were read */ } else { if (errno == EINTR) goto again; return(-1); /* error, errno set by read() */ } } *ptr = 0; /* null terminate like fgets() */ return(n); } /* end readline */ ssize_t Readline(int fd, void *ptr, size_t maxlen) { ssize_t n; if ( (n = readline(fd, ptr, maxlen)) < 0) err_sys("readline error"); return(n); }
當面臨從套接口讀入文本行的需求時,改用標准庫IO函數庫(stdio)相當誘人。不過卻會引發許多問題。究其原因在於stdio緩沖區的狀態是不可見的。
基於文本行的網絡協議非常之多,譬如http,ftp等。因此正對文本行操作的這一需求一再被提出。不過我們的建議是依照緩沖區而不是文本行的要求來考慮編程,編寫從緩沖區中讀取數據的代碼,當期待一個文本行時,查看緩沖區中是否含有那一行。
/* include readline */ #include "unp.h" static int read_cnt; static char *read_ptr; static char read_buf[MAXLINE]; static ssize_t my_read(int fd, char *ptr) { if (read_cnt <= 0) { again: if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) { if (errno == EINTR) goto again; return(-1); } else if (read_cnt == 0) return(0); read_ptr = read_buf; } read_cnt--; *ptr = *read_ptr++; return(1); } ssize_t readline(int fd, void *vptr, size_t maxlen) { ssize_t n, rc; char c, *ptr; ptr = vptr; for (n = 1; n < maxlen; n++) { if ( (rc = my_read(fd, &c)) == 1) { *ptr++ = c; if (c == '\n') break; /* newline is stored, like fgets() */ } else if (rc == 0) { *ptr = 0; return(n - 1); /* EOF, n - 1 bytes were read */ } else return(-1); /* error, errno set by read() */ } *ptr = 0; /* null terminate like fgets() */ return(n); } ssize_t readlinebuf(void **vptrptr) { if (read_cnt) *vptrptr = read_ptr; return(read_cnt); } /* end readline */ ssize_t Readline(int fd, void *ptr, size_t maxlen) { ssize_t n; if ( (n = readline(fd, ptr, maxlen)) < 0) err_sys("readline error"); return(n); }
內部函數my_read最多每次讀MAXLINE個字符,然后每次返回一個字符。
函數readline本身的唯一變化是調用函數my_read而不是read。
readlinebuf這個新的函數能夠展露內部緩沖區狀態,以便於調用者查看當前文本行之后是否有新的數據已收到。
-----------------------------------
但是差別在read每次讀的數據是調用者要求的大小,比如調用要求讀取10個字節數據,read就會讀10個字節數據到數組中,而fread不一樣,為了加快讀的速度,fread每次都會讀比要求更多的數據,然后放到緩沖區中,這樣下次再讀數據只需要到緩沖區中去取就可以了。
frea d每次會讀取一個緩沖區大小的數據,32位下一般是4096個字節,相當於調用了read(fd,buf,4096)
比如需要讀取512個字節數據,分4次讀取,調用read就是:
for(i=0; i<4; ++i)
read(fd,buf,128)
一共有4次系統調用
而fread一次就讀取了4096字節放到緩沖區了,所以省事了
fgetc
讀一個字節,
fgetc
有可能從內核中預讀1024個字節到I/O緩沖區中,再返回第一個字節,這時該文件在內核中記錄的讀寫位置是1024,而在
FILE
結構體中記錄的讀寫位置是1。注意返回值類型是
ssize_t
,表示有符號的
size_t
,這樣既可以返回正的字節數、0(表示到達文件末尾)也可以返回負值-1(表示出錯)。
read
函數返回時,返回值說明了
buf
中前多少個字節是剛讀上來的。有些情況下,實際讀到的字節數(返回值)會小於請求讀的字節數
count
,例如:
-
讀常規文件時,在讀到
count
個字節之前已到達文件末尾。例如,距文件末尾還有30個字節而請求讀100個字節,則read
返回30,下次read
將返回0。 -
從終端設備讀,通常以行為單位,讀到換行符就返回了。(輸入we,顯示讀入是3個字節,可以試驗下)
-
從網絡讀,根據不同的傳輸層協議和內核緩存機制,返回值可能小於請求的字節數,后面socket編程部分會詳細講解。
write
函數向打開的設備或文件中寫數據。
#include <unistd.h> ssize_t write(int fd, const void *buf, size_t count); 返回值:成功返回寫入的字節數,出錯返回-1並設置errno
write
的返回值通常等於請求寫的字節數
count
,而向終端設備或網絡寫則不一定。
讀常規文件是不會阻塞的,不管讀多少字節,read
一定會在有限的時間內返回。從終端設備或網絡讀則不一定,如果從終端輸入的數據沒有換行符,調用read
讀終端設備就會阻塞,如果網絡上沒有接收到數據包,調用read
從網絡讀就會阻塞,至於會阻塞多長時間也是不確定的,如果一直沒有數據到達就一直阻塞在那里。同樣,寫常規文件是不會阻塞的,而向終端設備或網絡寫則不一定。
sleep
指定的睡眠時間到了)它才有可能繼續運行。與睡眠狀態相對的是運行(Running)
狀態,在Linux內核中,處於運行狀態的進程分為兩種情況:
-
正在被調度執行。CPU處於該進程的上下文環境中,程序計數器(
eip
)里保存着該進程的指令地址,通用寄存器里保存着該進程運算過程的中間結果,正在執行該進程的指令,正在讀寫該進程的地址空間。 -
就緒狀態。該進程不需要等待什么事件發生,隨時都可以執行,但CPU暫時還在執行另一個進程,所以該進程在一個就緒隊列中等待被內核調度。系統中可能同時有多個就緒的進程,那么該調度誰執行呢?內核的調度算法是基於優先級和時間片的,而且會根據每個進程的運行情況動態調整它的優先級和時間片,讓每個進程都能比較公平地得到機會執行,同時要兼顧用戶體驗,不能讓和用戶交互的進程響應太慢。
下面這個小程序從終端讀數據再寫回終端。
#include <unistd.h>
#include <stdlib.h>
int main(void)
{
char buf[10];
int n;
n = read(STDIN_FILENO, buf, 10);
if (n < 0) {
perror("read STDIN_FILENO");
exit(1);
}
write(STDOUT_FILENO, buf, n);
return 0;
}
$ ./a.out hello(回車) hello $ ./a.out hello world(回車) hello worl$ d bash: d: command not found
第一次執行a.out
的結果很正常,而第二次執行的過程有點特殊,現在分析一下:
-
Shell進程創建
a.out
進程,a.out
進程開始執行,而Shell進程睡眠等待a.out
進程退出。 -
a.out
調用read
時睡眠等待,直到終端設備輸入了換行符才從read
返回,read
只讀走10個字符,剩下的字符仍然保存在內核的終端設備輸入緩沖區中。 -
a.out
進程打印並退出,這時Shell進程恢復運行,Shell繼續從終端讀取用戶輸入的命令,於是讀走了終端設備輸入緩沖區中剩下的字符d和換行符,把它當成一條命令解釋執行,結果發現執行不了,沒有d這個命令。
open
一個設備時指定了O_NONBLOCK
標志,read
/write
就不會阻塞。以read
為例,如果設備暫時沒有數據可讀就返回-1,同時置errno
為EWOULDBLOCK
(或者EAGAIN
,這兩個宏定義的值相同),表示本來應該阻塞在這里(would block,虛擬語氣),事實上並沒有阻塞而是直接返回錯誤,調用者應該試着再讀一次(again)。這種行為方式稱為輪詢(Poll),調用者只是查詢一下,而不是阻塞在這里死等,這樣可以同時監視多個設備:
while(1) { 非阻塞read(設備1); if(設備1有數據到達) 處理數據; 非阻塞read(設備2); if(設備2有數據到達) 處理數據; ... }
read(設備1)
是阻塞的,那么只要設備1沒有數據到達就會一直阻塞在設備1的read
調用上,即使設備2有數據到達也不能處理,使用非阻塞I/O就可以避免設備2得不到及時處理。
while
循環中一直不停地查詢(這稱為Tight Loop
),而是每延遲等待一會兒來查詢一下,以免做太多無用功,在延遲等待的時候可以調度其它進程執行。
while(1) { 非阻塞read(設備1); if(設備1有數據到達) 處理數據; 非阻塞read(設備2); if(設備2有數據到達) 處理數據; ... sleep(n); }
select(2)
函數可以阻塞地同時監視多個設備,還可以設定阻塞等待的超時時間,從而圓滿地解決了這個問題。
O_NONBLOCK
標志。所以就像
例 28.2 “阻塞讀終端”一樣,讀標准輸入是阻塞的。我們可以重新打開一遍設備文件
/dev/tty
(表示當前終端),在打開時指定
O_NONBLOCK
標志。
read
讀終端設備就會阻塞
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#define MSG_TRY "try again\n"
int main(void)
{
char buf[10];
int fd, n;
fd = open("/dev/tty", O_RDONLY|O_NONBLOCK);
if(fd<0) {
perror("open /dev/tty");
exit(1);
}
tryagain:
n = read(fd, buf, 10);
if (n < 0) {
if (errno == EAGAIN) {
sleep(1);
write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));
goto tryagain;
}
perror("read /dev/tty");
exit(1);
}
write(STDOUT_FILENO, buf, n);
close(fd);
return 0;
}
直到按下回車把之前的輸入輸出(最多10個),然后停止。
以下是用非阻塞I/O實現等待超時的例子。既保證了超時退出的邏輯又保證了有數據到達時處理延遲較小。
例 28.4. 非阻塞讀終端和等待超時
read:既可以返回正的字節數、0(表示到達文件末尾)也可以返回負值-1(表示出錯)
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#define MSG_TRY "try again\n"
#define MSG_TIMEOUT "timeout\n"
int main(void)
{
char buf[10];
int fd, n, i;
fd = open("/dev/tty", O_RDONLY|O_NONBLOCK);
if(fd<0) {
perror("open /dev/tty");
exit(1);
}
for(i=0; i<5; i++) {
n = read(fd, buf, 10);
if(n>=0)
break;
if(errno!=EAGAIN) {
perror("read /dev/tty");
exit(1);
}
sleep(1);
write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));
}
if(i==5)
write(STDOUT_FILENO, MSG_TIMEOUT, strlen(MSG_TIMEOUT));
else
write(STDOUT_FILENO, buf, n);
close(fd);
return 0;
}