Openssl之BIO系列

130 篇文章 1 订阅
85 篇文章 0 订阅

1.BIO-抽象的IO接口

其实包含了很多种接口,用通用的函数接口,主要控制在BIO_METHOD中的不同实现函数控制,我初步估计了一下,大概有14种,包括4filter型和10source/sink型。

BIO是在底层覆盖了许多类型I/O接口细节的一种应用接口,如果你在程序中使用BIO,那么就可以和SSL、非加密的网络以及文件IO进行透明的连接。有两种不通的BIO接口,一种是source/sink型,一种是fileter型。

顾名思义,source/sink类型的BIO是数据源或输入数据源(我不知道sink该怎么翻译),例如,sokect BIO和文件BIO。而filter BIO就是把数据从一个BIO转换到另外一个BIO或应用接口,在转换过程中,这些数据可以不修改(如信息摘要BIO),也可以进行转换。例如在加密BIO中,如果写操作,数据就会被加密,如果是读操作,数据就会被解密。

BIO可以连接在一起成为一个BIO链(单个的BIO就是一个环节的BIO链的特例),如下是BIO的结构定义,可以看到它有上下环节的:

struct bio_st

{

BIO_METHOD *method;

/* bio, mode, argp, argi, argl, ret */

long (*callback)(struct bio_st *,int,const char *,int, long,long);

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 */BIO下联

struct bio_st *prev_bio; /* used by filter BIOs */BIO上联

int references;

unsigned long num_read;

unsigned long num_write;

 

CRYPTO_EX_DATA ex_data;

};

一个BIO链通常包括一个source BIO和一个或多个filter BIO,数据从第一个BIO读出或写入,然后经过一系列BIO变化到输出(通常是一个source/sink BIO)。

2.BIO结构介绍

