0723------Linux基礎----------文件 IO 之 read 和 write (readn 、writen、readline)


1. readn 和 writen

  1.1 基礎鞏固: read 和 write 函數的返回值

    1.1.1 read 函數原型為:ssize_t  read(int fd, void* buf, size_t count); (這里的 void *在標准 C 中表示通用指針即任意類型的指針都可以對它賦值,ssize_t 是有符號整數)它的返回值如下:

      a)成功返回讀取的字節數,這里可能等於 count 或者小於 count (當 count > 文件 size 的時候,返回實際讀到的字節數);

      b)剛開始讀就遇到EOF 則返回 0;

      c)讀取失敗返回 -1, 並設置相應的 errno。

 

    1.1.2 write 函數的原型為:ssize_t write(int fd, const void *buf, size_t count); 它的返回值如下:

      a)成功返回寫入的字節數,這里同上;

      b)寫入失敗返回 -1,並設置相應的 errno;

      c)當返回值為0 時,表示什么也沒有寫進去,這種情況在socket編程中出現可能是因為連接已關閉,在寫磁盤文件的時候一般不會出現。

 

  1.2 為什么要封裝一個readn 函數writen 函數,現有的read 函數和 write 含有有什么缺陷? 

    這個是因為在調用read(或 write)函數的時候,讀(寫)一次的返回值可能不是我們想到讀的字節數(即read函數中的 count 參數),這經常在讀取管道,或者網絡數去時出現。

 

  1.3 readn 函數 和 writen 函數

    1.3.1 readn保證在沒有遇到EOF的情況下,一定可以讀取n個字節它的返回值有三種:  

      a) >0,表示成功讀取的字節數,如果小於n,說明中間遇到了EOF;

      b)==0 表示一開始讀取就遇到EOF;

      c) -1 表示錯誤(這里的errno絕對不是EINTR)。

 

    1.3.2 writen函數保證一定寫滿n個字節,返回值:

      a)n 表示寫入成功n個字節

        b)-1 寫入失敗(這里也沒有EINTR錯誤)

 

  1.3.3 源碼如下,舉例說明,當在while循環中調用readn(fd, buf, 20)去讀取一個大小為55字節的文件時,會調用 4 次該readn函數

    a)第一次 返回 20;

    b)第二次 返回 20;

    c)第三次 返回 15(這里在readn函數內部會調用2次read函數,第一次只能讀15字節,因為到EOF了,此時還差5字節,再讀的的時候read直接返回0);

    d)第四次 返回 0 , 表示文件讀完,跳出 while ,不會在進入 while 內部。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#define ERR_EXIT(m) \
    do { \
        perror(m);\
        exit(EXIT_FAILURE);\
    }while(0)

/*
 * readn 和 wirten 函數
 */

ssize_t readn(int fd, void *buf, int n);
ssize_t writen(int fd, void *buf, int n);


/*
 * 這里 readn 函數成功返回讀取的字節數
 * 如果字節數小於n 一定是遇到了 EOF
 * 出錯返回 -1 這里的錯誤一定不包含 EINTR
 * 一開始就遇到EOF 返回0
 */

ssize_t readn(int fd, void *buf, int n){
    size_t nleft = n; //還需要讀取的字節數
    char *bufptr = buf; //指向read函數當前存放數據的位置
    ssize_t  nread;

    while(nleft > 0){
        if((nread = read(fd, bufptr, n)) < 0){
            if(errno == EINTR){ //遇到中斷
                continue;
            }
            else            // 其他錯誤
                return -1;
        }
        else if(nread == 0){ // 遇到EOF
            break;
        }

        nleft -= nread;
        bufptr += nread;
    }

    return (n - nleft);
}
/*
 * 這里的 writen 必須寫滿 n 個字節
 * 少於 n 就屬於錯誤
 * 返回 n  或者 -1
 */

ssize_t writen(int fd, void *buf, int n){
    size_t nleft = n;
    char *bufptr = buf;
    ssize_t nwrite;

    while(nleft > 0){
        if((nwrite = write(fd, bufptr, n)) <= 0){
            if(errno == EINTR)
                nwrite = 0;
            else
                return -1;
        }

        nleft -= nwrite;
        bufptr += nwrite;
    }

    return n; //  注意這里必須是 n 因為這里保證了 n 字節都被寫入
}

int main(int argc, const char *argv[])
{
    char buf[20] = {0};
    int fd = open("test.txt", O_RDONLY);
    if(fd == -1){
        ERR_EXIT("open");
    }

    int ret;
    while(printf("-----call readn----\n"), (ret = readn(fd, buf, 20)) > 0){
        writen(STDOUT_FILENO, buf, ret);
    }

    close(fd);
    return 0;
}

  

2. readline 函數 

  2.1 readline函數:ssize_t readline(int fd, void *usrbuf, size_t maxlen),它的返回值:

    a)錯誤返回-1,不包括EINTR;

    b)取過程中碰到\n;

    c)沒有碰到\n,而是讀滿maxlen-1個字節。

 

  2.2 源碼。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#define ERR_EXIT(m) \
    do { \
        perror(m);\
        exit(EXIT_FAILURE);\
    }while(0)

/*
 * 用 read 實現 readline 函數 每次讀取一個字節 判斷是不是 \n
 *
 */

