OpenSSL之抽像IO用法

OpenSSL抽象IO(I/O abstraction,即BIO)是OpenSSL对于IO类型的抽象封装,包括:内存、文件、日志、标准输入输出、socket(TCP/UDP)、加/解密、摘要和SSL通道等。OpenSSL BIO通过回调函数为用户隐藏了底层实现细节,所有类型的BIO的调用大体上是类似的。BIO中的数据能从一个BIO传送到另外一个BIO或者是应用程序。

本文假设你已经安装好了OpenSSL,并且持有一份1.1.1的源码。
BIO的头文件为bio.h,源文件实现在crypto/bio目录中。

主要结构:

struct bio_st {
    const BIO_METHOD *method;
    /* bio, mode, argp, argi, argl, ret */
    BIO_callback_fn callback;
    BIO_callback_fn_ex callback_ex;
    char *cb_arg;               /* first argument for the callback */
    int init;
    int shutdown;
    int flags;                  /* extra storage */
    int retry_reason;
    int num;
    void *ptr;
    struct bio_st *next_bio;    /* used by filter BIOs */
    struct bio_st *prev_bio;    /* used by filter BIOs */
    CRYPTO_REF_COUNT references;
    uint64_t num_read;
    uint64_t num_write;
    CRYPTO_EX_DATA ex_data;
    CRYPTO_RWLOCK *lock;
};
typedef struct bio_st BIO;

这个结构定义了抽象IO的数据结构。主要字段含义:
method —— 抽象IO的函数方法集合。
init —— 初使化标志,对于文件IO来说,当使用BIO_set_fp()关联文件指针时,则置该标志为1。
shutdown —— 关闭标志,当该值不为0时,需要释放资源。
flags —— 用来存放控制标志集合,如BIO_CLOSE。
num —— 用来存放关联的整型句柄。
ptr —— 用来存放关联的指针型句柄。
next_bio —— 抽象IO是用链表来管理的,这里表示链接下一个节点。
prev_bio —— 链接的上一个节点。

struct bio_method_st {
    int type;
    char *name;
    int (*bwrite) (BIO *, const char *, size_t, size_t *);
    int (*bwrite_old) (BIO *, const char *, int);
    int (*bread) (BIO *, char *, size_t, size_t *);
    int (*bread_old) (BIO *, char *, int);
    int (*bputs) (BIO *, const char *);
    int (*bgets) (BIO *, char *, int);
    long (*ctrl) (BIO *, int, long, void *);
    int (*create) (BIO *);
    int (*destroy) (BIO *);
    long (*callback_ctrl) (BIO *, int, BIO_info_cb *);
};
typedef struct bio_method_st BIO_METHOD;

这个结构定义了具体的抽象IO的方法集合。主要字段含义:
type —— 抽象IO的类型。
name —— 抽象IO的名称。
bwrite —— 抽象IO的写操作函数实现。
bread —— 抽象IO的读操作函数实现。
ctrl —— 抽象IO的控制方法,关于控制方法,在头文件中,BIO会引入非常多的宏,最终都会传入到这里来,交给具体的抽象类型来处理。

对每种抽象IO类型来说,都有其对应的BIO结构及BIO_METHOD方法集合实现。

在1.1.1中,大多数的数据结构已经不再向使用者开放,从封装的角度来看,这是更合理的。如果你在头文件中找不到结构定义,不妨去源码中搜一搜。

主要函数:

BIO *BIO_new(const BIO_METHOD *type);
根据BIO方法集合,创建BIO对象,返回其指针。
其内部实现为:

{
    BIO *bio = OPENSSL_zalloc(sizeof(*bio));

    bio->method = method;
    bio->shutdown = 1;
    bio->references = 1;

    if (method->create != NULL && !method->create(bio)) {
        BIOerr(BIO_F_BIO_NEW, ERR_R_INIT_FAIL);
        CRYPTO_free_ex_data(CRYPTO_EX_INDEX_BIO, bio, &bio->ex_data);
        CRYPTO_THREAD_lock_free(bio->lock);
        goto err;
    }

    return bio;

err:
    OPENSSL_free(bio);
    return NULL;
}

可以看到先初使化BIO数据结构,然后关联抽象方法,并进行具体的create调用。
注意:此方法并不确保抽象IO完成了初使化,比如对文件抽像IO来说,还需要绑定FILE指针。

int BIO_free(BIO *a);
释放BIO对象。
成功返回1,失败返回0。
其实现实现为:

{
    if (a == NULL)
        return 0;

    if ((a->method != NULL) && (a->method->destroy != NULL))
        a->method->destroy(a);

    OPENSSL_free(a);
    return 1;
}

可以看到关闭抽象IO、释放资源。

const BIO_METHOD *BIO_s_file(void);
这个函数返回一个默认的FILE抽象方法集合。
其内部实现为:
{
return &methods_filep;
}
可以看到直接返回一个预定义的BIO_METHOD对象指针。
关于methods_filep的定义:

static const BIO_METHOD methods_filep = {
    BIO_TYPE_FILE,
    "FILE pointer",
    bwrite_conv,
    file_write,
    bread_conv,
    file_read,
    file_puts,
    file_gets,
    file_ctrl,
    file_new,
    file_free,
    NULL,
};

类似的,OpenSSL支持的抽象IO方法集合还有:

const BIO_METHOD *BIO_s_mem(void);
const BIO_METHOD *BIO_s_socket(void);
const BIO_METHOD *BIO_s_connect(void);
const BIO_METHOD *BIO_s_accept(void);
const BIO_METHOD *BIO_s_fd(void);
const BIO_METHOD *BIO_s_log(void);
const BIO_METHOD *BIO_s_bio(void);
const BIO_METHOD *BIO_s_null(void);
const BIO_METHOD *BIO_s_datagram(void);
const BIO_METHOD *BIO_f_null(void);
const BIO_METHOD *BIO_f_buffer(void);
const BIO_METHOD *BIO_f_linebuffer(void);
const BIO_METHOD *BIO_f_zlib(void);
const BIO_METHOD *BIO_f_md(void);
const BIO_METHOD *BIO_f_base64(void);
const BIO_METHOD *BIO_f_cipher(void);
const BIO_METHOD *BIO_f_reliable(void);
const BIO_METHOD *BIO_f_ssl(void);

BIO *BIO_new_file(const char *filename, const char *mode);
这个函数创建BIO,并且关联抽象方法,并完成文件指针的绑定。
其内部实现为:

{
    BIO  *ret;
    FILE *file = openssl_fopen(filename, mode);
    int fp_flags = BIO_CLOSE;

    if ((ret = BIO_new(BIO_s_file())) == NULL) {
        fclose(file);
        return NULL;
    }   

    BIO_clear_flags(ret, BIO_FLAGS_UPLINK);
    BIO_set_fp(ret, file, fp_flags);
    return ret;
}

BIO *BIO_new_fp(FILE *stream, int close_flag);
这个函数创建BIO,并且关联抽象方法,并完成文件指针的绑定。
其内部实现为:

{
    BIO *ret;

    if ((ret = BIO_new(BIO_s_file())) == NULL)
        return NULL;

    /* redundant flag, left for documentation purposes */
    BIO_set_flags(ret, BIO_FLAGS_UPLINK);
    BIO_set_fp(ret, stream, close_flag);
    return ret;
}

类似的,OpenSSL提供的直接根据相关参数创建BIO的函数还有:

BIO *BIO_new_mem_buf(const void *buf, int len);
BIO *BIO_new_dgram(int fd, int close_flag);
BIO *BIO_new_socket(int sock, int close_flag);
BIO *BIO_new_connect(const char *host_port);
BIO *BIO_new_accept(const char *host_port);
BIO *BIO_new_fd(int fd, int close_flag);
int BIO_new_bio_pair(BIO **bio1, size_t writebuf1,
BIO *BIO_new_ssl(SSL_CTX *ctx, int client);
BIO *BIO_new_ssl_connect(SSL_CTX *ctx);
BIO *BIO_new_buffer_ssl_connect(SSL_CTX *ctx);

const char *BIO_method_name(const BIO *b);
这个函数返回BIO的名称。

int BIO_method_type(const BIO *b);
这个函数返回BIO的类型。
关于类型取值:

/* There are the classes of BIOs */
# define BIO_TYPE_DESCRIPTOR     0x0100 /* socket, fd, connect or accept */
# define BIO_TYPE_FILTER         0x0200
# define BIO_TYPE_SOURCE_SINK    0x0400

/* These are the 'types' of BIOs */
# define BIO_TYPE_NONE             0
# define BIO_TYPE_MEM            ( 1|BIO_TYPE_SOURCE_SINK)
# define BIO_TYPE_FILE           ( 2|BIO_TYPE_SOURCE_SINK)

# define BIO_TYPE_FD             ( 4|BIO_TYPE_SOURCE_SINK|BIO_TYPE_DESCRIPTOR)
# define BIO_TYPE_SOCKET         ( 5|BIO_TYPE_SOURCE_SINK|BIO_TYPE_DESCRIPTOR)
# define BIO_TYPE_NULL           ( 6|BIO_TYPE_SOURCE_SINK)
# define BIO_TYPE_SSL            ( 7|BIO_TYPE_FILTER)
# define BIO_TYPE_MD             ( 8|BIO_TYPE_FILTER)
# define BIO_TYPE_BUFFER         ( 9|BIO_TYPE_FILTER)
# define BIO_TYPE_CIPHER         (10|BIO_TYPE_FILTER)
# define BIO_TYPE_BASE64         (11|BIO_TYPE_FILTER)
# define BIO_TYPE_CONNECT        (12|BIO_TYPE_SOURCE_SINK|BIO_TYPE_DESCRIPTOR)
# define BIO_TYPE_ACCEPT         (13|BIO_TYPE_SOURCE_SINK|BIO_TYPE_DESCRIPTOR)

# define BIO_TYPE_NBIO_TEST      (16|BIO_TYPE_FILTER)/* server proxy BIO */
# define BIO_TYPE_NULL_FILTER    (17|BIO_TYPE_FILTER)
# define BIO_TYPE_BIO            (19|BIO_TYPE_SOURCE_SINK)/* half a BIO pair */
# define BIO_TYPE_LINEBUFFER     (20|BIO_TYPE_FILTER)
# define BIO_TYPE_DGRAM          (21|BIO_TYPE_SOURCE_SINK|BIO_TYPE_DESCRIPTOR)
# define BIO_TYPE_ASN1           (22|BIO_TYPE_FILTER)
# define BIO_TYPE_COMP           (23|BIO_TYPE_FILTER)
# ifndef OPENSSL_NO_SCTP
#  define BIO_TYPE_DGRAM_SCTP    (24|BIO_TYPE_SOURCE_SINK|BIO_TYPE_DESCRIPTOR)
# endif

#define BIO_TYPE_START           128

int BIO_read(BIO *b, void *data, int dlen);
从BIO读取一块数据,返回读取的字节数。
通常成功返回>0,失败返回<=0。

int BIO_write(BIO *b, const void *data, int dlen);
向BIO写入一块数据,返回写入的字节数。
通常成功返回>0,失败返回<=0。

int BIO_gets(BIO *bp, char *buf, int size);
从BIO读取一行数据,返回读取的字节数。
通常成功返回>0,失败返回<=0。

int BIO_puts(BIO *bp, const char *buf);
向BIO写入C字符串数据,返回写入的字节数。
通常成功返回>0,失败返回<=0。

long BIO_ctrl(BIO *bp, int cmd, long larg, void *parg);
向BIO写入一条控制指令。
成功返回1,失败返回0。

void *BIO_ptr_ctrl(BIO *bp, int cmd, long larg);
BIO_ctrl()的指针版。
long BIO_int_ctrl(BIO *bp, int cmd, long larg, int iarg);
BIO_ctrl()的整数版。

BIO *BIO_push(BIO *b, BIO *append);
连接两个BIO,将append链接到b尾部。

BIO *BIO_pop(BIO *b);
将b从链中弹出,解除b与其它BIO的链表关系。

BIO *BIO_next(BIO *b);
获取链接到b的下一个BIO。

BIO *BIO_find_type(BIO *b, int bio_type);
在b链接中搜索指定类型的BIO。

void BIO_free_all(BIO *a);
释放整个BIO链表。

助记宏函数:

由于BIO支持的种类非常多,而且每类BIO还分别实现了自己的ctrl函数,这个ctrl函数的参数是不确定的,BIO规范只是约定了接口形式。因此,BIO_ctrl的调用非常复杂,OpenSSL为了助记,将一些常用的调用整理为宏,这里只摘录一部分,如下:

# define BIO_NOCLOSE          0x00
# define BIO_CLOSE               0x01

# define BIO_get_fp(b,fpp)       BIO_ctrl(b,BIO_C_GET_FILE_PTR,0,(char *)(fpp))
# define BIO_set_fp(b,fp,c)      BIO_ctrl(b,BIO_C_SET_FILE_PTR,c,(char *)(fp))

# define BIO_get_fd(b,c)         BIO_ctrl(b,BIO_C_GET_FD,0,(char *)(c))
# define BIO_set_fd(b,fd,c)      BIO_int_ctrl(b,BIO_C_SET_FD,c,fd)

# define BIO_set_ssl(b,ssl,c)    BIO_ctrl(b,BIO_C_SET_SSL,c,(char *)(ssl))
# define BIO_set_md(b,md)     BIO_ctrl(b,BIO_C_SET_MD,1,(char *)(md))
# define BIO_set_mem_buf(b,bm,c) BIO_ctrl(b,BIO_C_SET_BUF_MEM,c,(char *)(bm))

使用举例1:

下面这个例子演示了内存抽象IO的创建、读写和释放操作。

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>

#include <openssl/bio.h>

namespace dakuang {}

int main(int argc, char* argv[])
{
    BIO* bio = BIO_new(BIO_s_mem());

    int nBytesWrite = BIO_write(bio, "hello", 5);
    printf("write size:[%d] \n", nBytesWrite);

    int nBytesPending = BIO_ctrl_pending(bio);
    printf("pending size:[%d] \n", nBytesPending);

    char* pBuf = (char *)malloc(nBytesPending + 1);
    int nBytesRead = BIO_read(bio, pBuf, nBytesPending);
    printf("read size:[%d] \n", nBytesRead);
    if (nBytesRead > 0)
    {
        pBuf[nBytesRead] = '\0';
        printf("read:[%s] \n", pBuf);
    }
    free(pBuf);

    BIO_free(bio);

    return 0;
}

输出:
write size:[5]
pending size:[5]
read size:[5]
read:[hello]

使用举例2:

下面这个例子演示了文件抽象IO的创建、读写和释放操作。

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>

#include <openssl/bio.h>

namespace dakuang {}

int main(int argc, char* argv[])
{
    BIO* bio = BIO_new_file("testbio.txt", "w");
    int nBytesWrite = BIO_write(bio, "hello", 5);
    printf("write size:[%d] \n", nBytesWrite);
    BIO_free(bio);

    bio = BIO_new_file("testbio.txt", "r");
    while (true)
    {
        char sBuf[128] = {0};
        int nBytesRead = BIO_read(bio, sBuf, sizeof(sBuf)-1);
        printf("read size:[%d] \n", nBytesRead);
        if (nBytesRead <= 0)
            break;

        printf("read body:[%s] \n", sBuf);
    }
    BIO_free(bio);

    return 0;
}

输出:
write size:[5]
read size:[5]
read body:[hello]
read size:[0]

使用举例3:

下面这个例子演示了套接口抽象IO的创建、读取和释放操作。

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#include <openssl/bio.h>

namespace dakuang {}

int main(int argc, char* argv[])
{
    int sock = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in sinto;
    sinto.sin_family = AF_INET;
    sinto.sin_addr.s_addr = inet_addr("127.0.0.1");
    sinto.sin_port = htons(9999);

    int ret = connect(sock, (struct sockaddr*)&sinto, sizeof(sinto));
    if (ret < 0)
    {
        printf("connect failed! \n");
        return -1;
    }

    printf("connect ok! \n");

    BIO* pBio = BIO_new_socket(sock, BIO_NOCLOSE);

    while (true)
    {
        char sBuf[128] = {0};        
        int nBytesRead = BIO_read(pBio, sBuf, sizeof(sBuf)-1);
        printf("read size:%d \n", nBytesRead);
        if (nBytesRead <= 0)
            break;

        printf("read body:[%s] \n", sBuf);        
    }

    BIO_free(pBio);

    close(sock);

    return 0;
}

启动服务器:
nc -lv4 9999
运行这个例子,并且在nc端输入hello,输出:
connect ok!
read size:[6]
read body:[hello
]
read size:[0]

使用举例4:

下面这个例子演示了使用抽象IO进行MD5摘要计算的过程。

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>

#include <openssl/crypto.h>
#include <openssl/bio.h>
#include <openssl/evp.h>

namespace dakuang {}

int main(int argc, char* argv[])
{
    BIO* bioMD = BIO_new(BIO_f_md());

    const EVP_MD* md = EVP_md5();
    BIO_set_md(bioMD, md);

    BIO* bioNull = BIO_new(BIO_s_null());
    BIO_push(bioMD, bioNull);

    int nBytesWrite = BIO_write(bioMD, "hello", 5);
    printf("write:[%d] \n", nBytesWrite);

    char sBuf[128] = {0};
    int nBytesRead = BIO_gets(bioMD, sBuf, sizeof(sBuf) - 1);
    printf("read %d : [%s] \n", nBytesRead, OPENSSL_buf2hexstr((const unsigned char*)sBuf, nBytesRead));

    BIO_free(bioNull);
    BIO_free(bioMD);

    return 0;
}

输出:
write:[5]
read 16 : [5D:41:40:2A:BC:4B:2A:76:B9:71:9D:91:10:17:C5:92]

关于MD BIO,从源码实现来看,其尾部必须链接一个过滤BIO,并且其行为如下:

  1. 每次MD BIO写入数据之前,先向尾部的过滤BIO尝试写入,以过滤BIO成功写入的数据为准,再向自已写入。
  2. 每次MD BIO读取数据之前,先向尾部的过滤BIO尝试读取,如果读取到数据,要向自已写入。
  3. MD BIO只在BIO_gets()方法生成摘要。

使用举例5:

下面这个例子演示了使用抽象IO进行DES加解密的过程。

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#include <openssl/crypto.h>
#include <openssl/bio.h>
#include <openssl/evp.h>

namespace dakuang {}

int main(int argc, char* argv[])
{
    char sKey[] = "12345678";
    char sIV[] = "12345678";
    char sText[] = "hello";
    char sCipher[128] = {0};
    int nCipherLen = 0;

    {
        BIO* bioCipher = BIO_new(BIO_f_cipher());
        const EVP_CIPHER* cipher = EVP_des_ecb();
        BIO_set_cipher(bioCipher, cipher, (const unsigned char*)sKey, (const unsigned char*)sIV, 1); 

        BIO* bioNull = BIO_new(BIO_s_null());
        BIO_push(bioCipher, bioNull);

        int nBytesWrite = BIO_write(bioCipher, sText, strlen(sText));
        printf("write:[%d] \n", nBytesWrite);

        char sBuf[128] = {0};
        int nBytesRead = BIO_read(bioCipher, sBuf, sizeof(sBuf) - 1);
        printf("read %d : [%s] \n", nBytesRead, OPENSSL_buf2hexstr((const unsigned char*)sBuf, nBytesRead));

        memcpy(sCipher, sBuf, nBytesRead);
        nCipherLen = nBytesRead;
        
        BIO_free(bioNull);
        BIO_free(bioCipher);
    }

    {
        BIO* bioCipher = BIO_new(BIO_f_cipher());
        const EVP_CIPHER* cipher = EVP_des_ecb();
        BIO_set_cipher(bioCipher, cipher, (const unsigned char*)sKey, (const unsigned char*)sIV, 0);

        BIO* bioNull = BIO_new(BIO_s_null());
        BIO_push(bioCipher, bioNull);

        int nBytesWrite = BIO_write(bioCipher, sCipher, nCipherLen);
        printf("write:[%d] \n", nBytesWrite);

        char sBuf[128] = {0};
        int nBytesRead = BIO_read(bioCipher, sBuf, sizeof(sBuf) - 1);
        printf("read %d : [%s] \n", nBytesRead, sBuf);

        BIO_free(bioNull);
        BIO_free(bioCipher);
    }

    return 0;
}

输出:
write:[5]
read 8 : [BA:16:C6:A0:25:71:25:AF]
write:[8]
read 5 : [hello]

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容

  • IO流(同步、阻塞) 、 NIO(同步、非阻塞) 、 NIO2(异步、非阻塞) 概述在我们学习Java的IO流之前...
    _情绪疯子阅读 297评论 0 1
  • 概述在我们学习Java的IO流之前,我们都要了解几个关键词 同步与异步(synchronous/asynchron...
    廖111阅读 445评论 0 0
  • 前天刚好看了点《UNIX网络编程》,比较头大。现在我来整理一下所学所得,并用于个人备忘。如果有不对,请批评。 想要...
    tengshe789阅读 694评论 0 0
  • 1.目前很多博客说的五种模型都是从read角度来描述的。 2.我们也常会说Direct IO,或者其他文件IO。他...
    简书徐小耳阅读 1,624评论 0 5
  • 注:1)本人非科班出身,文章的来源主要是基于一些能找到的资料,在理解的基础上做一些总结归纳,以期对IO相关的知识体...
    Drew_Zhong阅读 1,012评论 0 2