BIO的结构定义和相关项解析如下:(包含在bio.h文件中,其主文件为bio_lib.c

typedef struct bio_st BIO;

 

struct bio_st

{

BIO_METHOD *method;

/*BIO方法结构,是决定BIO类型和行为的重要参数,各种BIO的不同之处主要也正在于此项。*/

/* bio, mode, argp, argi, argl, ret */

long (*callback)(struct bio_st *,int,const char *,int, long,long); //BIO回调函数

char *cb_arg; /* first argument for the callback */ //回调函数的第一个参量

 

int init; //初始化标志,初始化了为1,否则为0

int shutdown; //BIO开关标志,如果为1,则处于关闭状态,如果为0,则处于打开的状态。

int flags; /* extra storage */

int retry_reason;

int num;

void *ptr;

struct bio_st *next_bio; /* used by filter BIOs */BIO下联

struct bio_st *prev_bio; /* used by filter BIOs */BIO上联

int references;

unsigned long num_read;//读出的数据长度

unsigned long num_write;//写入的数据长度

 

CRYPTO_EX_DATA ex_data;

};

BIO的所用成员中,method可以说是最关键的一个成员,它决定了BIO的类型,可以看到,在声明一个新的BIO结构时,总是使用下面的声明:

BIO* BIO_new(BIO_METHOD *type);

在源代码可以看出,BIO_new函数除了给一些初始变量赋值外,主要就是把type中的各个变量赋值给BIO结构中的method成员。一般来说,上述type参数是以一个类型生成函数的形式提供的,如生成一个mem型的BIO结构,就使用下面的语句:

BIO *mem = BIO_new(BIO_s_mem());

这样的函数有以下一些:

2.1 source/sink

1.       BIO_s_accept():是一个封装了类似TCP/IP socket Accept规则的接口,并且使TCP/IP操作对于BIO接口是透明的。

2.       BIO_s_bio():封装了一个BIO对,数据从其中一个BIO写入,从另外一个BIO读出

3.       BIO_s_connect():是一个封装了类似TCP/IP socket Connect规则的接口,并且使TCP/IP操作对于BIO接口是透明的

4.       BIO_s_fd():是一个封装了文件描述符的BIO接口,提供类似文件读写操作的功能

5.       BIO_s_file():封装了标准的文件接口的BIO,包括标志的输入输出设备如stdin

6.       BIO_s_mem():封装了内存操作的BIO接口,包括了对内存的读写操作

7.       BIO_s_null():返回空的sinkBIO接口,写入这种接口的所有数据读被丢弃,读的时候总是返回EOF

8.       BIO_s_socket():封装了socket接口的BIO类型

2.2 filter

1.       BIO_f_base64():封装了base64编码方法的BIO,写的时候进行编码,读的时候解码

2.       BIO_f_buffer():封装了缓冲区操作的BIO,写入该接口的数据一般是准备传入下一个BIO接口的,从该接口读出的数据一般也是从另一个BIO传过来的。

3.       BIO_f_cipher():封装了加解密方法的BIO,写的时候加密,读的时候解密

4.       BIO_f_md():封装了信息摘要方法的BIO,通过该接口读写的数据都是已经经过摘要的。

5.       BIO_f_null():一个不作任何事情的BIO,对它的操作都简单传到下一个BIO去了,相当于不存在。

6.       BIO_f_ssl():封装了openssl SSL协议的BIO类型,也就是为SSL协议增加了一些BIO操作方法。

上述各种类型的函数正是构成BIO强大功能的基本单元,所以,要了解BIO的各种结构和功能,也就应该了解这些函数类型相关的操作函数。所有这些源文件,都基本上包含于/crypto/bio/目录下的同名.c文件(大部分是同名的)中。

BIO_METHOD里面,定义了一组行为函数,上述不同类型的BIO_METHOD行为函数的定义是不同的,其结构如下(以非16位系统为例):

typedef struct bio_method_st

{

int type;

const char *name;

int (*bwrite)(BIO *, const char *, int);

int (*bread)(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 *);

} BIO_METHOD;

BIO的成员中,callback也是比较重要的,它能够用于程序调试用或者自定义改变BIO的行为。详细会在以后相关的部分介绍。BIO的很多操作,都是BIO_ctrl系列函数根据不同参数组成的宏定义来完成的。所以要了解BIO的行为,了解BIO_ctrl系列函数以及其各个参数的意义也是很重要的。

2.3 BIO目录文件的简要说明

1.       bio.h:主定义的头文件,包括了很多通用的宏的定义。

2.       bio_lib.c主要的BIO操作定义文件,是比较上层的函数了。

3.       bss_*系列:是soruce/sinkBIO具体的操作实现文件

4.       bf_*系列:是filterBIO具体的操作实现文件

5.       bio_err.c:是错误信息处理文件

6.       bio_cb.c:是callback函数的相关文件

7.       b_print.c:是信息输出的处理函数文件

8.       b_socket.c:Socket连接的一些相关信息处理文件

9.       b_dump.c:是对内存内容的存储操作处理

3.BIO的声明和释放等基本操作

BIO的基本操作系列函数中,他们用来BIO分配和释放操作,其声明形式如下(openssl/bio.h):

BIO * BIO_new(BIO_METHOD *type);

int BIO_set(BIO *a,BIO_METHOD *type);

int BIO_free(BIO *a);

void BIO_vfree(BIO *a);

void BIO_free_all(BIO *a);

下面分别对这些函数进行解释:

3.1 BIO_new

这个函数创建并返回一个相应的新的BIO,并根据给定的BIO_METHOD类型调用下述的BIO_set()函数给BIO结构的method成员赋值,如果创建或给method赋值失败,则返回NULL。创建一个Memory类型的BIO例子如下:

BIO* mem=BIO_new(BIO_s_mem());

有些类型的BIO使用BIO_new()函数之后就可以直接使用了,如memory类型的BIO;而有些BIO创建之后还需要一些初始化工作,如文件BIO。一般来说,也提供了这样的一些函数来创建和初始化这种类型的BIO

这是什么意思呢,举个简单的例子大家就明白了。比如创建一个文件BIO,使用下面的代码:

BIO* in=NULL;

in=BIO_new(BIO_s_file());

BIO_read_filename(in,"rsa512.pem");

这样,BIO in才能使用,而如果是创建一个memory类型的BIO,则只需要如下一句代码:

BIO* mem=BIO_new(BIO_s_mem());

然后就可以对该BIO mem进行操作了。

另外,需要补充的是(这个大家从前面两篇文章可能已经认识到了),对于source/sink类型的BIO,其类型创建函数一般为BIO_s_*的形式,对于filter型的函数,其类型创建函数一般为BIO_f_*的形式。

3.2 BIO_set

该函数功能比较简单,就是对一个已经存在的BIO设置新的BIO_METHOD类型。其实就是简单的对BIO的各个成员进行初始化,并将参数type赋值给该BIO。其实,BIO_new函数在使用OPENSSL_mallocBIO分配了内存之后,就简单调用了BIO_set函数进行初始化工作。所以一般来说,除非你要重新设置你已经存在的BIO,否则是不需要直接调用这个函数的。成功操作返回1,否则返回0

3.3 BIO_free

该函数释放单个BIO的内存和资源,成功操作返回1,失败返回0BIO的操作不仅仅是释放BIO结构所占用的资源,也会释放其下层的I/O资源,比如关闭释放相关的文件符等,这对不同类型的BIO是不一样的,详细的请参看各种类型BIO本身的说明文件和源文件。需要注意的是,BIO_free只释放当前的一个BIO,如果用来释放一个BIO链,就可能会导致内存泄漏,这种情况应该使用下述的BIO_free_all函数。

3.4 BIO_vfree

该函数功能与BIO_free完全相同,只是没有返回值。事实上,它简单调用了BIO_free函数,但不返回该函数的返回值,所以它的函数实现代码只有一个语句。

3.5 BIO_free_all

该函数释放这个BIO链,并且即使在这个过程中,如果释放其中一个BIO出错,释放过程也不会停止,会继续释放下面的BIO,这保证了尽量避免内存泄漏的出现。如果你非要调用这个函数释放单个的BIO,那么效果跟BIO_free是一样的。事实上,该函数只是简单的遍历整个BIO链,并调用BIO_free释放各个环节的BIO

4.BIO控制函数介绍

BIO控制函数有许多,并且不同的BIO类型还有不同的控制函数,这里只简单介绍一些通用的BIO控制函数,至于某种类型BIO的特定控制函数,则参考后续的文件。

BIO的通用控制函数有以下几种,其声明如下(openssl/bio.h):

long BIO_ctrl(BIO *bp,int cmd,long larg,void *parg);

long BIO_callback_ctrl(BIO *b, int cmd, void (*fp)(struct bio_st *, int, const char *, int, long, long));

char * BIO_ptr_ctrl(BIO *bp,int cmd,long larg);

long BIO_int_ctrl(BIO *bp,int cmd,long larg,int iarg);

 

int BIO_reset(BIO *b);

int BIO_seek(BIO *b, int ofs);

int BIO_tell(BIO *b);

int BIO_flush(BIO *b);

int BIO_eof(BIO *b);

int BIO_set_close(BIO *b,long flag);

int BIO_get_close(BIO *b);

int BIO_pending(BIO *b);

int BIO_wpending(BIO *b);

size_t BIO_ctrl_pending(BIO *b);

size_t BIO_ctrl_wpending(BIO *b);

其实,在这些函数中,除了BIO_ctrlBIO_callback_ctrlBIO_ptr_ctrl,BIO_int_ctrlBIO_ctrl_pendingBIO_ctrl_wpending是真正的函数外,其它都是宏定义,而且,在这些函数中,除了BIO_ctrlBIO_callback_ctrl,其它基本上都是简单的BIO_ctrl输入不同的参数的调用。下面就一个一个介绍这些函数。

4.1 BIO_ctrl

从上面的叙述可以知道,BIO_ctrl是整个控制函数中最基本的函数,它支持不同的命令输入,从而产生不同的功能,由此,它也就衍生了许多其它函数,作为一个比较地层的控制函数,一般来说用户并不需要直接调用它,因为在它之上已经使用宏定义和函数调用的形式建造了许多直接面向用户的函数。

filter型的BIO没有定义BIO_ctrl功能,如果对他们调用这个函数,他们就简单的把命令传到BIO链中的下一个BIO。也就是说,通常可以不用直接调用一个BIOBIO_ctrl函数,只需要在它所在的BIO链上调用该函数,那么BIO链就会自动将该调用函数传到相应的BIO上去。这样可能就会导致一些意想不到的结果,比如,在目前的filterBIO中没有实现BIO_seek()函数(大家待会就会明白BIO_seek就是BIO_ctrl的简单宏定义),但如果在这个BIO链上的末尾是一个文件或文件描述符型BIO,那么这个调用也会返回成功的结果。

对于source/sinkBIO来说,如果他们不认得BIO_ctrl所定义的操作,那么就返回0

4.2 BIO_callback_ctrl

这个函数是这组控制函数中唯一一个不是通过调用BIO_ctrl建立起来的,它有自己的实现函数,而且跟BIO_ctrl毫不相干。跟BIO_ctrl一样,它也是比较底层的控制函数,在它上面也定义了一些直接面向用户的控制函数,一般来说,用户不需要直接调用该函数。

需要说明的是,该函数和BIO_ctrl函数为了实现不同类型BIO具有不同的BIO_ctrl控制功能,他们的操作基本上都是由各个BIOcallback函数来定义的。这是不同的BIO能灵活实现不同功能的根本所在。

4.3 BIO_ptr_ctrlBIO_int_ctrl

这两个函数都是简单的调用了BIO_ctrl函数,不同的是,后者是输入了四个参数并传入到BIO_ctrl函数中,简单返回了调用BIO_ctrl返回的返回值;而前者只输入了三个参数,最后一个BIO_ctrl参数是作为输出参数并作为返回值的。

4.4 BIO_reset

该函数是BIO_ctrl的宏定义函数,为了大家对BIO_ctrl的宏定义函数有一个感性的认识,我把这个宏定义写出来,如下:

#define BIO_reset(b) (int)BIO_ctrl(b,BIO_CTRL_RESET,0,NULL)

这就是BIO_ctrl的典型宏定义方式,它通过这种方式产生了大量的控制函数。顾名思义,BIO_reset函数只是简单的将BIO的状态设回到初始化的时候的状态,比如文件BIO,调用该函数就是将文件指针指向文件开始位置。一般来说,调用成功的时候该函数返回1,失败的时候返回0或-1;但是文件BIO是一个例外,成功调用的时候返回0,失败的时候返回-1

4.5 BIO_seek

该函数也是BIO_ctrl的宏定义函数,其定义如下:

#define BIO_seek(b,ofs) (int)BIO_ctrl(b,BIO_C_FILE_SEEK,ofs,NULL)

该函数将文件相关的BIO(文件和文件描述符类型)的文件指针知道距离开始位置ofs(输入参数)字节的位置上。调用成功的时候,返回文件的位置指针,否则返回-1;但是文件BIO例外,成功的时候返回0,失败的时候返回-1

4.6 BIO_tell

该函数也是BIO_ctrl的宏定义函数,其定义如下:

#define BIO_tell(b) (int)BIO_ctrl(b,BIO_C_FILE_TELL,0,NULL)

该函数返回了文件相关BIO的当前文件指针位置。跟BIO_seek一样,调用成功的时候,返回文件的位置指针,否则返回-1;但是文件BIO例外,成功的时候返回0,失败的时候返回-1

4.7 BIO_flush

该函数也是BIO_ctrl的宏定义函数,其定义如下:

#define BIO_flush(b) (int)BIO_ctrl(b,BIO_CTRL_FLUSH,0,NULL)

该函数用来将BIO内部缓冲区的数据都写出去,有些时候,也用于为了根据EOF查看是否还有数据可以写。调用成功的时候该函数返回1,失败的时候返回0或-1。之所以失败的时候返回0或者-1,是为了标志该操作是否需要稍后以跟BIO_write()相同的方式重试。这时候,应该调用BIO_should_retry()函数,当然,正常的情况下该函数的调用应该是失败的。

4.8 BIO_eof

该函数也是BIO_ctrl的宏定义函数,其定义如下

#define BIO_eof(b) (int)BIO_ctrl(b,BIO_CTRL_EOF,0,NULL)

如果BIO读到EOF,该函数返回1,至于EOF的具体定义,根据BIO的类型各不相同。如果没有读到EOF,该函数返回0

4.9 BIO_set_close

该函数也是BIO_ctrl的宏定义函数,其定义如下:

#define BIO_set_close(b,c) (int)BIO_ctrl(b,BIO_CTRL_SET_CLOSE,(c),NULL)

该函数设置BIO的关闭标志,该标志可以为BIO_CLOSEBIO_NOCLOSE。一般来说,该标志是为了指示在source/sinkBIO释放该BIO的时候是否关闭其下层的I/O流。该函数总是返回1

4.10 BIO_get_close

该函数也是BIO_ctrl的宏定义函数,其定义如下:

#define BIO_get_close(b) (int)BIO_ctrl(b,BIO_CTRL_GET_CLOSE,0,NULL)

该函数读取BIO的关闭标志,返回BIO_CLOSEBIO_NOCLOSE

4.11 BIO_pendingBIO_wpendingBIO_ctrl_pendingBIO_ctrl_wpending

这些函数都是用来得到BIO中读缓存或写缓存中字符的数目的,返回相应缓存中字符的数目。前面两个函数也是BIO_ctrl的宏定义函数,其定义如下:

#define BIO_pending(b) (int)BIO_ctrl(b,BIO_CTRL_PENDING,0,NULL)

#define BIO_wpending(b) (int)BIO_ctrl(b,BIO_CTRL_WPENDING,0,NULL)

后两个函数功能跟他们是一样的,只不过他们是通过调用BIO_ctrl函数实现的,而不是宏定义。此外,前面两个函数返回的是int型,而后面两个函数返回的是size_t型。

需要注意的是,BIO_pending BIO_wpending并不是在所有情况下都能很可靠地得到缓存数据的数量,比如在文件BIO中,有些数据可能在文件内部结构的缓存中是有效的,但是不可能简单的在BIO中得到这些数据的数量。而在有些类型BIO中,这两个函数可能还不支持。基于此,Openssl作者本身也建议一般不要使用这两个函数,而是使用后面两个,除非你对你所做的操作非常清楚和了解。

5.CallBack函数及其控制

通过前面的介绍大家已经知道,BIOcallback函数是非常重要的,是实现BIO多态性的一个关键因素之一,BIO提供的callback控制系列函数有五个,其实都是一些宏定义,下面是它的声明和定义(openssl/bio.h):

#define BIO_set_callback(b,cb) ((b)->callback=(cb))

#define BIO_get_callback(b) ((b)->callback)

#define BIO_set_callback_arg(b,arg) ((b)->cb_arg=(char *)(arg))

#define BIO_get_callback_arg(b) ((b)->cb_arg)

其中,callback函数本身的声明如下:

typedef long callback(BIO *b, int oper, const char *argp, int argi, long argl, long retvalue);

此外,还有一个用于调试目的的函数,其实声明如下:

long BIO_debug_callback(BIO *bio,int cmd,const char *argp,int argi,long argl,long ret);

如果要看具体的例子,那么在文件crypto/bio/bio_cb.c的函数BIO_debug_callback()本身就是一个非常好的例子。

下面,我们从callback函数本身开始分别简单介绍这些函数的作用。

5.1 callback

callback函数在BIO中非常重要,许多控制功能都是要通过callbank函数协助完成的,比如BIO要执行释放的操作BIO_free,那么其实它是先调用callback函数设置下面的操作将是释放操作(控制码:BIO_CB_FREE),然后才调用别的相关函数执行真正的操作,在后面我们会列出这些控制功能函数,并简单说明callback函数是怎么在这些功能的实现中使用的。现在,我先简单介绍callback函数的各个参数:(参数名字参看说明的函数的声明)

1.       参数-b:这是callback函数的输入参数,也就是callback函数对应的BIO

2.       参数-oper:设置BIO将要执行的操作,有些操作,callback函数将被调用两次,一次实在实际操作之前,一次实在实际操作之后,在后面的调用的时候,一般是将operBIO_CB_RETURN相或操作后作为参数的。也就是说,后一次调用的时候oper参数应该使用oper|BIO_CB_RETURN

3.       参数-argpargiargl:这些参数根据oper定义的操作的不同而不一样,是在相应操作中要用到的参数。

4.       参数-retvalue:这是默认的callback函数返回值,也就是说,如果没有提供BIO没有提供相应的callback函数,那么就会返回这个值。真正的返回值是callback函数本身提供的。如果在实际的操作之前调用callback函数,并且这时候retvalue参数设置为1,如果callback的函数返回值无效,那么对callback函数的调用就会导致程序立刻返回,BIO的操作就不会执行。

一般情况下,callback函数在执行完后都应该返回retvalue的值,除非该操作有特别的目的要修改这个返回值。下面简单列出我们比较熟悉的一些跟callback函数相关的BIO函数使用callback函数的情况:

1.       BIO_free(b):在执行该操作之前,调用了callback(b, BIO_CB_FREE, NULL, 0L, 0L, 1L)

2.       BIO_read(b,out,outl):在执行该操作之前,调用了callback(b, BIO_CB_READ, out, outl, 0L, 1L),之后调用了callback(b, BIO_CB_READ|BIO_CB_RETURN, out, outl, 0L,retvalue),大家可以看到,这就是我们上面说明过的情况,即两次调用callback的操作,后面一次oper的参数需要或上BIO_CB_RETURN

3.       BIO_write(b,in,inl):在执行该操作之前,调用了callback(b, BIO_CB_WRITE, in, inl, 0L, 1L),之后调用了callback(b, BIO_CB_WRITE|BIO_CB_RETURN, in, inl, 0L, retvalue)

4.       BIO_gets(b,out,outl):在执行该操作之前,调用了callback(b, BIO_CB_GETS, out, outl, 0L, 1L),之后调用了callback(b, BIO_CB_GETS|BIO_CB_RETURN, out, outl, 0L, retvalue)

5.       BIO_puts(b, in):在执行该操作之前,调用了callback(b, BIO_CB_WRITE, in, 0, 0L, 1L),之后调用了callback(b, BIO_CB_WRITE|BIO_CB_RETURN, in, 0, 0L,retvalue)

6.       BIO_ctrl(BIO *b, int cmd, long larg, void *parg):在执行该操作之前,调用了callback(b,BIO_CB_CTRL,parg,cmd,larg,1L),之后调用了callback(b,BIO_CB_CTRL|BIO_CB_RETURN,parg,cmd, larg,ret)

5.2 BIO_set_callbackBIO_get_callback

这两个函数用于设置和返回BIO中的callback函数,它们都是宏定义,根据前面的叙述我们已经知道,callback函数在许多高层的操作中都使用了,因为它能用于调试跟踪的目的或更改BIO的操作,具有很大的灵活性,所以这两个函数也就有用武之地了。

5.3 BIO_set_callback_argIO_get_callback_arg

顾名思义,这两个函数用了设置和得到callback函数中的参数。

5.4 BIO_debug_callback

这是一个标准的调试信息输出函数,它把相关BIO执行的所有操作信息都打印输出到制定的地方。如果callback参数没有指定输出这些信息的BIO口,那么就会默认使用stderr作为信息输出端口。

6.BIOIO操作函数

这些函数是BIO的基本读写操作函数,包括四个,他们的定义如下(openssl/bio.h):

int BIO_read(BIO *b, void *buf, int len);

int BIO_gets(BIO *b,char *buf, int size);

int BIO_write(BIO *b, const void *buf, int len);

int BIO_puts(BIO *b,const char *buf);

6.1 BIO_read

BIO接口中读出指定数量字节len的数据并存储到buf中。成功就返回真正读出的数据的长度,失败返回0或-1,如果该BIO没有实现本函数则返回-2

6.2 BIO_gets

该函数从BIO中读取一行长度最大为size的数据。通常情况下,该函数会以最大长度限制读取一行数据,但是也有例外,比如digest型的BIO,该函数会计算并返回整个digest信息。此外,有些BIO可能不支持这个函数。成功就返回真正读出的数据的长度,失败返回0或-1,如果该BIO没有实现本函数则返回-2。需要注意的时,如果相应的BIO不支持这个函数,那么对该函数的调用可能导致BIO链自动增加一个buffer型的BIO

6.3 BIO_write

BIO中写入长度为len的数据。成功就返回真正写入的数据的长度,失败返回0或-1,如果该BIO没有实现本函数则返回-2

6.4 BIO_puts

BIO中写入一个以NULL为结束符的字符串,成功就返回真正写入的数据的长度,失败返回0或-1,如果该BIO没有实现本函数则返回-2

需要注意的是,返回指为0或-1的时候并不一定就是发生了错误。在非阻塞型的source/sink型或其它一些特定类型的BIO中,这仅仅代表目前没有数据可以读取,需要稍后再进行该操作。

有时候,你可能会使用了阻塞类型的sokect使用的一些系统调用技术(如selectpollequivalent)来决定BIO中是否有有效的数据被read函数读取,但建议不要在阻塞型的接口中使用这些技术,因为这样的情况下如果调用BIO_read就会导致在底层的IO中多次调用read函数,从而导致端口阻塞。建议select(或equivalent)应该和非阻塞型的IO一起使用,可以在失败之后能够重新读取该IO,而不是阻塞住了。

关于BIOIO操作为什么会失败以及怎么处理这些情况请参加BIO_should_retry()函数的说明文档。

7.BIO链的操作

BIO结构其实是一个链式结构,单个BIO是只有一个环节的BIO链的特例,那么我们怎么构造或在一个BIO链中增加一个BIO,怎么从一个BIO链中删除一个BIO呢,本节就是专门讲述这个问题的。

Openssl中,针对BIO链的操作还是很简单的,仅仅包括两个函数(openssl/bio.h):

BIO * BIO_push(BIO *b,BIO *append);

BIO * BIO_pop(BIO *b);

7.1 BIO_push

该函数把参数中名为appendBIO附加到名为bBIO上,并返回b。其实,openssl作者本身也认识到,BIO_push的函数名字可能会导致误会,因为BIO_push函数其实只是将两个BIO连接起来,而不是Push的功能,应该是join才对。

我们举几个简单的例子说明BIO_push的作用,假设md1md2digest类型的BIOb64Base64类型的BIO,而ffile类型的BIO,那么如果执行操作

BIO_push(b64, f);

那么就会形成一个b64-f的链。然后再执行下面的操作:

BIO_push(md2, b64);

BIO_push(md1, md2);

那么就会形成md1-md2-b64-fBIO链,大家可以看到,在构造完一个BIO后,头一个BIO就代表了整个BIO链,这根链表的概念几乎是一样的。这时候,任何写往md1的数据都会经过md1md2的摘要,然后经过base64编码,最后写入文件f。可以看到,构造一条好的BIO链后,操作是非常方便的,你不用再关心具体的事情了,整个BIO链会自动将数据进行指定操作的系列处理。

需要注意的是,如果是读操作,那么数据会从相反的方向传递和处理,对于上面的BIO链,数据会从f文件读出,然后经过base64解码,然后经过md1md2编码,最后读出。

7.2 BIO_pop

该函数把名为bBIO从一个BIO链中移除并返回下一个BIO,如果没有下一个BIO,那么就返回NULL。被移除的BIO就成为一个单个的BIO,跟原来的BIO链就没有关系了,这样你可以把它释放或连接到另一个BIO上去。可以看到,如果是单个BIO的时候,该操作是没有任何意义的。如果你执行操作

BIO_pop(md2);

那么返回值将为b64,而md2从上述的链中移除,形成一个新的md1-b64-fBIO链,对于数据操作来说,还是往md1读写,没有什么变化,但是底层处理过程已经发生变化了,这就是封装与透明的概念。可以看到,虽然BIO_pop参数只是一个BIO,但该操作直接的后果会对该BIO所在的链产生影响,所以,当BIO所在的链不一样的时候,其结果是不一样的。

此外:BIO_pushBIO_pop操作还可能导致其它一些附加的结果,一些相关的BIO可能会调用一些控制操作,这些具体的细节因为各个类型的BIO不一样,在他们各自的说明中会有说明。

8.读写出错控制

BIO_readBIO_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_readBIO_write操作之后被调用的。

8.1 BIO_should_retry

如果读写出错的情况是要求程序稍后重试,那么该函数返回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)来设置等待时间。