ssize_t readline(int fd, void *usrbuf, size_t maxlen){
    char *bufptr = usrbuf;
    char c;
    size_t nleft = maxlen - 1; // 為\0 預留一個位置
    int nread;

    while(nleft > 0){
        if((nread = read(fd, &c, 1)) <  0){
            if(errno == EINTR){
                continue;
            }
            else
                return -1;
        }
        else if(nread == 0){ //EOF
            break;
        }

        *bufptr++ = c;
        nleft --;

        if(c == '\n'){ //遇到 \n 就結束
            break;
        }
    }

    *bufptr = '\0';
    return (maxlen - 1 - nleft);
}


int main(int argc, const char *argv[])
{
    int fd = open("test.txt", O_RDONLY);
    if(fd == -1){
        ERR_EXIT("open");
    }
    char buf[1024] = {0};
    int ret;
    while((ret = readline(fd, buf, 1000)) > 0){
        printf("ret = %d\nbuf = %s",ret, buf);
    }
    return 0;
}

  

  

  2.3 readn、writen、readline屬於同一個系列,稱為網絡編程三大函數。

 

3. RIO 一個用緩沖區實現的IO系統

  3.1 RIO中rio_read函數的編寫思想:

    a)用預讀取方案,提前把數據讀入Buffer;

    b)每當用戶取數據的時候,從Buffer里面讀取,而不是使用系統調用read函數,這樣減少了多次使用系統調用的開銷。

 

   3.2 rio_read函數的編寫原則:必須與系統的 read 函數保持語義一致,這意味着rio_read的返回值有三種情況:

    a) -1代表出錯,這里不包含EINTR;

    b) 0代表EOF,讀取結束;

    c) >0表示讀取的字節數。

 

  3.3 源碼實現見。

 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#define ERR_EXIT(m) \
    do { \
        perror(m);\
        exit(EXIT_FAILURE);\
    }while(0)
#define BUF_LEN 8192

/*
 * RIO 一個帶有緩沖區的IO系統
 * 每次讀取都從緩沖區中讀取 而不是系統調用
 *
 */

typedef struct{
    int fd_;     //要讀取的fd
    size_t nleft_;  // 緩沖區剩余可用的數據字節數
    char *bufptr_;  //指向剩余可用數據的起止地址
    char buffer_[BUF_LEN];
}Rio_t;

void rio_init(Rio_t *rp, int fd);
ssize_t rio_read(Rio_t *rp, void *usrbuf, size_t n);
ssize_t rio_readn(Rio_t *rp, void *usrbuf, size_t n);
ssize_t rio_readline(Rio_t *rp, void *usrbuf, size_t maxline);
ssize_t rio_writen(int fd, void *usrbuf, size_t n);



void rio_init(Rio_t *rp, int fd){ //初始化一個RIO
    rp->fd_ = fd;
    rp->nleft_ = 0;
    rp->bufptr_ = rp->buffer_;
}

ssize_t rio_read(Rio_t *rp, void *usrbuf, size_t n){ //將數據預讀取入 buffer中

    int nread;
    while(rp->nleft_ == 0){  // 當前緩沖區中沒有可用的數據
        if((nread = read(rp->fd_, rp->buffer_, sizeof(rp->buffer_))) < 0){
            if(errno == EINTR){
                continue;
            }
            else
                return -1;
        }
        else if(nread == 0){ // EOF
            return 0;
        }
        rp->nleft_ = nread;
        rp->bufptr_ = rp->buffer_;//重置緩沖區指針
    }
    // 此時緩沖區中已經有數據
    int cnt = n;

    if(rp->nleft_ < n) //緩沖區可提供的字節數小於用戶要求的字節數
        cnt = rp->nleft_;

    memcpy(usrbuf, rp->bufptr_, cnt);

    rp->nleft_ -= cnt; //讀取后 可用字節數減少
    rp->bufptr_ += cnt;

    return  cnt;
}

ssize_t rio_readn(Rio_t *rp, void *usrbuf, size_t n){
    char *bufptr = usrbuf;
    size_t  nleft = n;
    size_t  nread;

    while(nleft > 0){
        if((nread = rio_read(rp, bufptr, nleft)) == -1){ //neft
            return -1;
        }
        else if(nread == 0){
            break;
        }

        nleft -= nread;
        bufptr += nread;
    }
    return (n - nleft);

}

ssize_t rio_readline(Rio_t *rp, void *usrbuf, size_t maxline){
    size_t nleft = maxline - 1;
    char *bufptr = usrbuf;
    char c;
    int nread;

    while(nleft > 0){
        if((nread = rio_read(rp, &c, 1)) == -1)
            return -1;
        else if(nread == 0){
            break;
        }
        *bufptr++ = c;
        nleft --;

        if(c == '\n')
            break;
    }
    *bufptr = '\0';
    return (maxline - 1 - nleft);
}

ssize_t rio_writen(int fd, void *usrbuf, size_t n){
    char *bufptr = usrbuf;
    size_t nleft = n;
    size_t nwrite;

    while(nleft > 0){
        if((nwrite = write(fd, bufptr, n)) < 0){
            if(errno == EINTR)
                continue;
            else
                return -1;
         }

        nleft -= nwrite;
        bufptr += nwrite;
    }
    return n;
}

int main(int argc, const char *argv[])
{
    Rio_t  r;
    int fd = open("test.txt", O_RDONLY);
    if(fd == -1){
        ERR_EXIT("open");
    }

    rio_init(&r, fd);
    int ret;
    char buf[50] = {0};
    while((ret = rio_readline(&r, buf, 50)) >0){
        rio_writen(STDOUT_FILENO, buf, ret);
    }
    return 0;
}

 

  

 

 

 

 

 

 

 

 





免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM