C Linux read write function extension

前言 - 贈送 readn / writen函數

  Linux 上默認的 read 和 write 函數會被信號軟中斷. 且 read 和 write 函數中第三個參數 count測試

#include <unistd.h>

extern ssize_t read(int fd, void * buf, size_t count);
extern ssize_t write(int fd, const void * buf, size_t count);

也會因內部緩衝機制, 不必定保證讀取或寫入到指定 count 大小數據.spa

這裏將 read 和 write 拓展成 readn 和 writencode

//
// readn - 力求讀取 n 個字節
// fd       : 文件描述符
// buf      : 緩衝區
// n        : 讀取長度
// return   : 返回讀取的長度, -1 標識錯誤, < n 標識關閉, 默認 n
//
ssize_t 
readn(int fd, void * buf, size_t n) {
    size_t div = n;
    char * ptr = buf;

    while (div > 0) {
        ssize_t ret = read(fd, ptr, div);
        if (ret < 0) {
            if (errno == EINTR)
                continue;
            return -1;
        }
        if (ret == 0) 
            break;
        ptr += ret;
        div -= ret;
    }

    return n - div;
}

//
// writen - 力求寫入 n 個字節
// fd       : 文件描述符
// buf      : 緩衝區
// n        : 讀取長度
// return   : 返回寫入長度, -1 標識錯誤, 默認 n
//
ssize_t 
writen(int fd, const void * buf, size_t n) {
    size_t div = n;
    const char * ptr = buf;

    while (div > 0) {
        ssize_t ret = write(fd, ptr, div);
        if (ret <= 0) {
            if (errno == EINTR)
                continue;
            return -1;
        }
        ptr += ret;
        div -= ret;
    }

    return n;
}

有了這些收穫, 不妨寫個小測試對象

#include <stdio.h>
#include <errno.h>
#include <assert.h>
#include <unistd.h>

//
// readn - 力求讀取 n 個字節
// fd       : 文件描述符
// buf      : 緩衝區
// n        : 讀取長度
// return   : 返回讀取的長度, -1 標識錯誤, < n 標識關閉, 默認 n
//
extern ssize_t readn(int fd, void * buf, size_t n);

//
// writen - 力求寫入 n 個字節
// fd       : 文件描述符
// buf      : 緩衝區
// n        : 讀取長度
// return   : 返回寫入長度, -1 標識錯誤, 默認 n
//
extern ssize_t writen(int fd, const void * buf, size_t n);

/*
                   _oo0oo_
                  o8888888o
                  88" . "88
                  (| -_- |)
                  0\  =  /0
                ___/`---'\___
              .' \\|     |// '.
             / \\|||  :  |||// \
            / _||||| -:- |||||- \
           |   | \\\  -  /// |   |
           | \_|  ''\---/''  |_/ |
           \  .-\__  '-'  ___/-. /
         ___'. .'  /--.--\  `. .'___
       ."" '<  `.___\_<|>_/___.' >' "".
      | | :  `- \`.;`\ _ /`;.`/ - ` : | |
      \  \ `_.   \_ __\ /__ _/   .-` /  /
   =====`-.____`.___ \_____/___.-`___.-'=====
                     `=---='
 */
int main(int argc, char * argv[]) {
    ssize_t ret = writen(STDOUT_FILENO, "12345\n1", 6);
    printf("ret = %ld\n", ret);

    char buf[4];
    ret = readn(STDIN_FILENO, buf, 3);
    buf[3] = '\0';
    printf("ret = %ld, buf = %s\n", ret, buf);

    return 0;
}

一憂一喜皆心火,一榮一枯皆眼塵,靜心看透炎涼事,千古不作夢裏人。blog

聰明人,一味向前看;智慧人,事事向後看;聰明人,是打敗別人的人;智慧人,是打敗本身的人。事件

修心當以淨心爲要,修道當以無我爲基。get

過去事,過去心,不可記得;如今事,如今心,隨緣便可;將來事,將來心,沒必要勞心。it

 

正文 -  緩衝讀io

  在瞭解 readn 套路基礎上, 你是否有所想過那緩衝讀寫的實現思路呢. 這裏不妨借用深刻理解計算機系統

書中的思路實現一番.

struct rio {
    int fd;             // 文件描述符
    char * ptr;         // 下一次讀取緩衝池 buf 起點
    ssize_t cnt;        // 緩衝池 buf 字符數量
    char buf[BUFSIZ];   // 緩衝池
};

// rio_init - rio 初始化
inline void rio_init(struct rio * r, int fd) {
    assert(r && fd >= 0);
    r->fd = fd;
    r->cnt = 0;
    r->ptr = r->buf;
}

//
// readn - 力求讀取 n 個字節
// r        : 緩衝讀取對象
// buf      : 緩衝區
// n        : 讀取長度
// return   : 返回讀取的長度, -1 標識錯誤, < n 標識關閉, 默認 n
//
extern ssize_t rio_readn(struct rio * r, void * buf, size_t n);

//
// rio_readline - 力求讀取一行數據
// r        : 緩衝讀取對象
// buf      : 緩衝區
// n        : 讀取長度
// return   : 返回讀取的長度, -1 標識錯誤, < n 標識關閉, 默認 n
//
extern ssize_t rio_readline(struct rio * r, void * buf, size_t n);

實現了緩衝讀固定字符和緩衝讀一行. 額外的緩衝寫也是類似的思路, 簡單點寫不了會進入寫緩衝區,

能夠當課外做業自行實現.

// rio_read - 帶緩衝版本的 read
static ssize_t rio_read(struct rio * r, void * buf, size_t n) {
    // 當緩衝區中沒有數據, 咱們從新填充緩衝區
    while (r->cnt <= 0) {
        r->cnt = read(r->fd, r->buf, sizeof r->buf);
        if (r->cnt < 0) {
            if (errno == EINTR)
                continue;
            return -1;
        }
        // EOF 直接返回
        if (r->cnt == 0)
            return 0;
        // 從新設置 buffer ptr 
        r->ptr = r->buf;
    }
    // 嘗試讀取數據並返回
    ssize_t cnt = r->cnt < n ? r->cnt : n;
    memcpy(buf, r->ptr, cnt);
    r->cnt -= cnt;
    r->ptr += cnt;
    return cnt;
}

//
// readn - 力求讀取 n 個字節
// r        : 緩衝讀取對象
// buf      : 緩衝區
// n        : 讀取長度
// return   : 返回讀取的長度, -1 標識錯誤, < n 標識關閉, 默認 n
//
ssize_t 
rio_readn(struct rio * r, void * buf, size_t n) {
    size_t div = n;
    char * ptr = buf;

    while (div > 0) {
        ssize_t ret = rio_read(r, ptr, div);
        if (ret < 0)
            return -1;
        if (ret == 0) 
            break;
        ptr += ret;
        div -= ret;
    }

    return n - div;    
}

//
// rio_readline - 力求讀取一行數據, 會吃掉最後一個 \n 字符
// r        : 緩衝讀取對象
// buf      : 緩衝區
// n        : 讀取長度
// return   : 返回讀取的長度, -1 標識錯誤, < n 標識關閉, 默認 n
//
ssize_t 
rio_readline(struct rio * r, void * buf, size_t n) {
    size_t i;
    char * ptr = buf, c;

    for (i = 1; i < n; ++i) {
        ssize_t ret = rio_read(r, &c, 1);
        if (ret < 0)
            return -1;
        if (c == '\n' || ret == 0)
            break;
        *ptr++ = c;
    }

    *ptr = '\0';
    return i - 1;
}

緩衝寫實戰包裝要複雜一點. 和業務綁定重(或者實現多策略的). 例如緩衝區滿了這時候的策略就由業務

決定, 是緩衝區擴容, 仍是等待下次寫事件觸發. 等等, 真實戰場的緩衝讀寫須要具體場景和機器打配合,

來構造滿意的讀寫策略.

 

後記 - 讓水倒流

相關文章
相關標籤/搜索