8.2 BIO_should_read

该函数返回true如果导致IO操作失败的原因是BIO此时要读数据。

8.3 BIO_should_write

该函数返回true如果导致IO操作失败的原因是BIO此时要写数据。

8.4 BIO_should_io_special

该函数返回true如果导致IO操作失败的原因是特殊的(也就是读写之外的原因)

8.5 BIO_get_retry_reason

返回失败的原因,其代码包括BIO_FLAGS_READBIO_FLAGS_WRITEBIO_FLAGS_IO_SPECIAL。目前的BIO类型只返回其中之一。如果输入的BIO是产生特殊出错情况的BIO,那么该函数返回错误的原因代码,就跟BIO_get_retry_BIO()返回的reason一样。

8.6 BIO_get_retry_BIO

该函数给出特殊情况错误的简短原因,它返回出错的BIO,如果reason不是设置为NULL,它会包含错误代码,错误码的含义以及下一步应该采取的处理措施应该根据发生这种情况下各种BIO的类型而定。

9.BIO对的创建和应用

BIO对是BIO中专门创建的一对缓存BIO,要创建BIO对,调用下面定义的函数(openssl\bio.h):

int BIO_new_bio_pair(BIO **bio1, size_t writebuf1, BIO **bio2, size_t writebuf2);

这个函数调用成功后返回1,这时候bio1bio2都是有效的了;否则就返回0,而bio1bio2就会设为NULL,这是后可以检测出错堆栈以得到更多错误信息。

这个BIO对创建之后,它的两端都能作为数据缓冲的输入和输出。典型的应用是它一端和SSLIO连接,而另一端则被应用控制,这样,应用程序就不需要直接和网络连接打交道了。

这两个BIO对的功能是完全对称的,它们的缓冲区的大小由参数writebuf1writebuf2决定,如果给定的大小是0,那么该函数就会使用缺省的缓存大小。BIO_new_bio_pair不会检查bio1bio2是否真的指向其它BIObio1bio2的值都被重写,但是在此之前不会调用BIO_free()函数。所以,在使用bio1bio2之前,必须自己保证这两个变量是空的BIO,否则可能造成内存泄漏。

值得注意的是,虽然这两个BIO是一对的和一起创建的,但是却必须分别释放。之所以这样做,是有其重要原因的,因为有些SSL函数,如SSL_set_bioBIO_free会隐含调用BIO_free函数,所以这时候另一端的BIO就只能单独释放了。

下面举一个简单的例子说明问题。

BIO对能给提供应用程序中对网络处理的完全控制能力,程序可以对根据需要调用soketselect()函数,同时却可以避免直接处理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操作了,否则就会造成死锁现象,因为另一端可能知道等到有数据了才会继续进行下面的操作。

10.BIO链的定位操作

这里讲的是在一个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类型一一进行介绍,现在大家只要有一个概念就可以了。

10.1 BIO_find_type

该函数在给定的BIO链中根据特定的BIO类型bio_type进行搜索,搜索的起始位置就是b。如果给定的类型是一个特定的实现类型,那么就会搜索一个给类型的BIO;如果只是一个总体的类型定义,如BIO_TYPE_SOURCE_SINK(就是sourc/sink类型的BIO),那么属于这种类型的最先找到的BIO就是符合条件的。在找到符合的BIO后,BIO_find_type返回该BIO,否则返回NULL。需要注意的是,如果你使用的0.9.5a以前版本,如果给输入参数b赋值为NULL,可能引起异常错误!

10.2 BIO_next

该函数顾名思义,是返回当前BIO所在的BIO链中的下一个BIO,所以,它可以用来遍历整个BIO链,并且可以跟BIO_find_type函数结合起来,在整个BIO链中找出所有特定类型的BIO。这个函数是在0.9.6版本新加的,以前的版本要使用这个功能,只能使用bio->next_bio来定位了。

10.3 BIO_method_type

该函数返回给定的BIO的类型。

下面给出一个在一个BIO链中找出所有digest类型BIO的例子:

BIO *btmp;

btmp = in_bio; /* in_bio 是被搜索的BIO */

 

do {

btmp = BIO_find_type(btmp, BIO_TYPE_MD);

if(btmp == NULL) break; /* 如果没有找到*/

/* btmp 是一个digest类型的BIO,做些你需要做的处理 ...*/

……

btmp = BIO_next(btmp);

} while(btmp);

到此为止,就已经基本写完了BIO的基础知识方面的东西,下面的文章将开始对每一个具体的BIO类型进行介绍。

11.文件(file)类型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)

下面逐一介绍它们的作用和用法。

11.1 BIO_s_file

经过前面的介绍,大家应该对这种类型的函数结构很熟悉了,他们就是生成BIO类型的基本构造函数,BIO_s_file返回file类型的BIOfile类型的BIO封装了一个标准的文件结构,它是一种source/sinkBIOfile类型的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_readBIO_write对底层的file数据流进行读写操作,file类型BIO是支持BIO_getsBIO_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函数。

11.2 BIO_new_file

该函数根据给定的mode类型创建了一个文件BIOmode参数的函数跟fopen函数中mode参数的含义是一样的。返回的BIO设置了BIO_CLOSE标志。调用成功返回一个BIO,否则返回NULL。事实上,该函数先调用了fopen函数打开一个文件,然后调用BIO_new函数创建一个file类型BIO,最后调用函数BIO_set_fp函数给BIO结构跟相关的file帮定。

11.3 BIO_new_fp

用文件描述符创建一个file类型BIO,参数Flags可以为BIO_CLOSE,BIO_NOCLOSE(关闭标志)以及BIO_FP_TEXT(将文件设置为文本模式,默认的是二进制模式,该选项只对Win32平台有效)。事实上,该函数调用BIO_new函数创建一个file类型BIO,然后调用函数BIO_set_fp函数给BIO结构跟相关的file绑定。需要注意的是,如果下层封装的是stdout,stdinstderr,他们因为跟一般的是不关闭的,所以应该设置BIO_NOCLOSE标志。调用成功返回一个BIO,否则返回NULL

11.4 BIO_set_fp

该函数将BIO跟文件描述符fp帮定在一起,其参数flags的含义跟BIO_new_fp是一样的。该函数是一个宏定义函数。调用成功返回1,否则返回0,不过目前的实现是从来不会出现失败情况的。

11.5 BIO_get_fp

该函数返回file类型BIO中文件描述符,也是一个宏定义。调用成功返回1,否则返回0,不过目前的实现是从来不会出现失败情况的。

11.6 BIO_tell

返回位置指针的值。是一个宏定义函数。

11.7 BIO_read_filenameBIO_write_filenameBIO_append_filenameBIO_rw_filename

这四个函数分别设置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());

if(bio_out == NULL) /* 出错*/

if(!BIO_set_fp(bio_out, stdout, BIO_NOCLOSE)) /* 出错则将文件流定向到标准输出*/

BIO_printf(bio_out, "Hello World\n");

3.写文件操作

BIO *out;

out = BIO_new_file("filename.txt", "w");

if(!out) /*出错 */

BIO_printf(out, "Hello World\n");

BIO_free(out);

4.上述例子的另一种实现方法

BIO *out;

out = BIO_new(BIO_s_file());

if(out == NULL) /* Error ... */

if(!BIO_write_filename(out, "filename.txt")) /* Error ... */

BIO_printf(out, "Hello World\n");

BIO_free(out);

12.文件描述符(fd)类型BIO

文件描述符类型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里面找。

12.1 BIO_s_fd

该函数返回一个文件描述符类型的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_readBIO_write对底层的文件描述符结构进行读写操作。这两个函数的一些行为取决于他们所在的平台的文件描述符的读写函数的行为,如果底层的文件描述符是非阻塞型的,那么他们基本上是跟我们前面介绍过得BIOIO操作函数一样的。请参看前面的文章和资料。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

12.2 BIO_set_fd

该函数将BIO的底层文件描述符设置为fd,关闭标志也同时做了设置,其含义与文件类型BIO相应的含义一样。返回1

12.3 BIO_get_fd

返回相应BIO的底层文件描述符,存于参数c,不过,同时也作为返回值返回。c应该为int *类型的指针。如果BIO没有初始化,调用该函数将失败,返回-1

12.4 BIO_new_fd

创建并返回一个底层描述符为fd,关闭标志为close_flag的文件描述符类型的BIO。其实,该函数依次调用了BIO_s_fdBIO_newBIO_set_fd完成了该功能。该函数如果调用失败返回NULL

下面是一个简单的例子:

BIO *out;

out = BIO_new_fd(fileno(stdout), BIO_NOCLOSE);

BIO_printf(out, "Hello World\n");

BIO_free(out);

13.Socket类型BIO

Socket类型的BIO也是一种source/sinkBIO,封装了SocketIO操作,它相关的一些函数定义如下(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实现的函数基本是相同的,并且具有很多的共性。

13.1 BIO_s_socket

该函数返回一个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_readBIO_write对底层的Socket结构进行读写操作。

BIO_puts是支持的,但是BIO_getsSocket类型BIO中是不支持的,大家如果看源代码就可以知道,虽然BIO_getsSocket类型是不支持的,但是如果调用该函数,不会出现异常,只会返回-1的出错信息。

如果设置了关闭标志,那么当BIO被释放的时候底层的Socket连接就会被关闭。

13.2 BIO_set_fd

该函数将Socket描述符fd设置为BIO的底层IO结构,同时可以设置关闭标志c。该函数返回1

13.3 BIO_get_fd

该函数返回指定BIOSocket描述符,如果c参数不是NULL,那么就将该描述符存在参数c里面,当然,Socket描述符同时也作为返回值,如果BIO没有初始化则调用失败,返回-1

13.4 BIO_new_socket

该函数根据给定的参数返回一个socket类型的BIO,成功返回该BIO指针,失败返回NULL。其实,该函数依次调用了BIO_s_socketBIO_newBIO_set_fd实现它的功能。

14.source/sinkBIO

这是一个空的source/sinkBIO,写到这个BIO的数据都被丢掉了,从这里执行读操作也总是返回EOF。该BIO非常简单,其相关函数的定义如下(openssl\bio.h)

BIO_METHOD * BIO_s_null(void);

其相关的源文件实现函数在bss_null.c里面。

14.1 BIO_s_null

该函数返回一个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实现了不少的函数,但是,仔细看看源文件,就会发现所有这些函数都只是简单返回01或者输入数据的长度,而不作任何事情。熟悉Linux系统的技术人员可能知道,这跟Linux系统的/dev/null设备的行为是一样的。

一般来说,在openssl里面,这种类型的BIO是置放在BIO链的末尾的,比如在应用程序中,如果你要将一些数据通过filter型的BIO digest进行摘要算法,但不需要把它送往任何地方,又因为一个BIO链要求以source/sinkBIO开始或结束,所以这时候就可以在BIO链的末尾添加一个source/sink型的NUll类型BIO来实现这个功能。

15.mem类型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);

内存型BIOsource/sinkBIO,它使用内存作为它的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就可以再访问)。

15.1 BIO_s_mem

该函数返回一个内存型的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_writeBIO_read函数是支持的。对内存型BIO执行写操作总是成功的,因为内存型BIO的内存能够无限扩大。任何一个对可读写的内存型BIO的读操作都会在使用内部拷贝操作从BIO里面删除该段数据,这样一来,如果BIO里面有大量的数据,而读的却只是很小的一些片断,那么会导致操作非常慢。使用只读的内存型BIO避免了这个问题。在使用的时候,如果内存型BIO必须使用可读写的,那么可以加一个BufferBIOBIO链中,这可以使操作速度更快。在以后的版本(该文档所述版本为0.9.6a,可能会优化速度操作的问题。

BIO_getsBIO_puts操作在该类型BIO是支持的。

如果设置了BIO_CLOSE标志,那么内存型BIO被释放的时候其底层的BUF_MEMBIO也同时被释放。

BIO_reset函数被调用时,如果该BIO是可读写的,那么该BIO所有数据都会被清空;如果该BIO是只读的,那么该操作只会简单将指针指向原始位置,里面的数据可以再读。该文档所述版本的(0.9.6a)的内存型BIO对读写模式的BIO没有提供一个可以将指针重置但不破坏原有数据的方法,在以后的版本可能会增加的。

BIO_eof返回true,表明只时候BIO里面没有可读数据。

BIO_ctrl_pending返回目前BIO里面存储的数据的字节(byte)数。

15.2 BIO_set_mem_eof_return

该函数设置一个没有数据的内存型BIO的执行读动作的行为。如果参数v0,那么该空的内存型BIO就会返回EOF,也就是说,这时候返回为0,如果调用BIO_should_retry就会返回false。如果参数v为非零,那么就会返回v,并且同时会设置重试标志,也就是说此时调用BIO_read_retry会返回true。为了跟正常的返回正值避免混淆,v应该设置为负值,典型来说是-1

15.3 BIO_get_mem_data

该函数是一个宏定义函数,它将参数pp的指针指向内存型BIO的数据开始处,返回所有有效的数据。

15.4 BIO_set_mem_buf

该函数将参数bm所代表的BUF_MEM结构作为该BIO的底层结构,并把关闭标志设置为参数cc可以是BIO_CLOSEBIO_NOCLOSE,该函数也是一个宏定义。

15.5 BIO_get_mem_ptr

该函数也是一个宏定义函数,它将底层的BUF_MEM结构放在指针pp中。

15.6 BIO_new_mem_buf

该函数创建一个内存型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);

16.BIO(pair)类型BIO

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结构中参数peerptr成员)相互设置为对方,从而形成一种对称的结构,如下:

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/SSLI/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_readBIO_write操作成功执行并将数据写入到写缓冲区中。BIO_read调用开始会失败,BIO_should_retry返回true。如果此时对写缓冲区不执行flush操作,那么BIO_read调用永远不会成功,因为底层传输通道会一直等待直到数据有效(但数据却在写缓冲区里,没有传到底层通道)。

16.1 BIO_s_bio

该函数返回一个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_pendingBIO_ctrl_wpending函数可以用来查看在读或写缓冲区里面有效的数据的数量。

BIO_reset函数将写缓冲区里面的数据清除。

16.2 BIO_make_bio_pair

该函数将两个单独的BIO连接起来成为一个BIO对。

16.3 BIO_destroy_pair

该函数跟上面的函数相反,它将两个连接起来的BIO对拆开;如果一个BIO对中的任何一个BIO被释放,该操作会自动执行。

