发布时间:2010-5-28 16:14
分类名称:OpenSSL
当BIO_read或BIO_write函数调用出错的时候,BIO本身提供了一组出错原因的诊断函数,他们定义如下(openssl/bio.h):
#define BIO_should_read(a) ((a)->flags & BIO_FLAGS_READ)
#define BIO_should_write(a) ((a)->flags & BIO_FLAGS_WRITE)
#define BIO_should_io_special(a) ((a)->flags & BIO_FLAGS_IO_SPECIAL)
#define BIO_retry_type(a) ((a)->flags & BIO_FLAGS_RWS)
#define BIO_should_retry(a) ((a)->flags & BIO_FLAGS_SHOULD_RETRY)
#define BIO_FLAGS_READ 0x01
#define BIO_FLAGS_WRITE 0x02
#define BIO_FLAGS_IO_SPECIAL 0x04
#define BIO_FLAGS_RWS (BIO_FLAGS_READ | BIO_FLAGS_WRITE | BIO_FLAGS_IO_SPECIAL)
#define BIO_FLAGS_SHOULD_RETRY 0x08
BIO * BIO_get_retry_BIO(BIO *bio, int *reason);
int BIO_get_retry_reason(BIO *bio);
因为这些函数是用于决定为什么BIO在读写数据的时候不能读出或写入数据,所以他们一般也是在执行BIO_read或BIO_write操作之后被调用的。
如果读写出错的情况是要求程序稍后重试,那么该函数返回true.如果该函数返回false,这时候判定错误情况就要根据BIO的类型和BIO操作的返回值来确定了。比如,如果对socket类型的BIO调用BIO_read操作并且返回值为0,此时BIO_should_retry返回false就说明socket连接已经关闭了。而如果是file类型的BIO出现这样的情况,那说明就是读到文件eof了。有些类型BIO还会提供更多的出错信息,具体情况参见各自的说明。
如果BIO下层I/O结构是阻塞模式的,那么几乎所有(SSL类型BIO例外)BIO类型都不会返回重试的情况(就是说调用BIO_should_retry不会返回true),因为这时候对下层I/O的调用根本不会进行。所以建议如果你的应用程序能够判定该类型BIO在执行IO操作后不会出现重试的情况时,就不要调用BIO_should_retry函数。file类型BIO就是这样的一个典型例子。
SSL类型的BIO是上述规则的唯一例外,也就是说,既便在阻塞型的I/O结构中,如果在调用BIO_read的时候发生了握手的过程,它也能会返回重试要求(调用BIO_should_retry返回true)。在这种情况下,应用程序可以立刻重新执行失败的I/O操作,或者在底层的I/O结构中设置为SSL_MODE_AUTO_RETRY,那么就可以避免出现这种失败的情况。
如果应用程序在非阻塞型BIO中调用IO操作失败后立刻重试,那么可能导致效率很低,因为在数据允许读取或有效之前,调用会重复返回失败结果。所以,正常的应用应该是等到需要的条件满足之后,程序才执行相关的调用,至于具体怎么做,就跟底层的IO结构有关了。例如,如果一个底层IO是一个soket,并且BIO_should_retry返回true,那么可以调用select()来等待数据有效之后再重试IO操作。在一个线程中,可以使用一个select()来处理多个非阻塞型的BIO,不过,这时候执行效率可能出现非常低的情况,比如如果其中一个延时很长的SSL类型BIO在握手的时候就会导致这种情况。
在阻塞型的IO结构中,对数据的读取操作可能会导致无限期的阻塞,其情况跟系统的IO结构函数有关。我们当然不期望出现这种情况,解决的办法之一是尽量使用非阻塞型的IO结构和使用select函数(或equivalent)来设置等待时间。
该函数返回true如果导致IO操作失败的原因是BIO此时要读数据。
该函数返回true如果导致IO操作失败的原因是BIO此时要写数据。
该函数返回true如果导致IO操作失败的原因是特殊的(也就是读写之外的原因)
返回失败的原因,其代码包括BIO_FLAGS_READ、BIO_FLAGS_WRITE和BIO_FLAGS_IO_SPECIAL。目前的BIO类型只返回其中之一。如果输入的BIO是产生特殊出错情况的BIO,那么该函数返回错误的原因代码,就跟BIO_get_retry_BIO()返回的reason一样。
该函数给出特殊情况错误的简短原因,它返回出错的BIO,如果reason不是设置为NULL,它会包含错误代码,错误码的含义以及下一步应该采取的处理措施应该根据发生这种情况下各种BIO的类型而定。
BIO对是BIO中专门创建的一对缓存BIO,要创建BIO对,调用下面定义的函数(openssl\bio.h):
int BIO_new_bio_pair(BIO **bio1, size_t writebuf1, BIO **bio2, size_t writebuf2);
这个函数调用成功后返回1,这时候bio1和bio2都是有效的了;否则就返回0,而bio1和bio2就会设为NULL,这是后可以检测出错堆栈以得到更多错误信息。
这个BIO对创建之后,它的两端都能作为数据缓冲的输入和输出。典型的应用是它一端和SSL的IO连接,而另一端则被应用控制,这样,应用程序就不需要直接和网络连接打交道了。
这两个BIO对的功能是完全对称的,它们的缓冲区的大小由参数writebuf1和writebuf2决定,如果给定的大小是0,那么该函数就会使用缺省的缓存大小。BIO_new_bio_pair不会检查bio1和bio2是否真的指向其它BIO,bio1和bio2的值都被重写,但是在此之前不会调用BIO_free()函数。所以,在使用bio1和bio2之前,必须自己保证这两个变量是空的BIO,否则可能造成内存泄漏。
值得注意的是,虽然这两个BIO是一对的和一起创建的,但是却必须分别释放。之所以这样做,是有其重要原因的,因为有些SSL函数,如SSL_set_bio或BIO_free会隐含调用BIO_free函数,所以这时候另一端的BIO就只能单独释放了。
下面举一个简单的例子说明问题。
BIO对能给提供应用程序中对网络处理的完全控制能力,程序可以对根据需要调用soket的select()函数,同时却可以避免直接处理SSL接口。下面是使用BIO_new_bio_pair的简单代码模型:
BIO *internal_bio, *network_bio;
……
BIO_new_bio_pair(internal_bio, 0, network_bio, 0);
SSL_set_bio(ssl, internal_bio);
SSL_operations();
……
application | TLS-engine
| |
+----------> SSL_operations()
| /\ ||
| || \/
| BIO-pair (internal_bio)
+----------< BIO-pair (network_bio)
| |
socket |
……
SSL_free(ssl); /* 隐式释放 internal_bio */
BIO_free(network_bio); /* 显式释放 network_bio*/
……
因为BIO对只会简单的缓存数据,而不会直接涉及到连接,所以它看起来就象非阻塞型的接口,如果写缓存满了或读缓存空的时候,调用IO函数就会立刻返回。也就是说,应用程序必须自己对写缓存执行flush操作或对读缓存执行fill操作。可以使用前面介绍过的BIO_ctrl_pending函数看看是否有数据在缓存里面并需要传输到网络上去;为了下面的SSL_operation能够正确执行,可以调用BIO_ctrl_get_read_request函数,以决定需要在写缓存写入多少数据。上面两个函数可以保证正确的SSL操作的进行。
需要注意的是,SSL_operation的调用可能会出现返回ERROR_SSL_WANT_READ值,但这时候写缓存却还有数据的情况,所以应用程序不能简单的根据这个错误代码进行判断,而必须保证写缓存以及执行过flush操作了,否则就会造成死锁现象,因为另一端可能知道等到有数据了才会继续进行下面的操作。
这里讲的是在一个BIO链中,怎么查找一个特定的BIO,怎么遍历BIO链中的每一个BIO,这组函数定义如下(openssl/bio.h):
BIO * BIO_find_type(BIO *b,int bio_type);
BIO * BIO_next(BIO *b);
#define BIO_method_type(b) ((b)->method->type)
可以看到,这组函数中有两个是真正的函数,另一个则是宏定义,其中,bio_type的值定义如下:
#define BIO_TYPE_NONE 0
#define BIO_TYPE_MEM (1|0x0400)
#define BIO_TYPE_FILE (2|0x0400)
#define BIO_TYPE_FD (4|0x0400|0x0100)
#define BIO_TYPE_SOCKET (5|0x0400|0x0100)
#define BIO_TYPE_NULL (6|0x0400)
#define BIO_TYPE_SSL (7|0x0200)
#define BIO_TYPE_MD (8|0x0200)
#define BIO_TYPE_BUFFER (9|0x0200)
#define BIO_TYPE_CIPHER (10|0x0200)
#define BIO_TYPE_BASE64 (11|0x0200)
#define BIO_TYPE_CONNECT (12|0x0400|0x0100)
#define BIO_TYPE_ACCEPT (13|0x0400|0x0100)
#define BIO_TYPE_PROXY_CLIENT (14|0x0200)
#define BIO_TYPE_PROXY_SERVER (15|0x0200)
#define BIO_TYPE_NBIO_TEST (16|0x0200)
#define BIO_TYPE_NULL_FILTER (17|0x0200)
#define BIO_TYPE_BER (18|0x0200)
#define BIO_TYPE_BIO (19|0x0400)
#define BIO_TYPE_DESCRIPTOR 0x0100
#define BIO_TYPE_FILTER 0x0200
#define BIO_TYPE_SOURCE_SINK 0x0400
可以看到,这些定义大部分都是根据各种BIO类型来命名的,但并不是跟现有的BIO类型是一一对应的,在以后的文章里,我会对这些BIO类型一一进行介绍,现在大家只要有一个概念就可以了。
该函数在给定的BIO链中根据特定的BIO类型bio_type进行搜索,搜索的起始位置就是b。如果给定的类型是一个特定的实现类型,那么就会搜索一个给类型的BIO;如果只是一个总体的类型定义,如BIO_TYPE_SOURCE_SINK(就是sourc/sink类型的BIO),那么属于这种类型的最先找到的BIO就是符合条件的。在找到符合的BIO后,BIO_find_type返回该BIO,否则返回NULL。需要注意的是,如果你使用的
该函数顾名思义,是返回当前BIO所在的BIO链中的下一个BIO,所以,它可以用来遍历整个BIO链,并且可以跟BIO_find_type函数结合起来,在整个BIO链中找出所有特定类型的BIO。这个函数是在
该函数返回给定的BIO的类型。
下面给出一个在一个BIO链中找出所有digest类型BIO的例子:
BIO *btmp;
btmp = in_bio; /* in_bio 是被搜索的BIO链 */
do {
btmp = BIO_find_type(btmp, BIO_TYPE_MD);
break; /* 如果没有找到*/
/* btmp 是一个digest类型的BIO,做些你需要做的处理 ...*/
……
btmp = BIO_next(btmp);
} while(btmp);
到此为止,就已经基本写完了BIO的基础知识方面的东西,下面的文章将开始对每一个具体的BIO类型进行介绍。
文件(file)类型BIO的相关函数和定义如下(openssl\bio.h):
BIO_METHOD * BIO_s_file(void);
BIO *BIO_new_file(const char *filename, const char *mode);
BIO *BIO_new_fp(FILE *stream, int flags);
BIO_set_fp(BIO *b,FILE *fp, int flags);
BIO_get_fp(BIO *b,FILE **fpp);
int BIO_read_filename(BIO *b, char *name)
int BIO_write_filename(BIO *b, char *name)
int BIO_append_filename(BIO *b, char *name)
int BIO_rw_filename(BIO *b, char *name)
下面逐一介绍它们的作用和用法。
经过前面的介绍,大家应该对这种类型的函数结构很熟悉了,他们就是生成BIO类型的基本构造函数,BIO_s_file返回file类型的BIO,file类型的BIO封装了一个标准的文件结构,它是一种source/sink型BIO。file类型的BIO_METHOD结构定义如下:
static BIO_METHOD methods_filep=
{
BIO_TYPE_FILE,
"FILE pointer",
file_write,
file_read,
file_puts,
file_gets,
file_ctrl,
file_new,
file_free,
NULL,
};
可以看到,file类型的BIO定义了7个函数,这些函数的实现都在Crypto\bio\bss_file.c里面,大家如果要了解该类型BIO的函数实现,可以参考该文件。事实上,BIO_s_file只是简单返回一个file类型的BIO_METHOD的结构的指针,其函数实现如下:
BIO_METHOD *BIO_s_file(void)
{
return(&methods_filep);
}
其实,从这个结构可以略见BIO的实现的一斑,即BIO的所有动作都是根据它的BIO_METHOD的类型(第一个参数)来决定它的动作和行为的,从而实现BIO对各种类型的多态实现。
在file类型中,使用前面介绍过的BIO_read和BIO_write对底层的file数据流进行读写操作,file类型BIO是支持BIO_gets和BIO_puts函数的。
BIO_flush函数在file类型BIO中只是简单调用了API函数fflush。
BIO_reset函数则将文件指针重新指向文件的开始位置,它调用fseek(stream,0,0)函数实现该功能。
BIO_seek函数将文件指针位置至于所定义的位置ofs上(从文件开头开始计算的偏移ofs),它调用了文件的操作函数fseek(stream,ofs,0),是一个宏定义形式的函数,需要注意的是,因为该函数调用了fseek函数,所以成功的时候返回0,失败的时候返回-1,这是跟标准BIO_seek函数定义不一样的,因为标准的定义是成功返回1,失败返回非正值。
BIO_eof调用了feof函数。
如果在BIO结构中设置了BIO_CLOSE的标志,则在BIO释放的时候会自动调用fclose函数。
该函数根据给定的mode类型创建了一个文件BIO,mode参数的函数跟fopen函数中mode参数的含义是一样的。返回的BIO设置了BIO_CLOSE标志。调用成功返回一个BIO,否则返回NULL。事实上,该函数先调用了fopen函数打开一个文件,然后调用BIO_new函数创建一个file类型BIO,最后调用函数BIO_set_fp函数给BIO结构跟相关的file帮定。
用文件描述符创建一个file类型BIO,参数Flags可以为BIO_CLOSE,BIO_NOCLOSE(关闭标志)以及BIO_FP_TEXT(将文件设置为文本模式,默认的是二进制模式,该选项只对Win32平台有效)。事实上,该函数调用BIO_new函数创建一个file类型BIO,然后调用函数BIO_set_fp函数给BIO结构跟相关的file绑定。需要注意的是,如果下层封装的是stdout,stdin和stderr,他们因为跟一般的是不关闭的,所以应该设置BIO_NOCLOSE标志。调用成功返回一个BIO,否则返回NULL。
该函数将BIO跟文件描述符fp帮定在一起,其参数flags的含义跟BIO_new_fp是一样的。该函数是一个宏定义函数。调用成功返回1,否则返回0,不过目前的实现是从来不会出现失败情况的。
该函数返回file类型BIO中文件描述符,也是一个宏定义。调用成功返回1,否则返回0,不过目前的实现是从来不会出现失败情况的。
返回位置指针的值。是一个宏定义函数。
这四个函数分别设置BIO的读文件名,写文件名,附加文件名以及读写的文件名。他们都是一些宏定义函数。调用成功返回1,否则返回0。
从上面各函数的介绍可以看出,因为BIO调用了底层的各种操作函数,所以,如果底层函数的调用有任何异常,都会反映在BIO的调用上。
下面举几个BIO文件类型操作的简单例子:
1.最简单的实例程序
BIO *bio_out;
bio_out = BIO_new_fp(stdout, BIO_NOCLOSE);
BIO_printf(bio_out, "Hello World\n");
2.上述例子的另一种实现方法
BIO *bio_out;
bio_out = BIO_new(BIO_s_file());
/* 出错*/
) /* 出错则将文件流定向到标准输出*/
BIO_printf(bio_out, "Hello World\n");
3.写文件操作
BIO *out;
out = BIO_new_file("filename.txt", "w");
/*出错 */
BIO_printf(out, "Hello World\n");
BIO_free(out);
4.上述例子的另一种实现方法
BIO *out;
out = BIO_new(BIO_s_file());
/* Error ... */
) /* Error ... */
BIO_printf(out, "Hello World\n");
BIO_free(out);
文件描述符类型BIO也是一个source/sink型的BIO,它定义了以下一些类型的函数(openssl\bio.h):
BIO_METHOD * BIO_s_fd(void);
#define BIO_set_fd(b,fd,c) BIO_int_ctrl(b,BIO_C_SET_FD,c,fd)
#define BIO_get_fd(b,c) BIO_ctrl(b,BIO_C_GET_FD,0,(char *)c)
BIO *BIO_new_fd(int fd, int close_flag);
有一点需要说明的是,虽然存在bss_fd.c文件,但是关于fd类型的BIO的实现函数,并非真正在bss_fd.c里面,而是在bss_sock.c里面,bss_fd.c这是简单包含了bss_sock.c文件,所以大家要找实现函数,应该到bss_sock.c里面找。
该函数返回一个文件描述符类型的BIO_METHOD结构,它封装了文件描述符类型的一些规则,如read()和write()函数等。fd类型的BIO_METHOD结构如下:
static BIO_METHOD methods_fdp=
{
BIO_TYPE_FD,"file descriptor",
fd_write,
fd_read,
fd_puts,
NULL, /* fd_gets, */
fd_ctrl,
fd_new,
fd_free,
NULL,
};
可见,跟file类型BIO相比,它没有实现gets的方法。下面对一些同样的BIO操作函数作些简单说明:
BIO_read和BIO_write对底层的文件描述符结构进行读写操作。这两个函数的一些行为取决于他们所在的平台的文件描述符的读写函数的行为,如果底层的文件描述符是非阻塞型的,那么他们基本上是跟我们前面介绍过得BIO的IO操作函数一样的。请参看前面的文章和资料。socket是一类特殊的描述符,不应该使用文件描述符类型的BIO来封装它,而应该使用专门的socke类型BIO,在以后我们会进行介绍。
BIO_puts是支持的,但是BIO_gets在本类型描述符中是不支持的。如果设置了关闭标志,那么当BIO被释放的时候底层的文件描述符就会被关闭。
BIO_reset调用lseek(fd,0,0)函数,使文件指针指向开始的位置。调用成功返回0,失败返回-1。
BIO_seek调用了lseek(fd,ofs,0)函数,设置文件指针的位置到从文件头偏移ofs的位置,成功返回文件指针的位置,失败返回-1。
BIO_tell返回目前文件指针的位置,它其实调用了lseek(fd,0,1)函数,失败返回-1。
该函数将BIO的底层文件描述符设置为fd,关闭标志也同时做了设置,其含义与文件类型BIO相应的含义一样。返回1。
返回相应BIO的底层文件描述符,存于参数c,不过,同时也作为返回值返回。c应该为int *类型的指针。如果BIO没有初始化,调用该函数将失败,返回-1。
创建并返回一个底层描述符为fd,关闭标志为close_flag的文件描述符类型的BIO。其实,该函数依次调用了BIO_s_fd、BIO_new和BIO_set_fd完成了该功能。该函数如果调用失败返回NULL。
下面是一个简单的例子:
BIO *out;
out = BIO_new_fd(fileno(stdout), BIO_NOCLOSE);
BIO_printf(out, "Hello World\n");
BIO_free(out);
Socket类型的BIO也是一种source/sink型BIO,封装了Socket的IO操作,它相关的一些函数定义如下(openssl\bio.h):
BIO_METHOD * BIO_s_socket(void);
#define BIO_set_fd(b,fd,c) BIO_int_ctrl(b,BIO_C_SET_FD,c,fd)
#define BIO_get_fd(b,c) BIO_ctrl(b,BIO_C_GET_FD,0,(char *)c)
BIO *BIO_new_socket(int sock, int close_flag);
前面我们在介绍fd类型BIO的时候曾经说过,它的函数的实现文件跟Soket类型的BIO其实是放在一起的,都在文件bss_socket.c里面,从这些定义我们就可以知道,之所以这样做,是因为这两种类型的BIO实现的函数基本是相同的,并且具有很多的共性。
该函数返回一个Socket类型的BIO_METHOD结构,BIO_METHOD结构的定义如下:
static BIO_METHOD methods_sockp=
{
BIO_TYPE_SOCKET,
"socket",
sock_write,
sock_read,
sock_puts,
NULL, /* sock_gets, */
sock_ctrl,
sock_new,
sock_free,
NULL,
};
可以看到,它跟fd类型BIO在实现的动作上基本上是一样的。只不过是前缀名和类型字段的名称不一样。其实在象Linux这样的系统里,Socket类型跟fd类型是一样,他们是可以通用的,但是,为什么要分开来实现呢,那是因为有些系统如windows系统,socket跟文件描述符是不一样的,所以,为了平台的兼容性,openssl就将这两类分开来了。
BIO_read和BIO_write对底层的Socket结构进行读写操作。
BIO_puts是支持的,但是BIO_gets在Socket类型BIO中是不支持的,大家如果看源代码就可以知道,虽然BIO_gets在Socket类型是不支持的,但是如果调用该函数,不会出现异常,只会返回-1的出错信息。
如果设置了关闭标志,那么当BIO被释放的时候底层的Socket连接就会被关闭。
该函数将Socket描述符fd设置为BIO的底层IO结构,同时可以设置关闭标志c。该函数返回1。
该函数返回指定BIO的Socket描述符,如果c参数不是NULL,那么就将该描述符存在参数c里面,当然,Socket描述符同时也作为返回值,如果BIO没有初始化则调用失败,返回-1。
该函数根据给定的参数返回一个socket类型的BIO,成功返回该BIO指针,失败返回NULL。其实,该函数依次调用了BIO_s_socket,BIO_new和BIO_set_fd实现它的功能。
这是一个空的source/sink型BIO,写到这个BIO的数据都被丢掉了,从这里执行读操作也总是返回EOF。该BIO非常简单,其相关函数的定义如下(openssl\bio.h):
BIO_METHOD * BIO_s_null(void);
其相关的源文件实现函数在bss_null.c里面。
该函数返回一个NULL型的BIO_METHOD结构,该结构定义如下:
static BIO_METHOD null_method=
{
BIO_TYPE_NULL,
"NULL",
null_write,
null_read,
null_puts,
null_gets,
null_ctrl,
null_new,
null_free,
NULL,
};
从结构上看,这个类型的BIO实现了不少的函数,但是,仔细看看源文件,就会发现所有这些函数都只是简单返回0、1或者输入数据的长度,而不作任何事情。熟悉Linux系统的技术人员可能知道,这跟Linux系统的/dev/null设备的行为是一样的。
一般来说,在openssl里面,这种类型的BIO是置放在BIO链的末尾的,比如在应用程序中,如果你要将一些数据通过filter型的BIO digest进行摘要算法,但不需要把它送往任何地方,又因为一个BIO链要求以source/sink型BIO开始或结束,所以这时候就可以在BIO链的末尾添加一个source/sink型的NUll类型BIO来实现这个功能。
内存(mem)类型BIO所定义的相关系列函数如下(openssl\bio.h):
BIO_METHOD * BIO_s_mem(void);
BIO_set_mem_eof_return(BIO *b,int v)
long BIO_get_mem_data(BIO *b, char **pp)
BIO_set_mem_buf(BIO *b,BUF_MEM *bm,int c)
BIO_get_mem_ptr(BIO *b,BUF_MEM **pp)
BIO *BIO_new_mem_buf(void *buf, int len);
内存型BIO是source/sink型BIO,它使用内存作为它的I/O。写进该类型BIO的数据被存储在BUF_MEM结构中,该结构被定义为适合存储数据的一种结构,其结构定义如下:
typedef struct buf_mem_st
{
int length; /* current number of bytes */
char *data;
int max; /* size of buffer */
} BUF_MEM;
可见,该结构定义了内存数据长度,数据存储空间以及最大长度三个变量来表述一段内存存储数据。但值得注意的是,内存型BIO的内存是可以无限扩大的,也就是说,不过你往里面写多少数据,都能成功执行。
一般来说,任何写入内存型BIO的数据都能被读出,除非该内存型BIO是只读类型的,那么,这时候如果对只读的内存型BIO执行读操作,那么相关数据就会从该BIO删除掉(其实没有删掉,只是指针往后面移动,访问不了了,如果调用BIO_reset就可以再访问)。
该函数返回一个内存型的BIO_METHOD结构,期定义如下:
static BIO_METHOD mem_method=
{
BIO_TYPE_MEM,
"memory buffer",
mem_write,
mem_read,
mem_puts,
mem_gets,
mem_ctrl,
mem_new,
mem_free,
NULL,
};
BIO_write和BIO_read函数是支持的。对内存型BIO执行写操作总是成功的,因为内存型BIO的内存能够无限扩大。任何一个对可读写的内存型BIO的读操作都会在使用内部拷贝操作从BIO里面删除该段数据,这样一来,如果BIO里面有大量的数据,而读的却只是很小的一些片断,那么会导致操作非常慢。使用只读的内存型BIO避免了这个问题。在使用的时候,如果内存型BIO必须使用可读写的,那么可以加一个Buffer型BIO到BIO链中,这可以使操作速度更快。在以后的版本(该文档所述版本为
BIO_gets和BIO_puts操作在该类型BIO是支持的。
如果设置了BIO_CLOSE标志,那么内存型BIO被释放的时候其底层的BUF_MEM型BIO也同时被释放。
BIO_reset函数被调用时,如果该BIO是可读写的,那么该BIO所有数据都会被清空;如果该BIO是只读的,那么该操作只会简单将指针指向原始位置,里面的数据可以再读。该文档所述版本的(
BIO_eof返回true,表明只时候BIO里面没有可读数据。
BIO_ctrl_pending返回目前BIO里面存储的数据的字节(byte)数。
该函数设置一个没有数据的内存型BIO的执行读动作的行为。如果参数v是0,那么该空的内存型BIO就会返回EOF,也就是说,这时候返回为0,如果调用BIO_should_retry就会返回false。如果参数v为非零,那么就会返回v,并且同时会设置重试标志,也就是说此时调用BIO_read_retry会返回true。为了跟正常的返回正值避免混淆,v应该设置为负值,典型来说是-1。
该函数是一个宏定义函数,它将参数pp的指针指向内存型BIO的数据开始处,返回所有有效的数据。
该函数将参数bm所代表的BUF_MEM结构作为该BIO的底层结构,并把关闭标志设置为参数c,c可以是BIO_CLOSE或BIO_NOCLOSE,该函数也是一个宏定义。
该函数也是一个宏定义函数,它将底层的BUF_MEM结构放在指针pp中。
该函数创建一个内存型BIO,其数据为buf里面长度为len的数据(单位为byte),如果参数len是-1,那么就默认buf是以null结束的,使用strlen解决长度。这时候BIO被设置为只读的,不能执行写操作。它用于数据需要存储在一块静态内存中并以BIO形式存在的时候。所需要的数据是直接从内存中读取的,而不是先要执行拷贝操作(读写方式的内存BIO就是要先拷贝),这也就要求这块内存是只读的,不能改变,一直维持到BIO被释放。
例子
1.创建一个内存型BIO并写入数据
BIO *mem = BIO_new(BIO_s_mem());
BIO_puts(mem, "Hello World\n");
2.创建一个只读的内存型BIO
char data[] = "Hello World";
BIO *mem;
mem = BIO_new_mem_buf(data, -1);
3.把一个BUF_MEM结构从一个BIO中取出并释放该BIO
BUF_MEM *bptr;
BIO_get_mem_ptr(mem, &bptr);
BIO_set_close(mem, BIO_NOCLOSE); /* BIO_free() 不释放BUF_MEM结构 */
BIO_free(mem);
BIO对也是作为一种source/sink类型的BIO来处理的,也就是说,BIO里面还提供了一种专门的BIO_METHO方法来处理BIO对的各种操作。BIO对的各种相关的函数定义如下(openssl\bio.h):
BIO_METHOD *BIO_s_bio(void);
#define BIO_make_bio_pair(b1,b2) (int)BIO_ctrl(b1,BIO_C_MAKE_BIO_PAIR,0,b2)
#define BIO_destroy_bio_pair(b) (int)BIO_ctrl(b,BIO_C_DESTROY_BIO_PAIR,0,NULL)
#define BIO_shutdown_wr(b) (int)BIO_ctrl(b, BIO_C_SHUTDOWN_WR, 0, NULL)
#define BIO_set_write_buf_size(b,size) (int)BIO_ctrl(b,BIO_C_SET_WRITE_BUF_SIZE,size,NULL)
#define BIO_get_write_buf_size(b,size) (size_t)BIO_ctrl(b,BIO_C_GET_WRITE_BUF_SIZE,size,NULL)
int BIO_new_bio_pair(BIO **bio1, size_t writebuf1, BIO **bio2, size_t writebuf2);
#define BIO_get_write_guarantee(b) (int)BIO_ctrl(b,BIO_C_GET_WRITE_GUARANTEE,0,NULL)
size_t BIO_ctrl_get_write_guarantee(BIO *b);
#define BIO_get_read_request(b) (int)BIO_ctrl(b,BIO_C_GET_READ_REQUEST,0,NULL)
size_t BIO_ctrl_get_read_request(BIO *b);
int BIO_ctrl_reset_read_request(BIO *b);
可以看到,这些函数中大多数是宏定义函数并且都是基于BIO_ctrl函数的。
BIO对类型的BIO是一对source/sink型的BIO,数据通常是从一个BIO缓冲写入,从另一个BIO读出。其实,从源代码(bss_bio.c)可以看出,所谓的BIO对只是将两个BIO的终端输出(BIO结构中参数peer的ptr成员)相互设置为对方,从而形成一种对称的结构,如下:
bio1->peer->ptr=bio2
bio2->peer->ptr=bio1
数据流向1(写bio1,读bio2):--->bio1--->bio2--->
数据流行2(写bio2,读bio1):--->bio2--->bio1--->
因为没有提供内部数据结构的内存锁结构(lock),所以,一般来说这个BIO对的两个BIO都必须在一个线程下使用。因为BIO链通常是以一个source/sink BIO结束的,所以就可以实现应用程序通过控制BIO对的一个BIO从而控制整个BIO链的数据处理。其实,也就相当于BIO对给应用程序提供了一个处理整个BIO链的入口。上次我们说BIO对的时候就说过,BIO对的一个典型应用就是在应用程序里面控制TLS/SSL的I/O接口,一般来说,在应用程序想在TLS/SSL中使用非标准的传输方法或者不适合使用标准的socket方法的时候就可以采用这样的方法来实现。
前面提过,BIO对释放的时候,需要分别释放两个BIO,如果在使用BIO_free或者BIO_free_all释放了其中一个BIO的时候,另一个BIO就也必须要释放。
当BIO对使用在双向应用程序的时候,如TLS/SSL,一定要对写缓冲区里面的数据执行flush操作。当然,也可以通过在BIO对中的另一个BIO调用BIO_pending函数,如果有数据在缓冲区中,那么就将它们读出并发送到底层的传输通道中区。为了使请求或BIO_should_read函数调用成功(为true),在执行任何正常的操作(如select)之前,都必须这样做才行。
下面举一个例子说明执行flush操作的重要性:
考虑在TLS/SSL握手过程中,采用了BIO_write函数发送了数据,相应的操作应该使BIO_read。BIO_write操作成功执行并将数据写入到写缓冲区中。BIO_read调用开始会失败,BIO_should_retry返回true。如果此时对写缓冲区不执行flush操作,那么BIO_read调用永远不会成功,因为底层传输通道会一直等待直到数据有效(但数据却在写缓冲区里,没有传到底层通道)。
该函数返回一个BIO对类型的BIO_METHOD,其定义如下:
static BIO_METHOD methods_biop =
{
BIO_TYPE_BIO,
"BIO pair",
bio_write,
bio_read,
bio_puts,
NULL /* 没有定义 bio_gets */,
bio_ctrl,
bio_new,
bio_free,
NULL /* 没有定义 bio_callback_ctrl */
};
从定义中可以看到,该类型的BIO不支持BIO_gets的功能。
BIO_read函数从缓冲BIO中读取数据,如果没有数据,则发出一个重试请求。
BIO_write函数往缓冲BIO中写入数据,如果缓冲区已满,则发出一个重试请求。
BIO_ctrl_pending和BIO_ctrl_wpending函数可以用来查看在读或写缓冲区里面有效的数据的数量。
BIO_reset函数将写缓冲区里面的数据清除。
该函数将两个单独的BIO连接起来成为一个BIO对。
该函数跟上面的函数相反,它将两个连接起来的BIO对拆开;如果一个BIO对中的任何一个BIO被释放,该操作会自动执行。
该函数关闭BIO对的其中一个BIO,一个BIO被关闭后,针对该BIO的任何写操作都会返回错误。从另一个BIO读数据的时候要么返回剩余的有效数据,要么返回EOF。
该函数设置BIO的缓冲区大小。如果该BIO的缓存区大小没有初始化,那么就会使用默认的值,大小为17k,这对于一个TLS记录来说是足够大的了。
该函数返回写缓冲区的大小。
该函数我们在前面的《BIO系列之9---BIO对的创建和应用》中已经做了详细的介绍,其实,它是调用了BIO_new,BIO_make_bio_pair和BIO_set_write_buf_size函数来创建一对BIO对的。如果两个缓冲区长度的参数都为零,那么就会使用默认的缓冲区长度。
这两个函数返回当前能够写入BIO的数据的最大长度。如果往BIO写入的数据长度比该函数返回的数据长度大,那么BIO_write返回的写入数据长度会小于要求写入的数据,如果缓冲区已经满了,则会发出一个重试的请求。这两个函数的唯一不同之处是一个使用函数实现的,一个是使用宏定义实现的。
这两个函数返回要求发送的数据的长度,这通常是在对该BIO对的另一个BIO执行读操作时因为缓冲区数据为空导致失败时发出的请求。所以,这通常用来表明现在应该写入多少数据才能使接下来的读操作能够成功执行,这在TLS/SSL应用程序中是非常有用的,因为对于这个协议来说,读取的数据长度比缓冲区的数据长度通常要有意义的多。如果在读操作成功之后调用这两个函数会返回0,如果在调用该函数之前有新的数据写入(不管是部分还是全部满足需要读取的数据的要求),那么调用该函数也会返回0。理所当然,该函数返回的数据长度肯定不会大于BIO_get_write_guarantee函数返回的数据长度。
该函数就是把BIO_get_read_request要返回值设置为0。