16.4 BIO_shutdown_wr

该函数关闭BIO对的其中一个BIO,一个BIO被关闭后,针对该BIO的任何写操作都会返回错误。从另一个BIO读数据的时候要么返回剩余的有效数据,要么返回EOF

16.5 BIO_set_write_buf_size

该函数设置BIO的缓冲区大小。如果该BIO的缓存区大小没有初始化,那么就会使用默认的值,大小为17k,这对于一个TLS记录来说是足够大的了。

16.6 BIO_get_write_buf_size

该函数返回写缓冲区的大小。

16.7 BIO_new_bio_pair

该函数我们在前面的《BIO系列之9---BIO对的创建和应用》中已经做了详细的介绍,其实,它是调用了BIO_new,BIO_make_bio_pairBIO_set_write_buf_size函数来创建一对BIO对的。如果两个缓冲区长度的参数都为零,那么就会使用默认的缓冲区长度。

16.8 BIO_get_write_guaranteeBIO_ctrl_get_write_guarantee

这两个函数返回当前能够写入BIO的数据的最大长度。如果往BIO写入的数据长度比该函数返回的数据长度大,那么BIO_write返回的写入数据长度会小于要求写入的数据,如果缓冲区已经满了,则会发出一个重试的请求。这两个函数的唯一不同之处是一个使用函数实现的,一个是使用宏定义实现的。

16.9 BIO_get_read_requestBIO_ctrl_get_read_request

这两个函数返回要求发送的数据的长度,这通常是在对该BIO对的另一个BIO执行读操作时因为缓冲区数据为空导致失败时发出的请求。所以,这通常用来表明现在应该写入多少数据才能使接下来的读操作能够成功执行,这在TLS/SSL应用程序中是非常有用的,因为对于这个协议来说,读取的数据长度比缓冲区的数据长度通常要有意义的多。如果在读操作成功之后调用这两个函数会返回0,如果在调用该函数之前有新的数据写入(不管是部分还是全部满足需要读取的数据的要求),那么调用该函数也会返回0。理所当然,该函数返回的数据长度肯定不会大于BIO_get_write_guarantee函数返回的数据长度。

16.10 BIO_ctrl_reset_read_request

该函数就是把BIO_get_read_request要返回值设置为0

17.连接(connect)类型BIO

该类型的BIO封装了socketConnect方法,它使得编程的时候可以使用统一的BIO规则进行socketconnect连接的操作和数据的发送接受,而不用关心具体平台的Socketconnect方法的区别。其相关定义的一些函数如下(openssl\bio.h)

BIO_METHOD * BIO_s_connect(void);

#define BIO_set_conn_hostname(b, name) BIO_ctrl(b, BIO_C_SET_CONNECT, 0, (char *) name)

#define BIO_set_conn_port(b,port) BIO_ctrl(b,BIO_C_SET_CONNECT,1,(char *)port)

#define BIO_set_conn_ip(b,ip) BIO_ctrl(b,BIO_C_SET_CONNECT,2,(char *)ip)

#define BIO_set_conn_int_port(b,port) BIO_ctrl(b,BIO_C_SET_CONNECT,3,(char *)port)

#define BIO_get_conn_hostname(b) BIO_ptr_ctrl(b,BIO_C_GET_CONNECT,0)

#define BIO_get_conn_port(b) BIO_ptr_ctrl(b,BIO_C_GET_CONNECT,1)

#define BIO_get_conn_ip(b,ip) BIO_ptr_ctrl(b,BIO_C_SET_CONNECT,2)

#define BIO_get_conn_int_port(b,port) BIO_int_ctrl(b,BIO_C_SET_CONNECT,3,port)

#define BIO_set_nbio(b,n) BIO_ctrl(b,BIO_C_SET_NBIO,(n),NULL)

#define BIO_do_connect(b) BIO_do_handshake(b)

BIO *BIO_new_connect(char *str)

17.1 BIO_s_connect

该函数返回一个connect类型的BIO_METHOD结构,该结构定义如下:

static BIO_METHOD methods_connectp=

{

BIO_TYPE_CONNECT,

"socket connect",

conn_write,

conn_read,

conn_puts,

NULL, /* connect_gets, */

conn_ctrl,

conn_new,

conn_free,

conn_callback_ctrl,

};

事实上,为了维护一个Socket结构,openssl里面还定义了一个BIO_CONNECT结构来维护底层socket的地址信息以及状态信息,不过,通过封装,我们一般是不用直接接触该结构的,在此也就不再多做介绍,感兴趣可以参看文件bss_conn.c里面的定义和函数。

BIO_readBIO_write的操作调用底层的连接的IO操作来完成。如果在服务器地址和端口设置正确,但连接没有建立的时候调用读写操作函数,那么会先进行连接的建立操作,然后再进行读写操作。

BIO_puts操作是支持的,但是BIO_gets操作不支持,这在该类型BIOBIO_METHOD结构定义中就可以看出来。

如果关闭标志设置了,那么在BIO被释放的时候,任何活动的连接和socket都会被关闭。

BIO_reset方法被调用的时候,连接(connect)类型的BIO的任何活动连接都会被关闭,从而回到可以重新跟同样的主机建立连接的状态。

BIO_get_fd函数返回连接类型的BIO的底层socket,当参数c不是NULL的时候,就将该socket赋值给c,当然,socket也作为返回值。c参数应该为int*类型。如果BIO没有初始化,则返回-1

17.2 BIO_set_conn_hostname

该函数使用字符串设置主机名,该主机名也可以为IP地址的形式,还可以包括端口号,如hostname:porthostname/any/other/pathhostname:port/any/other/path也是可以的。返回1

17.3 BIO_set_conn_port

该函数设置主机的端口号。该端口号的形式可以为数字的形式,也可以为字符串类似"http"的形式。如果使用字符串形式,首先会使用getservbyname函数搜索其相关的端口,如果没有搜索到,那么就会使用一张缺省的名字端口解释表,目前该表列出的字符串有:http, telnet, socks, https, ssl, ftp, gopher  wais.返回1

需要注意的是:如果端口名已经作为主机名的一部分设置了,那么它就会覆盖BIO_set_conn_port函数设置的端口值。有的时候(如有些应用可能不希望用固定的端口连接)可能不方便,这时候可以通过检测输入主机名的字符串中的":"字符,报错或截取字符串来避免这种情况。

17.4 BIO_set_conn_ip

该函数使用二进制的模式设置IP地址。返回1

17.5 BIO_set_conn_int_port

该函数以整数形式设置主机端口号,参数应该为int*的形式。返回1

17.6 BIO_get_conn_hostname

该函数返回连接类型BIO的主机名,如果BIO以及初始化,但是没有设置主机名,那么返回NULL。返回值因为是一个内部指针,所有不能更改它的值。

17.7 BIO_get_conn_port

该函数返回字符串类型的端口信息。如果没有设置,就返回NULL

17.8 BIO_get_conn_ip

该函数返回二进制形式的IP地址。如果没有设置,返回为全0

17.9 BIO_get_conn_int_port

该函数返回整数形式的端口号,如果没有设置,则返回0

上述四个函数的返回值在连接操作完成之后会被更新。而在此之前,返回值都是应用程序自己设置的。

17.10 BIO_set_nbio

设置I/O的非阻塞标志。如果参数n0,则I/O设置为阻塞模式;如果n1,则I/O设置为非阻塞模式。缺省的模式是阻塞模式。应该在连接建立之前调用本函数,因为非阻塞模式的I/O是在连接过程中设置的。返回值恒为1

注意:如果是阻塞模式的I/O,执行IO操作时(如读写),如果返回负值,说明就产生了错误的情况,如果返回值是0,一般来说表明连接已经关闭。如果设置为非阻塞模式,那么发出重试的请求就是很正常的事情了。

17.11 BIO_do_connect

该函数进行给定BIO的连接操作,如果连接成功,返回1,否则返回0或负值。在非阻塞模式的时候,如果调用失败了,可以调用BIO_should_retry函数以决定是否需要重试。

一般来说,应用程序不需要调用本函数,只有在希望将连接过程跟其它IO处理过程独立开来的时候,才需要调用本函数。

在初始化连接的过程的时候,如果返回值失败的原因为BIO_RR_CONNECT,调用BIO_should_io_special返回值可能也为true。如果出现这种情况,说明连接过程被阻塞住了,应用程序应该使用正常的方法进行处理,直到底层的socket连接上了再重试。

17.12 BIO_new_connect

该函数创建并返回一个连接类型的BIO,其实,它调用了BIO_s_connectBIO_new已经BIO_set_conn_hostname函数完成了整个操作。成功则返回一个BIO,否则返回NULL

例子:这是一个连接到本地Web服务器的例子,返回一页的信息并把该信息复制到标准输出设备。

BIO *cbio, *out;

int len;

char tmpbuf[1024];

ERR_load_crypto_strings();

cbio = BIO_new_connect("localhost:http");

out = BIO_new_fp(stdout, BIO_NOCLOSE);

if(BIO_do_connect(cbio) <= 0)

{

fprintf(stderr, "Error connecting to server\n");

ERR_print_errors_fp(stderr);

/* whatever ... */

}

BIO_puts(cbio, "GET / HTTP/1.0\n\n");

for(;;)

{

len = BIO_read(cbio, tmpbuf, 1024);

if(len <= 0) break;

BIO_write(out, tmpbuf, len);

}

BIO_free(cbio);

BIO_free(out);

18.接受(accept)类型BIO

接受(accept)类型的BIO跟连接(connect)类型BIO是相对应的,它封装了Socketaccept方法及其相关的一些操作,使得能够对不同的平台使用同一的函数进行操作。其定义的相关函数如下(openssl\bio.h)

BIO_METHOD * BIO_s_accept(void);

#define BIO_set_accept_port(b,name) BIO_ctrl(b,BIO_C_SET_ACCEPT,0,(char *)name)

#define BIO_get_accept_port(b) BIO_ptr_ctrl(b,BIO_C_GET_ACCEPT,0)

BIO *BIO_new_accept(char *host_port);

#define BIO_set_nbio_accept(b,n) BIO_ctrl(b,BIO_C_SET_ACCEPT,1,(n)?"a":NULL)

#define BIO_set_accept_bios(b,bio) BIO_ctrl(b,BIO_C_SET_ACCEPT,2,(char *)bio)

#define BIO_set_bind_mode(b,mode) BIO_ctrl(b,BIO_C_SET_BIND_MODE,mode,NULL)

#define BIO_get_bind_mode(b,mode) BIO_ctrl(b,BIO_C_GET_BIND_MODE,0,NULL)

#define BIO_BIND_NORMAL 0

#define BIO_BIND_REUSEADDR_IF_UNUSED 1

#define BIO_BIND_REUSEADDR 2

#define BIO_do_accept(b) BIO_do_handshake(b)

18.1 BIO_s_accept

该函数返回一个接受(Accept)类型的BIO_METHOD结构,其定义如下:

static BIO_METHOD methods_acceptp=

{

BIO_TYPE_ACCEPT,

"socket accept",

acpt_write,

acpt_read,

acpt_puts,

NULL, /* connect_gets, */

acpt_ctrl,

acpt_new,

acpt_free,

NULL,

};

通过该结构我们可以一目了然的知道该方法支持什么I/O操作,在本类型中,BIO_gets的操作是不支持的。跟连接(connect)类型BIO一样,openssl也定义了一个维护接受Socket信息的结构,在此我也不再详细介绍该结构,大家参考bss_acpt.c文件。

本类型的BIO对各种平台的TCP/IPaccept做了封装,所以在使用的时候就可以同一的使用BIO的规则进行操作,而不用担心因为不同的平台带来程序改写或增加移植的工作量,这也是BIO很重要的一个目的。

BIO_readBIO_write函数操作调用了底层平台连接的I/O相关操作,如果这时候没有连接建立,端口设置正确,那么该BIO就会等待连接的建立。事实上,当一个连接建立的时候,一个新的socket类型BIO就会被创建并附加到BIO链中,形成accept->socketBIO结构,所以这时候对初始化了的接受socket进行IO操作就会导致它处于等待连接建立的状态。当一个接受类型的BIOBIO链的末尾的时候,在处理I/O调用之前它会先等待一个连接的建立;如果不是在末尾,那么它简单的把I/O调用传到下一个BIO

如果接受(accept)类型BIO的关闭标志设置了,那么当BIO被释放的时候,该BIO链上任何活动的连接和socket都会被关闭。

BIO_get_fdBIO_set_fd可以用来取得和设置该连接的socket描述符,详细情况参看“12.描述符(fd) 类型BIO”。

18.2 BIO_set_accept_port

该函数使用字串名来设置接受端口。其形式为“host:port”,“host”是要使用的接口,“port”是端口。这两部分都可以为“*”,这时候表示可以使用任意接口和端口。这里的端口的字符串含义跟连接(connect)类型BIO里面定义的一样,可以为数字形式的,可以使用getservbyname函数去匹配得到,还可以使用字符串的表,请参看连接型BIO说明的相关部分。

18.3 BIO_new_accept

该函数将BIO_newBIO_set_accept_port函数放在一个函数里面调用,创建一个新的接受(accept)类型的BIO

18.4 BIO_set_nbio_accept

该函数设置接受socket是否为阻塞模式(缺省),如果参数n0,为阻塞模式,n1则为非阻塞模式。

18.5 BIO_set_accept_bios

该函数用来设置一个BIO链,当接受到一个连接的时候,这个设置好的BIO链就会被复制或附加到整个BIO链上来。有的时候这中处理方法是非常好的,比如,如果每个连接都需要一个缓冲区或SSL类型的BIO,这时候使用本函数就省了很多麻烦了。需要注意的是,在调用本函数后,这个设置的链上的BIO不能自己释放,在接受(acceptBIO被释放后,它们会自动释放。

当该函数调用的时候,其设置的BIO链位于接受BIOsocket类型的BIO之间,即形成:accept->otherbios->socket的新的BIO链。

18.6 BIO_set_bind_modeBIO_get_bind_mode

这两个函数用来设置和取得目前的绑定模式。如果设置为BIO_BIND_NORMAL(缺省),那么另外一个socket就不能绑定到同一个端口。如果设置为BIO_BIND_REUSEADDR,那么另外的socket可以绑定到同一个端口。如果设置为BIO_BIND_REUSEADDR_IF_UNUSED,那么首先会尝试以BIO_BIND_NORMAL的模式绑定到端口,如果失败了而且端口没有使用,那么就会使用BIO_BIND_REUSEADDR模式绑定到端口。

18.7 BIO_do_accept

该函数有两个功能,当它在接受(acceptBIO设置好之后第一被调用的时候,它会创建一个接受socket并把它跟地址绑定;第二次被调用的时候,它会等待连接的到来。

注意:如果服务器端希望可以处理多个连接的情况(通常都是这样的),那么接受BIO必须能够用来处理以后的连接,这时候可以这样做:等待到一个连接后,调用

connection=BIO_pop(accept)

这样,connection会包含一个最近建立的连接的BIOaccept就再次成为一个独立的BIO,可以用来处理其它连接了。如果没有其它连接建立,那么就可以使用BIO_free释放该BIO

当然,如果只有一个连接需要处理,也可以使用连接BIO本身来进行I/O操作。但是一般来说不推荐这样做,因为这时候该接受BIO依然处于接受其它连接建立的状态。这可以使用上述的方法解决。

例子:这个例子在4444端口接受了两个连接,发送信息到每个连接上然后释放他们。

BIO *abio, *cbio, *cbio2;

ERR_load_crypto_strings();

abio = BIO_new_accept("4444");

/* 首先调用BIO_accept启动接受BIO */

if(BIO_do_accept(abio) <= 0)

{

fprintf(stderr, "Error setting up accept\n");

ERR_print_errors_fp(stderr);

exit(0);

}

/* 等待连接建立*/

if(BIO_do_accept(abio) <= 0)

{

fprintf(stderr, "Error accepting connection\n");

ERR_print_errors_fp(stderr);

exit(0);

}

fprintf(stderr, "Connection 1 established\n");

/* 返回连接的BIO*/

cbio = BIO_pop(abio);

BIO_puts(cbio, "Connection 1: Sending out Data on initial connection\n");

fprintf(stderr, "Sent out data on connection 1\n");

/* 等待另一个连接的建立 */

if(BIO_do_accept(abio) <= 0)

{

fprintf(stderr, "Error accepting connection\n");

ERR_print_errors_fp(stderr);

exit(0);

}

fprintf(stderr, "Connection 2 established\n");

/* 关闭连接BIO,不再接受连接的建立 */

cbio2 = BIO_pop(abio);

BIO_free(abio);

BIO_puts(cbio2, "Connection 2: Sending out Data on second\n");

fprintf(stderr, "Sent out data on connection 2\n");

BIO_puts(cbio, "Connection 1: Second connection established\n");

/* 关闭这两个连接 */

BIO_free(cbio);

BIO_free(cbio2);

19.Fileter类型的NULLBIO

前面我们已经介绍完source/sink型的BIO了,以后的BIO系列文章将开始介绍过滤(filter)类型的BIO。那么首先介绍的是一个非常简单的BIO类型——NULLfilter BIO,其定义如下(openssl\bio.h):

BIO_METHOD * BIO_f_null(void);

在本类型中,只有这个函数是定义了的,该函数返回一个NULL型的过滤BIO_METHOD结构,NULL过滤型BIO是一个不作任何事情的BIO。针对该类型BIO的任何调用都会被简单传递中BIO链中的下一个BIO中去,也就相当于该BIO是不存在的一样。所以,一般来说,该类型的BIO用处不大。

20.缓冲(buffer)类型BIO

缓冲(buffer)类型BIO是一种过滤(filter)型的BIO,其相关的一些函数定义如下(openssl\bio.h):

BIO_METHOD * BIO_f_buffer(void);

#define BIO_get_buffer_num_lines(b) BIO_ctrl(b,BIO_C_GET_BUFF_NUM_LINES,0,NULL)

#define BIO_set_read_buffer_size(b,size) BIO_int_ctrl(b,BIO_C_SET_BUFF_SIZE,size,0)

#define BIO_set_write_buffer_size(b,size) BIO_int_ctrl(b,BIO_C_SET_BUFF_SIZE,size,1)

#define BIO_set_buffer_size(b,size) BIO_ctrl(b,BIO_C_SET_BUFF_SIZE,size,NULL)

#define BIO_set_buffer_read_data(b,buf,num) BIO_ctrl(b,BIO_C_SET_BUFF_READ_DATA,num,buf)

20.1 BIO_f_buffer

该函数返回一个Buffer类型的BIO_METHOD结构,该结构定义如下(bf_buff.c):

static BIO_METHOD methods_buffer=

{

BIO_TYPE_BUFFER,

"buffer",

buffer_write,

buffer_read,

buffer_puts,

buffer_gets,

buffer_ctrl,

buffer_new,

buffer_free,

buffer_callback_ctrl,

};

由结构定义可见,该类型BIO支持所有BIOI/O函数。写入缓冲(buffer)BIO的数据存储在缓冲区里面,定期写入到BIO链的下一个BIO中,事实上,只有缓冲区已满或者调用了BIO_flush函数时,数据才会写入下面的BIO,所以,当任何存储在缓冲区的数据需要写入的时候(如在使用BIO_pop函数从BIO链中删除一个buffer类型BIO之前),必须使用BIO_flush函数,如果BIO链的末尾是一个非阻塞型的BIO,有时候调用BIO_flush可能出现失败,需要重试的情况。从该类型BIO读取数据时,数据从下一个BIO填充到该BIO的内部缓冲区中,然后再读出来。该类型BIO支持BIO_getsBIO_puts方法,事实上,BIO_gets函数是通过在下一个BIOBIO_read函数来实现的,所以,如果一个BIO不支持BIO_gets方法(如SSL类型的BIO),可以通过预先附加一个buffer类型BIO来实现BIO_gets的功能。

BIO_reset被调用的时候,该类型BIO里面的所有数据都会被清空。

20.2 BIO_get_buffer_num_lines

返回缓冲区中目前数据的的行数。

20.3 BIO_set_read_buffer_sizeBIO_set_write_buffer_sizeBIO_set_buffer_size

这三个函数分别设置缓冲类型BIO的读、写或者读写缓冲区的大小。初始的缓冲区大小由宏定义DEFAULT_BUFFER_SIZE决定,默认的是1024。如果设置的缓冲区大小小于DEFAULT_BUFFER_SIZE,那么就会被忽略,也就是说缓冲区大小会保持为DEFAULT_BUFFER_SIZE所定义的大小。当重新设置缓冲区大小时,里面的数据会全部被清空。成功执行返回1,否则返回0

20.4 BIO_set_buffer_read_data

该函数清空缓冲区原有的数据,并使用numbuf中的数据填充该缓冲区,如果num的大小大于目前的缓冲区设定大小,那么缓冲区就会自动扩大。成功设置返回1,否则返回0

21.Base64类型BIO

该类型为过滤(filter)类型BIO,其定义如下(openssl\bio.h,openssl\evp.h):

BIO_METHOD * BIO_f_base64(void);

21.1 BIO_f_base64

该函数返回一个Base64类型的BIO_METHOD结构,该结构定义如下(evp\bio_b64.c):

static BIO_METHOD methods_b64=

{

BIO_TYPE_BASE64,

"base64 encoding",

b64_write,

b64_read,

NULL, /* b64_puts, */

NULL, /* b64_gets, */

b64_ctrl,

b64_new,

b64_free,

b64_callback_ctrl,

};

应该注意的是,该类型的BIO其定义并不在bio目录下,而是在evp目录下。

当往该BIO写入数据时,数据被Base64编码,当从该BIO读数据时,数据被Base64解码。该BIO不支持BIO_getsBIO_puts的功能。

BIO_flush在该类型BIO被调用的时候,表示需要写入的数据已经写完,用来把最后的一段数据写入到BIO里面去。

21.2 BIO_set_flags

该函数可以用来设置标记BIO_FLAGS_BASE64_NO_NL,该标记设置后,将把所有数据编码成为一行或者说期望所有数据都在一行上。需要注意的是,由于base64编码本身格式的原因,不能准确可靠的决定编码后的数据块的结束位置,大家使用的时候自己需要注意数据的长度问题。

例子:

1、下面的程序将字符串"Hello World\n"进行base64编码并写入到标准输出设备。

BIO *bio, *b64;

char message[] = "Hello World \n";

 

b64 = BIO_new(BIO_f_base64());

bio = BIO_new_fp(stdout, BIO_NOCLOSE);

bio = BIO_push(b64, bio);

BIO_write(bio, message, strlen(message));

BIO_flush(bio);

 

BIO_free_all(bio);

2、下面的程序将base64编码的数据从标准输入设备读出并将解码数据输出到标准输出设备:

BIO *bio, *b64, bio_out;

char inbuf[512];

int inlen;

char message[] = "Hello World \n";

 

b64 = BIO_new(BIO_f_base64());

bio = BIO_new_fp(stdin, BIO_NOCLOSE);

bio_out = BIO_new_fp(stdout, BIO_NOCLOSE);

bio = BIO_push(b64, bio);

while((inlen = BIO_read(bio, inbuf, strlen(message))) > 0)

BIO_write(bio_out, inbuf, inlen);

BIO_free_all(bio);

22.Cipher类型BIO

该类型为过滤(filter)类型BIO,其定义如下(openssl\bio.h,openssl\evp.h):

BIO_METHOD * BIO_f_cipher(void);

void BIO_set_cipher(BIO *b,const EVP_CIPHER *cipher,unsigned char *key, unsigned char *iv, int enc);

int BIO_get_cipher_status(BIO *b)

int BIO_get_cipher_ctx(BIO *b, EVP_CIPHER_CTX **pctx)

22.1 BIO_f_cipher

该函数返回cipher类型的BIO_METHOD结构,其结构定义如下(evp\bio_enc.c):

static BIO_METHOD methods_enc=

{

BIO_TYPE_CIPHER,"cipher",

enc_write,

enc_read,

NULL, /* enc_puts, */

NULL, /* enc_gets, */

enc_ctrl,

enc_new,

enc_free,

enc_callback_ctrl,

};

该类型的BIO将写入该BIO的数据加密,从该BIO读数据时数据被解密,它事实上封装了EVP_CipherInitEVP_CipherUpdateEVP_CipherFinal三种方法。它不支持BIO_putsBIO_gets的方法,如果要使用这两个方法,可以通过在前面附加一个buffer类型的BIO来实现,这在前面我们介绍过。

base64BIO相似,当调用BIO_flush函数时,表明所有数据都已经通过该类型BIO加密了,用来将最后的一段数据通过该BIO进行加密。在进行加密的时候,必须调用BIO_flush函数来把最后的数据通过BIO进行加密,否则最后的数据会在解密的时候出现失败的情况。当从一个加密类型的BIO读取数据时,当读到最后一段数据时,会通过检测EOF自动检测到数据结束标志并自动将这段数据解密。

22.2 BIO_set_cipher

该函数设置该BIO的加密算法,数据使用参数key为加密密钥,参数iv作为加密的IV(初始化向量)。如果enc设置为1,则为加密,enc设置为0,则为解密。该函数不返回值。

22.3 BIO_get_cipher_status

该函数是一个BIO_ctrl的宏,用来检测解密是否成功执行。因为在解密的时候(执行读操作的时候),如果最后一段数据发生错误,会返回0,而遇到EOF成功完成操作后也会返回0,所以必须调用本函数确定解密操作是否成功执行了。解密成功返回1,否则返回0

22.4 BIO_get_cipher_ctx

该函数也是BIO_ctrl的一个宏定义函数,它返回BIO的内部加密体制。返回的加密体制可以使用标准的加密规则进行设置。这在BIO_set_cipher函数的灵活性不能适应应用程序的需要的时候是很有用的。该函数总是返回1

23.MD类型BIO

该类型为过滤(filter)类型BIO,其定义如下(openssl\bio.h,openssl\evp.h):

BIO_METHOD * BIO_f_md(void);

int BIO_set_md(BIO *b,EVP_MD *md);

int BIO_get_md(BIO *b,EVP_MD **mdp);

int BIO_get_md_ctx(BIO *b,EVP_MD_CTX **mdcp);

Cipher类型一样,该类型的一些定义和实现文件是在evp\bio_md.c里面,而不是在bio目录下。大家要看源文件,请参看这个文件。

23.1 BIO_f_md

该函数返回一个MD类型的BIO_METHOD结构,其定义如下:

static BIO_METHOD methods_md=

{

BIO_TYPE_MD,"message digest",

md_write,

md_read,

NULL, /* md_puts, */

md_gets,

md_ctrl,

md_new,

md_free,

md_callback_ctrl,

};

MD类型BIO对通过它的任何数据都进行摘要操作(digest),事实上,该类型BIO封装了EVP_DigestInitEVP_DigestUpdateEVP_DigestFinal三个函数的功能和行为。该类型BIO是完全对称的,也就是说,不管是读数据(BIO_read)还是写数据(BIO_write),都进行相同的摘要操作。

BIO_gets函数执行的时候,如果给定的size参数足够大,可以完成摘要(digest)计算,那么就会返回摘要值。BIO_puts函数是不支持的,如果需要支持该函数,可以在前面附加一个buffer类型的BIO

BIO_reset函数重新初始化一个摘要类型的BIO,事实上,它是简单重新调用了EVP_DigestInit函数进行初始化。

注意,在从一个摘要BIO里面读取完摘要信息之后,在重新使用该BIO之前,必须调用BIO_resetBIO_set_md重新初始化该BIO才行。

23.2 BIO_set_md

该函数是一个BIO_ctrl函数的宏定义函数,它使用参数md设置给定BIO的摘要算法。该函数必须在执行读写操作之前调用,用来初始化一个摘要类型的BIO。调用成功返回1,否则返回0

23.3 BIO_get_md

该函数也是BIO_ctrl函数一个宏定义。它返回BIO摘要方法的指针到mdp参数里面。调用成功返回1,否则返回0

23.4 BIO_get_md_ctx

该函数返回摘要BIO的方法结构到mdcp参数里面。该结构可以作为参数使用在EVP_DigestFinalEVP_SignFinalEVP_VerifyFinal函数里,这增加了灵活性。因为该函数返回的结构是一个BIO内部的结构,所以对该结构的任何改变操作都会影响到相应的BIO,并且如果该BIO释放了,该结构指针也就无效了。调用成功返回1,否则返回0

例子

1、下列的例子创建一个包含SHA1MD5类型摘要BIOBIO链,并将数据"Hello World"通过它们进行摘要操作。

BIO *bio, *mdtmp;

char message[] = "Hello World";

bio = BIO_new(BIO_s_null());

mdtmp = BIO_new(BIO_f_md());

BIO_set_md(mdtmp, EVP_sha1());

 

// 使用BIO_pushBIO链前面增加一个sink类型的BIO,作为BIO链开始的标志

bio = BIO_push(mdtmp, bio);

mdtmp = BIO_new(BIO_f_md());

BIO_set_md(mdtmp, EVP_md5());

bio = BIO_push(mdtmp, bio);

/* 注意,现在mdtmp变量已经没有用了*/

BIO_write(bio, message, strlen(message));//因为最后一个BIOnull型的BIO,所以数据实际上已经自动被丢弃了。

2、下面的例子演示了从摘要类型BIO读数据的过程:

BIO *bio, *mdtmp;

char buf[1024];

int rdlen;

bio = BIO_new_file(file, "rb");

mdtmp = BIO_new(BIO_f_md());

BIO_set_md(mdtmp, EVP_sha1());

bio = BIO_push(mdtmp, bio);

mdtmp = BIO_new(BIO_f_md());

BIO_set_md(mdtmp, EVP_md5());

bio = BIO_push(mdtmp, bio);

do

{

rdlen = BIO_read(bio, buf, sizeof(buf));

/* 可以在这里面加入处理数据的代码 */

} while(rdlen > 0);

3、下面的例子从一个BIO链中读取摘要数据并输出。可以跟上面的例子一起使用。

BIO *mdtmp;

unsigned char mdbuf[EVP_MAX_MD_SIZE];

int mdlen;

int i;

mdtmp = bio; /* 这里假设BIO已经设置好了*/

do

{

EVP_MD *md;

mdtmp = BIO_find_type(mdtmp, BIO_TYPE_MD);

if(!mdtmp) break;

BIO_get_md(mdtmp, &md);

printf("%s digest", OBJ_nid2sn(EVP_MD_type(md)));

mdlen = BIO_gets(mdtmp, mdbuf, EVP_MAX_MD_SIZE);

for(i = 0; i < mdlen; i++) printf(":%02X", mdbuf[i]);

printf("\n");

mdtmp = BIO_next(mdtmp);

} while(mdtmp);

BIO_free_all(bio);

24.SSL类型的BIO

从名字就可以看出,这是一个非常重要的BIO类型,它封装了openssl里面的ssl规则和函数,相当于提供了一个使用SSL很好的有效工具,一个很好的助手。其定义(openssl\bio.h,openssl\ssl.h)如下:

BIO_METHOD *BIO_f_ssl(void);

#define BIO_set_ssl(b,ssl,c) BIO_ctrl(b,BIO_C_SET_SSL,c,(char *)ssl)

#define BIO_get_ssl(b,sslp) BIO_ctrl(b,BIO_C_GET_SSL,0,(char *)sslp)

#define BIO_set_ssl_mode(b,client) BIO_ctrl(b,BIO_C_SSL_MODE,client,NULL)

#define BIO_set_ssl_renegotiate_bytes(b,num) BIO_ctrl(b,BIO_C_SET_SSL_RENEGOTIATE_BYTES,num,NULL);

#define BIO_set_ssl_renegotiate_timeout(b,seconds) BIO_ctrl(b,BIO_C_SET_SSL_RENEGOTIATE_TIMEOUT,seconds,NULL);

#define BIO_get_num_renegotiates(b) BIO_ctrl(b,BIO_C_SET_SSL_NUM_RENEGOTIATES,0,NULL);

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);

int BIO_ssl_copy_session_id(BIO *to,BIO *from);

void BIO_ssl_shutdown(BIO *bio);

#define BIO_do_handshake(b) BIO_ctrl(b,BIO_C_DO_STATE_MACHINE,0,NULL)

该类型BIO的实现文件在ssl\bio_ssl.c里面,大家可以参看这个文件得到详细的函数实现信息。

24.1 BIO_f_ssl

该函数返回一个SSL类型的BIO_METHOD结构,其定义如下:

static BIO_METHOD methods_sslp=

{

BIO_TYPE_SSL,"ssl",

ssl_write,

ssl_read,

ssl_puts,

NULL, /* ssl_gets, */

ssl_ctrl,

ssl_new,

ssl_free,

ssl_callback_ctrl,

};

可见,SSL类型BIO不支持BIO_gets的功能。

BIO_readBIO_write函数调用的时候,SSL类型的BIO会使用SSL协议进行底层的I/O操作。如果此时SSL连接并没有建立,那么就会在调用第一个IO函数的时候先进行连接的建立。

如果使用BIO_push将一个BIO附加到一个SSL类型的BIO上,那么SSL类型的BIO读写数据的时候,它会被自动调用。

BIO_reset调用的时候,会调用SSL_shutdown函数关闭目前所有处于连接状态的SSL,然后再对下一个BIO调用BIO_reset,这功能一般就是将底层的传输连接断开。调用完成之后,SSL类型的BIO就处于初始的接受或连接状态。

如果设置了BIO关闭标志,那么SSL类型BIO释放的时候,内部的SSL结构也会被SSL_free函数释放。

24.2 BIO_set_ssl

该函数设置SSL类型BIO的内部ssl指针指向ssl,同时使用参数c设置了关闭标志。

24.3 BIO_get_ssl

该函数返回SSL类型BIO的内部的SSL结构指针,得到该指针后,可以使用标志的SSL函数对它进行操作。

24.4 BIO_set_ssl_mode

该函数设置SSL的工作模式,如果参数client1,那么SSL工作模式为客户端模式,如果client0,那么SSL工作模式为服务器模式。

24.5 BIO_set_ssl_renegotiate_bytes

该函数设置需要重新进行session协商的读写数据的长度为num。当设置完成后,在没读写的数据一共到达num字节后,SSL连接就会自动重新进行session协商,这可以加强SSL连接的安全性。参数num最少为512字节。

24.6 BIO_set_ssl_renegotiate_timeout

该函数跟上述函数一样都是为了加强SSL连接的安全性的。不同的是,该函数采用的参数是时间。该函数设置重新进行session协商的时间,其单位是秒。当SSL session连接建立的时间到达其设置的时间时,连接就会自动重新进行session协商。

24.7 BIO_get_num_renegotiates

该函数返回SSL连接在因为字节限制或时间限制导致session重新协商之前总共读写的数据长度。

24.8 BIO_new_ssl

该函数使用ctx参数所代表的SSL_CTX结构创建一个SSL类型的BIO,如果参数client为非零值,就使用客户端模式。

24.9 BIO_new_ssl_connect

该函数创建一个包含SSL类型BIO的新BIO链,并在后面附加了一个连接类型的BIO。方便而且有趣的是,因为在filter类型的BIO里,如果是该BIO不知道(没有实现)BIO_ctrl操作,它会自动把该操作传到下一个BIO进行调用,所以我们可以在调用本函数得到BIO上直接调用BIO_set_host函数来设置服务器名字和端口,而不需要先找到连接BIO

24.10 BIO_new_buffer_ssl_connect

创建一个包含buffer型的BIO,一个SSL类型的BIO以及一个连接类型的BIO

24.11 BIO_ssl_copy_session_id

该函数将BIOfromSSL Session ID拷贝到BIOto中。事实上,它是通过查找到两个BIO链中的SSL类型BIO,然后调用SSL_copy_session_id来完成操作的。

24.12 BIO_ssl_shutdown

该函数关闭一个BIO链中的SSL连接。事实上,该函数通过查找到该BIO链中的SSL类型BIO,然后调用SSL_shutdown函数关闭其内部的SSL指针。

24.13 BIO_do_handshake

该函数在相关的BIO上启动SSL握手过程并建立SSL连接。连接成功建立返回1,否则返回0或负值,如果连接BIO是非阻塞型的BIO,此时可以调用BIO_should_retry函数以决定释放需要重试。如果调用该函数的时候SSL连接已经建立了,那么该函数不会做任何事情。一般情况下,应用程序不需要直接调用本函数,除非你希望将握手过程跟其它IO操作分离开来。

需要注意的是,如果底层是阻塞型(openssl帮助文档写的是非阻塞型,non blocking,但是根据上下文意思已经BIO的其它性质,我个人认为是阻塞型,blocking才是正确的)的BIO,在一些意外的情况SSL类型BIO下也会发出意外的重试请求,如在执行BIO_read操作的时候如果启动了session重新协商的过程就会发生这种情况。在0.9.6和以后的版本,可以通过SSL的标志SSL_AUTO_RETRY将该类行为禁止,这样设置之后,使用阻塞型传输的SSL类型BIO就永远不会发出重试的请求。

例子

1.一个SSL/TLS客户端的例子,完成从一个SSL/TLS服务器返回一个页面的功能。其中IO操作的方法跟连接类型BIO里面的例子是相同的。

BIO *sbio, *out;

int len;

char tmpbuf[1024];

SSL_CTX *ctx;

SSL *ssl;

ERR_load_crypto_strings();

ERR_load_SSL_strings();

OpenSSL_add_all_algorithms();

//如果系统平台不支持自动进行随机数种子的设置,这里应该进行设置(seed PRNG)

ctx = SSL_CTX_new(SSLv23_client_method());

//通常应该在这里设置一些验证路径和模式等,因为这里没有设置,所以该例子可以跟使用任意CA签发证书的任意服务器建立连接

sbio = BIO_new_ssl_connect(ctx);

BIO_get_ssl(sbio, &ssl);

if(!ssl)

{fprintf(stderr, "Can't locate SSL pointer\n");}

/* 不需要任何重试请求*/

SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);

//这里你可以添加对SSL的其它一些设置

BIO_set_conn_hostname(sbio, "localhost:https");

out = BIO_new_fp(stdout, BIO_NOCLOSE);

if(BIO_do_connect(sbio) <= 0)

{

fprintf(stderr, "Error connecting to server\n");

ERR_print_errors_fp(stderr);

}

if(BIO_do_handshake(sbio) <= 0)

{

fprintf(stderr, "Error establishing SSL connection\n");

ERR_print_errors_fp(stderr);

}

/* 这里可以添加检测SSL连接的代码,得到一些连接信息*/

BIO_puts(sbio, "GET / HTTP/1.0\n\n");

for(;;)

{

len = BIO_read(sbio, tmpbuf, 1024);

if(len <= 0) break;

BIO_write(out, tmpbuf, len);

}

BIO_free_all(sbio);

BIO_free(out);

2.一个简单的服务器的例子。它使用了buffer类型的BIO,从而可以使用BIO_gets从一个SSL类型的BIO读取数据。它创建了一个包含客户端请求的随机web页,并把请求信息输出到标准输出设备。

BIO *sbio, *bbio, *acpt, *out;

int len;

char tmpbuf[1024];

SSL_CTX *ctx;

SSL *ssl;

ERR_load_crypto_strings();

ERR_load_SSL_strings();

OpenSSL_add_all_algorithms();

//可能需要进行随机数的种子处理(seed PRNG

ctx = SSL_CTX_new(SSLv23_server_method());

if ( !SSL_CTX_use_certificate_file ( ctx, "server.pem", SSL_FILETYPE_PEM ) || !SSL_CTX_use_PrivateKey_file ( ctx, "server.pem", SSL_FILETYPE_PEM ) || !SSL_CTX_check_private_key(ctx))

{

fprintf(stderr, "Error setting up SSL_CTX\n");

ERR_print_errors_fp(stderr);

return 0;

}

//可以在这里设置验证路径,DHDSA算法的临时密钥回调函数等等

/* 创建一个新的服务器模式的SSL类型BIO*/

sbio=BIO_new_ssl(ctx,0);

BIO_get_ssl(sbio, &ssl);

if(!ssl)

{fprintf(stderr, "Can't locate SSL pointer\n");}

/* 不需要任何重试请求 */

SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);

/* 创建一个Buffer类型BIO */

bbio = BIO_new(BIO_f_buffer());

/* 加到BIO链上*/

sbio = BIO_push(bbio, sbio);

acpt=BIO_new_accept("4433");

/*当一个新连接建立的时候,我们可以将sbio链自动插入到连接所在的BIO链中去。这时候,这个BIO(sbio)就被accept类型BIO吞并了,并且当accept类型BIO释放的时候,它会自动被释放。*/

BIO_set_accept_bios(acpt,sbio);

out = BIO_new_fp(stdout, BIO_NOCLOSE);

/* 设置 accept BIO */

if(BIO_do_accept(acpt) <= 0)

{

fprintf(stderr, "Error setting up accept BIO\n");

ERR_print_errors_fp(stderr);

return 0;

}

/* 等待连接的建立 */

if(BIO_do_accept(acpt) <= 0)

{

fprintf(stderr, "Error in connection\n");

ERR_print_errors_fp(stderr);

return 0;

}

/*因为我们只想处理一个连接,所以可以删除和释放 accept BIO*/

sbio = BIO_pop(acpt);

BIO_free_all(acpt);

if(BIO_do_handshake(sbio) <= 0)

{

fprintf(stderr, "Error in SSL handshake\n");

ERR_print_errors_fp(stderr);

return 0;

}

BIO_puts(sbio, "HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n");

BIO_puts(sbio, "<pre>\r\nConnection Established\r\nRequest headers:\r\n");

BIO_puts(sbio, "--------------------------------------------------\r\n");

for(;;)

{

len = BIO_gets(sbio, tmpbuf, 1024);

if(len <= 0) break;

BIO_write(sbio, tmpbuf, len);

BIO_write(out, tmpbuf, len);

/* 查找请求头的结束标准空白行*/

if((tmpbuf[0] == '\r') || (tmpbuf[0] == '\n')) break;

}

BIO_puts(sbio, "--------------------------------------------------\r\n");

BIO_puts(sbio, "</pre>\r\n");

/* 因为使用了buffer类型的BIO,我们最好调用BIO_flush函数 */

BIO_flush(sbio);

BIO_free_all(sbio);


  • 2
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值