版权声明:本文根据DragonKing牛,E-Mail:[email protected]发布在https://openssl.126.com的系列文章整理修改而成(这个网站已经不能访问了),我自己所做的工作主要是针对新的1.0.2版本进行验证,修改错别字,和错误,重新排版,以及整理分类,配图。 未经作者允许,严禁用于商业出版,否则追究法律责任。网络转载请注明出处,这是对原创者的起码的尊重!!!
其实包含了很多种接口,用通用的函数接口,主要控制在BIO_METHOD中的不同实现函数控制,包括6种filter型和8种source/sink型。
source/sink类型的BIO是数据源,例如,sokect BIO和文件BIO。 而filter BIO就是把数据从一个BIO转换到另外一个BIO或应用接口,在转换过程中,这些数据可以不修改(如信息摘要BIO),也可以进行转换。例如在加密BIO中,如果写操作,数据就会被加密,如果是读操作,数据就会被解密。
BIO是封装了许多类型I/O接口细节的一种应用接口,可以和SSL连接、非加密的网络连接以及文件IO进行透明的连接。
BIO可以连接在一起成为一个BIO链(单个的BIO就是一个环节的BIO链的特例),如下是BIO的结构定义,可以看到它有上下环节。
一个BIO链通常包括一个source BIO和一个或多个filter BIO,数据从第一个BIO读出或写入,然后经过一系列BIO变化到输出(通常是一个source/sink BIO)。
BIO目录文件的简要说明:
typedef struct bio_st BIO;
struct bio_st
{
BIO_METHOD *method; //BIO方法结构,是决定BIO类型和行为的重要参数,各种BIO的不同之处主要也正在于此项。
long (*callback)(struct bio_st *,int,const char *,int, long,long); //BIO回调函数
char *cb_arg; //回调函数的第一个参量
int init; //初始化标志,初始化了为1,否则为0。比如文件BIO 中,通过BIO_set_fp
//关联一个文件指针时,该标记则置1。
int shutdown; //BIO开关标志,如果为BIO_CLOSE,则释放BIO时自动释放持有的资源,否则不自动释放持有资源
int flags; //有些BIO 实现需要它来控制各个函数的行为。比如文件BIO 默认该值为BIO_FLAGS_UPLINK,
//这时文件读操作调用UP_fread 函数而不是调用fread 函数。
int retry_reason; //重试原因,主要用在socket 和ssl BIO 的异步阻塞。比如socketbio 中,遇到
//WSAEWOULDBLOCK 错误时,openssl 告诉用户的操作需要重试
int num; //该值因具体BIO 而异,比如socket BIO 中num 用来存放链接字。
void *ptr; //ptr:指针,具体bio 有不同含义。比如文件BIO中它用来存放文件句柄;mem bio 中它用来存放
//内存地址;connect bio 中它用来存放BIO_CONNECT 数据,acceptbio 中它
//用来存放BIO_ACCEPT数据。
struct bio_st *next_bio; //BIO链中下一个BIO 地址,BIO 数据可以从一个BIO 传送到另一个BIO。
struct bio_st *prev_bio; //BIO链中上一个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参数是以一个返回值为BIO_METHOD类型的函数提供的,如生成一个mem型的BIO结构,就使用下面的语句:
BIO *mem = BIO_new(BIO_s_mem());
这样的函数有以下一些:
// 【source/sink型】
BIO_METHOD* BIO_s_accept() //一个封装了类似TCP/IP socket Accept规则的接口,并且使TCP/IP操作对于BIO接口透明。
BIO_METHOD* BIO_s_connect() //一个封装了类似TCP/IP socket Connect规则的接口,并且使TCP/IP操作对于BIO接口透明
BIO_METHOD* BIO_s_socket() //封装了socket接口的BIO类型
BIO_METHOD* BIO_s_bio() //封装了一个BIO对,数据从其中一个BIO写入,从另外一个BIO读出
BIO_METHOD* BIO_s_fd() //是一个封装了文件描述符的BIO接口,提供类似文件读写操作的功能
BIO_METHOD* BIO_s_file() //封装了标准的文件接口的BIO,包括标准的输入输出设备如stdin等
BIO_METHOD* BIO_s_mem() //封装了内存操作的BIO接口,包括了对内存的读写操作
BIO_METHOD* BIO_s_null() //返回空的sink型BIO接口,写入这种接口的所有数据读被丢弃,读的时候总是返回EOF
//【filter型】
BIO_METHOD* BIO_f_base64() //封装了base64编码方法的BIO,写的时候进行编码,读的时候解码
BIO_METHOD* BIO_f_cipher() //封装了加解密方法的BIO,写的时候加密,读的时候解密
BIO_METHOD* BIO_f_md() //封装了信息摘要方法的BIO,通过该接口读写的数据都是已经经过摘要的。
BIO_METHOD* BIO_f_ssl() //封装了openssl 的SSL协议的BIO类型,也就是为SSL协议增加了一些BIO操作方法。
BIO_METHOD* BIO_f_null() //一个不作任何事情的BIO,对它的操作都简单传到下一个BIO去了,相当于不存在。
BIO_METHOD* BIO_f_buffer() //封装了缓冲区操作的BIO,写入该接口的数据一般是准备传入下一个BIO接口的,从该——
//接口读出的数据一般也是从另一个BIO传过来的。
上述各种类型的函数正是构成BIO强大功能的基本单元,所以,要了解BIO的各种结构和功能,也就应该了解这些函数类型相关的操作函数。
所有这些源文件,都基本上包含于/crypto/bio/目录下的同名.c文件(大部分是同名的)中。
在BIO_METHOD里面,定义了一组行为函数,上述不通类型的BIO_METHOD行为函数的定义是不同的,其结构如下(以非16位系统为例):
typedef struct bio_method_st
{
int type; //具体BIO 类型
const char *name; //具体BIO 的名字;
int (*bwrite)(BIO *, const char *, int); //具体BIO 二进制写操作回调函数
int (*bread)(BIO *, char *, int); //具体BIO 二进制写操作回调函数
int (*bputs)(BIO *, const char *); //具体BIO 中文本写回调函数;
int (*bgets)(BIO *, char *, int); //具体BIO 中文本读回调函数;
long (*ctrl)(BIO *, int, long, void *); //具体BIO 的控制回调函数;
int (*create)(BIO *); //生成具体BIO 回调函数;
int (*destroy)(BIO *); //销毁具体BIO 回调函数;
long (*callback_ctrl)(BIO *, int, bio_info_cb *);//具体BIO 控制回调函数,与ctrl 回调函数不一样,
//该函数可由调用者(而不是实现者)来实现,然后
//通过BIO_set_callback 等函数来设置。
} BIO_METHOD;
//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的成员中,callback也是比较重要的,它能够用于程序调试用或者自定义改变BIO的行为。详细会在以后相关的部分介绍。
BIO的很多操作,都是BIO_ctrl系列函数根据不通参数组成的宏定义来完成的。所以要了解BIO的行为,了解BIO_ctrl系列函数以及其各个参数的意义也是很重要的。
在BIO的基本操作系列函数中,用来BIO分配和释放操作,,其声明形式如下:
#include
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);
【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_*的形式。
【BIO_set】
该函数功能比较简单,就是对一个已经存在的BIO设置新的BIO_METHOD类型。其实就是简单的对BIO的各个成员进行初始化,并将参数type赋值给该BIO。其实,BIO_new函数在使用OPENSSL_malloc给BIO分配了内存之后,就简单调用了BIO_set函数进行初始化工作。所以一般来说,除非你要重新设置你已经存在的BIO,否则是不需要直接调用这个函数的。成功操作返回1,否则返回0。
【BIO_free】
该函数释放单个BIO的内存和资源,成功操作返回1,失败返回0。**BIO的操作不仅仅是释放BIO结构所占用的资源,也会释放其下层的I/O资源,比如关闭释放相关的文件符等,这对不同类型的BIO是不一样的,详细的请参看各种类型BIO本身的说明文件和源文件。需要注意的是,**BIO_free只释放当前的一个BIO,如果用来释放一个BIO链,就可能会导致内存泄漏,这种情况应该使用下述的BIO_free_all函数。
【BIO_vfree】
该函数功能与BIO_free完全相同,只是没有返回值。事实上,它简单调用了BIO_free函数,但不返回该函数的返回值,所以它的函数实现代码只有一个语句。
【BIO_free_all】
该函数释放这个BIO链,并且即使在这个过程中,如果释放其中一个BIO出错,释放过程也不会停止,会继续释放下面的BIO,这保证了尽量避免内存泄漏的出现。如果你非要调用这个函数释放单个的BIO,那么效果跟BIO_free是一样的。事实上,该函数只是简单的遍历整个BIO链,并调用BIO_free释放各个环节的BIO。
#include
//最底层BIO控制函数,理解原理
long BIO_ctrl(BIO *bp,int cmd,long larg,void *parg);
//上层BIO控制函数
char* BIO_ptr_ctrl(BIO *bp,int cmd,long larg);
long BIO_int_ctrl(BIO *bp,int cmd,long larg,int iarg);
//最上层BIO控制函数,需要掌握!!!
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控制函数的宏定义方式
#define BIO_reset(b) (int)BIO_ctrl(b,BIO_CTRL_RESET,0,NULL)
#define BIO_seek(b,ofs) (int)BIO_ctrl(b,BIO_C_FILE_SEEK,ofs,NULL)
#define BIO_tell(b) (int)BIO_ctrl(b,BIO_C_FILE_TELL,0,NULL)
#define BIO_flush(b) (int)BIO_ctrl(b,BIO_CTRL_FLUSH,0,NULL)
#define BIO_eof(b) (int)BIO_ctrl(b,BIO_CTRL_EOF,0,NULL)
#define BIO_set_close(b,c) (int)BIO_ctrl(b,BIO_CTRL_SET_CLOSE,(c),NULL)
#define BIO_get_close(b) (int)BIO_ctrl(b,BIO_CTRL_GET_CLOSE,0,NULL)
#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回调函数控制函数,理解原理
long BIO_callback_ctrl(BIO *b, int cmd,
void (*fp)(struct bio_st *, int, const char *, int, long, long));
//上层BIO回调函数控制函数,需要掌握!!!
#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函数本身的声明在BIO结构体中
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);
其实,在这些函数中,除了BIO_ctrl, BIO_callback_ctrl, BIO_ptr_ctrl, BIO_int_ctrl, BIO_ctrl_pending, BIO_ctrl_wpending是真正的函数外,其它都是宏定义,而且,在这些函数中,除了BIO_ctrl,BIO_callback_ctrl,其它基本上都是简单的BIO_ctrl输入不同的参数的调用。
【BIO_ctrl】
从上面的叙述可以知道,BIO_ctrl是整个控制函数中最基本的函数,它支持不同的命令输入,从而产生不同的功能,由此,它也就衍生了许多其它函数,作为一个比较地层的控制函数,一般来说用户并不需要直接调用它,因为在它之上已经使用宏定义和函数调用的形式建造了许多直接面向用户的函数。
filter型的BIO没有定义BIO_ctrl功能,如果对他们调用这个函数,他们就简单的把命令传到BIO链中的下一个BIO。也就是说,通常可以不用直接调用一个BIO的BIO_ctrl函数,只需要在它所在的BIO链上调用该函数,那么BIO链就会自动将该调用函数传到相应的BIO上去。这样可能就会导致一些意想不到的结果,比如,在目前的filter型BIO中没有实现BIO_seek()函数(大家待会就会明白BIO_seek就是BIO_ctrl的简单宏定义),但如果在这个BIO链上的末尾是一个文件或文件描述符型BIO,那么这个调用也会返回成功的结果。
对于source/sink型BIO来说,如果他们不认得BIO_ctrl所定义的操作,**那么就返回0*8。
【BIO_callback_ctrl】
这个函数是这组控制函数中唯一一个不是通过调用BIO_ctrl建立起来的,它有自己的实现函数,而且跟BIO_ctrl毫不相干。跟BIO_ctrl一样,它也是比较底层的控制函数,在它上面也定义了一些直接面向用户的控制函数,一般来说,用户不需要直接调用该函数。
需要说明的是,该函数和BIO_ctrl函数为了实现不同类型BIO具有不同的BIO_ctrl控制功能,他们的操作基本上都是由各个BIO的callback函数来定义的。这是不同的BIO能灵活实现不同功能的根本所在。
【BIO_ptr_ctrl和BIO_int_ctrl】
这两个函数都是简单的调用了BIO_ctrl函数,不同的是,后者是输入了四个参数并传入到BIO_ctrl函数中,简单返回了调用BIO_ctrl返回的返回值;而前者只输入了三个参数,最后一个BIO_ctrl参数是作为输出参数并作为返回值的。
【BIO_reset】
宏定义函数,BIO_reset函数只是简单的将BIO的状态设回到初始化的时候的状态,比如文件BIO,调用该函数就是将文件指针指向文件开始位置。一般来说,调用成功的时候该函数返回1,失败的时候返回0或-1;但是文件BIO是一个例外,成功调用的时候返回0,失败的时候返回-1。
【BIO_seek】
宏定义函数,该函数将文件相关的BIO(文件和文件描述符类型)的文件指针设置到距离开始位置ofs(输入参数)字节的位置上。调用成功的时候,返回文件的位置指针,否则返回-1;但是文件BIO例外,成功的时候返回0,失败的时候返回-1。
【BIO_tell】
宏定义函数,该函数返回了文件相关BIO的当前文件指针位置。跟BIO_seek一样,调用成功的时候,返回文件的位置指针,否则返回-1*;但是文件BIO例外,成功的时候返回0,失败的时候返回-1。
【BIO_flush】
宏定义函数,该函数用来将BIO内部缓冲区的数据都写出去,有些时候,也用于为了根据EOF查看是否还有数据可以写。调用成功的时候该函数返回1,失败的时候返回0或-1。之所以失败的时候返回0或者-1,是为了标志该操作是否需要稍后以跟BIO_write()相同的方式重试。这时候,应该调用BIO_should_retry()函数,当然,正常的情况下该函数的调用应该是失败的。
【BIO_eof】
宏定义函数,如果BIO**读到EOF**,该函数返回1,至于EOF的具体定义,根据BIO的类型各不相同。如果没有读到EOF,该函数返回0.
【BIO_set_close】
宏定义函数,该函数设置BIO的关闭标志,该标志可以为BIO_CLOSE或BIO_NOCLOSE。一般来说,该标志是为了指示在source/sink型BIO释放该BIO的时候是否关闭其下层的I/O流。该函数总是返回1。
【BIO_get_close】
宏定义函数,该函数读取BIO的关闭标志,返回BIO_CLOSE或BIO_NOCLOSE。
【BIO_pending、BIO_wpending、BIO_ctrl_pending和BIO_ctrl_wpending】
宏定义函数,这些函数都是用来得到BIO中读缓存或写缓存中字符的数目的,返回相应缓存中字符的数目。
前面两个函数也是BIO_ctrl的宏定义函数,后两个函数功能跟他们是一样的,只不过他们是通过调用BIO_ctrl函数实现的,而不是宏定义。此外,前面两个函数返回的是int型,而后面两个函数返回的是size_t型。
需要注意的是,BIO_pending和 BIO_wpending并不是在所有情况下都能很可靠地得到缓存数据的数量,比如在文件BIO中,有些数据可能在文件内部结构的缓存中是有效的,但是不可能简单的在BIO中得到这些数据的数量。而在有些类型BIO中,这两个函数可能还不支持。基于此,openssl作者本身也建议一般不要使用这两个函数,而是使用后面两个,除非你对你所做的操作非常清楚和了解。
【callback】
callback函数在BIO中非常重要,许多控制功能都是要通过callback函数协助完成的,比如BIO要执行释放的操作BIO_free,那么其实它是先调用callback函数设置下面的操作将是释放操作(控制码:BIO_CB_FREE),然后才调用别的相关函数执行真正的操作,在后面我们会列出这些控制功能函数,并简单说明callback函数是怎么在这些功能的实现中使用的。现在,我先简单介绍callback函数的各个参数:
下面简单列出我们比较熟悉的一些跟callback函数相关的BIO函数使用callback函数的情况:
BIO_free(b)
,在执行该操作之前,调用了callback(b, BIO_CB_FREE, NULL, 0L, 0L, 1L)
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。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)
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)
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)
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)
【BIO_set_callback和BIO_get_callback】
这两个函数用于设置和返回BIO中的callback函数,它们都是宏定义,根据前面的叙述我们已经知道,callback函数在许多高层的操作中都使用了,因为它能用于调试跟踪的目的或更改BIO的操作,具有很大的灵活性,所以这两个函数也就有用武之地了。
【BIO_set_callback_arg和IO_get_callback_arg】
顾名思义,这两个函数用来设置和得到callback函数中的参数。
【BIO_debug_callback】
这是一个标准的调试信息输出函数,它把相关BIO执行的所有操作信息都打印输出到制定的地方。如果callback参数没有指定输出这些信息的BIO口,那么就会默认使用stderr作为信息输出端口。
这些函数是BIO的基本读写操作函数,包括四个,他们的定义如下:
#include
int BIO_read(BIO *b, void *buf, int len);
int BIO_write(BIO *b, const void *buf, int len);
int BIO_gets(BIO *b,char *buf, int size);
int BIO_puts(BIO *b,const char *buf);
【BIO_read】
从BIO接口中读出len字节的数据到buf中。成功就返回真正读出的数据的长度,失败返回0或-1,如果该BIO**没有实现本函数则返回-2**。
【BIO_gets】
该函数从BIO中读取一行长度最大为size的数据。通常情况下,该函数会以最大长度限制读取一行数据,但是也有例外,比如digest型的BIO,该函数会计算并返回整个digest信息。此外,有些BIO可能不支持这个函数。成功返回真正读出的数据的长度,失败返回0或-1,如果该BIO**没有实现本函数则返回-2**。需要注意的时,如果相应的BIO不支持这个函数,那么对该函数的调用可能导致BIO链自动增加一个buffer型的BIO。
【BIO_write】
往BIO中写入长度为len的数据。成功返回真正写入的数据的长度,失败返回0或-1,如果该BIO**没有实现本函数则返回-2**。
【BIO_puts】
往BIO中写入一个以NULL为结束符的字符串,成功就返回真正写入的数据的长度,失败返回0或-1,如果该BIO**没有实现本函数则返回-2**。
需要注意的是,返回指为0或-1的时候并不一定就是发生了错误。在非阻塞型的source/sink型或其它一些特定类型的BIO中,这仅仅代表目前没有数据可以读取,需要稍后再进行该操作。
有时候,你可能会使用了阻塞类型的sokect使用的一些系统调用技术(如select,poll,epoll)来决定BIO中是否有有效的数据被read函数读取,但建议不要在阻塞型的接口中使用这些技术,因为这样的情况下如果调用BIO_read就会导致在底层的IO中多次调用read函数,从而导致端口阻塞。建议select(或epoll)应该和非阻塞型的IO一起使用,可以在失败之后能够重新读取该IO,而不是阻塞住了。
当BIO_read或BIO_write函数调用出错的时候,BIO本身提供了一组出错原因的诊断函数,他们定义如下:
#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操作之后被调用的。
【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函数(或epoll)来设置等待时间。
【BIO_should_read】
该函数返回true,如果导致IO操作失败的原因是BIO此时要读数据。
【BIO_should_write】
该函数返回true,如果导致IO操作失败的原因是BIO此时要写数据。
【BIO_should_io_special】
该函数返回true,如果导致IO操作失败的原因是特殊的(也就是读写之外的原因)
【BIO_get_retry_reason】
返回失败的原因,其代码包括BIO_FLAGS_READ, BIO_FLAGS_WRITE和BIO_FLAGS_IO_SPECIAL。目前的BIO类型只返回其中之一。如果输入的BIO是产生特殊出错情况的BIO,那么该函数返回错误的原因代码,就跟BIO_get_retry_BIO()返回的reason一样。
【BIO_get_retry_BIO】
该函数给出特殊情况错误的简短原因,它返回出错的BIO,如果reason不是设置为NULL,它会包含错误代码,错误码的含义以及下一步应该采取的处理措施应该根据发生这
种情况下各种BIO的类型而定。
BIO对是BIO中专门创建的一对缓存BIO,要创建BIO对,调用下面定义的函数:
#include
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对的应用模型有一个感性的认识,下面举一个简单的例子说明问题。
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链中增加一个BIO,怎么从一个BIO链中删除一个BIO呢,其实,在openssl中,针对BIO链的操作还是很简单的,仅仅包括两个函数(openssl/bio.h):
BIO * BIO_push(BIO *b,BIO *append);
BIO * BIO_pop(BIO *b);
【BIO_push】
该函数把参数中名为append的BIO附加到名为b的BIO上,并返回b。
【BIO_pop】
该函数把名为b的BIO从一个BIO链中移除并返回下一个BIO,如果没有下一个BIO,那么就返回NULL。被移除的BIO就成为一个单个的BIO,跟原来的BIO链就没有关系了,这样你可以把它释放或连接到另一个BIO上去。可以看到,如果是单个BIO的时候,该操作是没有任何意义的。
我们举几个简单的例子说明BIO_push和BIO_pop的作用,假设md1、md2是digest类型的BIO,b64是Base64类型的BIO,而f是file类型的BIO,那么如果执行操作
BIO_push(b64, f);
那么就会形成一个b64-f的链。然后再执行下面的操作:
BIO_push(md2, b64);
BIO_push(md1, md2);
那么就会形成md1-md2-b64-f的BIO链,大家可以看到,在构造完一个BIO后,头一个BIO就代表了整个BIO链,这根链表的概念几乎是一样的。
这时候,任何写往md1的数据都会经过md1,md2的摘要(或说hush运算),然后经过base64编码,最后写入文件f。可以看到,构造一条好的BIO链后,操作是非常方便的,你不用再关心具体的事情了,整个BIO链会自动将数据进行指定操作的系列处理。
需要注意的是,如果是读操作,那么数据会从相反的方向传递和处理,对于上面的BIO链,数据会从f文件读出,然后经过base64解码,然后经过md1,md2编码,最后读出。
如果你执行操作:
BIO_pop(md2);
那么返回值将为b64,而md2从上述的链中移除,形成一个新的md1-b64-f的BIO链,对于数据操作来说,还是往md1读写,没有什么变化,但是底层处理过程已经发生变化了,这就是封装与透明的概念。可以看到,虽然BIO_pop参数只是一个BIO,但该操作直接的后果会对该BIO所在的链产生影响,所以,当BIO所在的链不一样的时候,其结果是不一样的。
此外:BIO_push和BIO_pop操作还可能导致其它一些附加的结果,一些相关的BIO可能会调用一些控制操作,这些具体的细节因为各个类型的BIO不一样,在他们各自的说明中会有说明。
3.6讲过BIO链的构造方法,这里讲的是在一个BIO链中,怎么查找一个特定的BIO,怎么遍历BIO链中的每一个BIO,这组函数定义如下:
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_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,可能引起异常错误!
【BIO_next】
该函数顾名思义,是返回当前BIO所在的BIO链中的下一个BIO,所以,它可以用来遍历整个BIO链,并且可以跟BIO_find_type函数结合起来,在整个BIO链中找出所有特定类型的BIO。这个函数是在0.9.6版本新加的,以前的版本要使用这个功能,只能使用bio->next_bio来定位了。
【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);
文件(file)类型BIO的相关函数和定义如下(openssl\bio.h):
#include
BIO_METHOD* BIO_s_file(void); //返回文件BIO的BIO_METHOD定义
BIO* BIO_new_file(const char *filename, const char *mode); //从文件名新建BIO
BIO* BIO_new_fp(FILE *stream, int flags); //从文件指针新建BIO
BIO_set_fp(BIO *b,FILE *fp, int flags); //将bio与文件指针绑定
BIO_get_fp(BIO *b,FILE **fpp); //获取bio绑定的的文件指针
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_s_file】
该函数是生成文件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函数的(大家如果忘了这些函数的作用,请参考前面的3.3节)。
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函数。
【BIO_new_file】
该函数根据给定的mode类型创建了一个文件BIO,mode参数的函数跟fopen函数中mode参数的含义是一样的。返回的BIO**设置了BIO_CLOSE**标志。调用成功返回一个BIO,否则返回NULL。
事实上,该函数先调用了fopen函数打开一个文件,然后调用BIO_new函数创建一个file类型BIO,最后调用函数BIO_set_fp函数给BIO结构跟相关的file绑定。
【BIO_new_fp】
用文件描指针创建一个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_set_fp】
该函数将BIO跟文件指针fp绑定在一起,其参数flags的含义跟BIO_new_fp是一样的。该函数是一个宏定义函数。调用成功返回1,否则返回0,不过目前的实现是从来不会出现失败情况的。
【BIO_get_fp】
该函数返回file类型BIO中文件指针,也是一个宏定义。**调用成功返回1,否则返
回0**,不过目前的实现是从来不会出现失败情况的。
【BIO_tell】
返回位置指针的值。是一个宏定义函数。
【BIO_read_filename, BIO_write_filename, BIO_append_filename, BIO_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);
文件描述符类型BIO也是一个source/sink型的BIO,它定义了以下一些类型的函数
#include
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_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_read和BIO_write对底层的文件描述符结构进行读写操作。这两个函数的一些行为取决于他们所在的平台的文件描述符的读写函数的行为,如果底层的文件描述符是非阻塞型的,那么他们基本上是跟我们前面介绍过得BIO的IO操作函数一样的。请参看前面资料。socket是一类特殊的描述符,不应该使用文件描述符类型的BIO来封装它,而应该使用专门的socke类型BIO,在以后我们会进行介绍。
BIO_puts是支持的,但是BIO_gets在本类型描述符中是不支持的。
BIO_reset调用lseek(fd,0,0)函数,使文件指针指向开始的位置。调用成功返回0,失败返回-1。
BIO_seek调用了lseek(fd,ofs,0)函数,设置文件指针的位置到从文件头偏移ofs的位置,成功返回文件指针的位置,失败返回-1。
BIO_tell返回目前文件指针的位置,它其实调用了lseek(fd,0,1)函数,失败返回-1。
如果设置了关闭标志,那么当BIO被释放的时候底层的文件描述符就会被关闭。
【BIO_set_fd】
该函数将BIO的底层文件描述符设置为fd,关闭标志也同时做了设置,其含义与文件类型BIO相应的含义一样。返回1。
【BIO_get_fd】返回相应BIO的底层文件描述符,存于参数c,不过,同时也作为返回值返回。c应该为int *类型的指针。如果BIO没有初始化,调用该函数将失败,返回-1。
【BIO_new_fd】
创建并返回一个底层描述符为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操作,它相关的一
些函数定义如下:
#include
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实现的函数基本是相同的,并且具有很多的共性。
【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_read和BIO_write对底层的Socket结构进行读写操作。
BIO_puts是支持的,但是BIO_get**s在Socket类型BIO中是不支持**的,大家如果看源代码就可以知道,虽然BIO_gets在Socket类型是不支持的,但是如果调用该函数,不会出现异常,只会返回-1的出错信息。
如果设置了关闭标志,那么当BIO被释放的时候底层的Socket连接就会被关闭。
【BIO_set_fd】
该函数将Socket描述符fd设置为BIO的底层IO结构,同时可以设置关闭标志c。该函
数返回1。
【BIO_get_fd】
该函数返回指定BIO的Socket描述符,如果c参数不是NULL,那么就将该描述符存在
参数c里面,当然,Socket描述符同时也作为返回值,如果BIO没有初始化则调用失败,返回-1。
【BIO_new_socket】
该函数根据给定的参数返回一个socket类型的BIO,成功返回该BIO指针,失败返回NULL。其实,该函数依次调用了BIO_s_socket,BIO_new和BIO_set_fd实现它的功能。
这是一个空的source/sink型BIO,写到这个BIO的数据都被丢掉了,从这里执行读操作也总是返回EOF。该BIO非常简单,其相关函数的定义如下:
#include
BIO_METHOD * BIO_s_null(void);
其相关的源文件实现函数在bss_null.c里面。
【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实现了不少的函数,但是,仔细看看源文件,就会发现所有这些函数都只是简单返回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):
#include
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_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_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是只读的,那么该操作只会简单将指针指向原始位置,里面的数据可以再读。该文档所述版本的(0.9.6a)的内存型BIO对读写模式的BIO没有提供一个可以将指针重置但不破坏原有数据的方法,在以后的版本可能会增加的
BIO_eof返回true,表明只读时候BIO里面没有可读数据。
BIO_ctrl_pending返回目前BIO里面存储的数据的字节(byte)数。
【BIO_set_mem_eof_return】
该函数设置一个没有数据的内存型BIO的执行读动作的行为。如果参数v是0,那么该空的内存型BIO就会返回EOF,也就是说,这时候返回为0,如果调用BIO_should_retry就会返回false。如果参数v为非零,那么就会返回v,并且同时会设置重试标志,也就是说此时调用BIO_read_retry会返回true。为了跟正常的返回正值避免混淆,v应该设置为负值,典型来说是-1。
【BIO_get_mem_data】
该函数是一个宏定义函数,它将参数pp的指针指向内存型BIO的数据开始处,返回所有有效的数据。
【BIO_set_mem_buf】
该函数将参数bm所代表的BUF_MEM结构作为该BIO的底层结构,并把关闭标志设置为参数c,c可以是BIO_CLOSE或BIO_NOCLOSE,该函数也是一个宏定义。
【BIO_get_mem_ptr】
该函数也是一个宏定义函数,它将底层的BUF_MEM结构放在指针pp中。
【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);
前面我们已经介绍过BIO对的概念,其实更进一步,BIO对也是作为一种source/sink类型的BIO来处理的,也就是说,BIO里面还提供了一种专门的BIO_METHO方法来处理BIO对的各种操作。BIO对类型的BIO各种相关的函数定义如下:
#include
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_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_pending和BIO_ctrl_wpending函数可以用来查看在读或写缓冲区里面有效的数据的数量。
BIO_reset函数将写缓冲区里面的数据清除。
【BIO_make_bio_pair】
该函数将两个单独的BIO连接起来成为一个BIO对。
【BIO_destroy_pair】
该函数跟上面的函数相反,它将两个连接起来的BIO对拆开;如果一个BIO对中的任何一个BIO被释放,该操作会自动执行。
【BIO_shutdown_wr】
该函数关闭BIO对的其中一个BIO,一个BIO被关闭后,针对该BIO的任何写操作都会
返回错误。从另一个BIO读数据的时候要么返回剩余的有效数据,要么返回EOF。
【BIO_set_write_buf_size】
该函数设置BIO的缓冲区大小。如果该BIO的缓存区大小没有初始化,那么就会使用默认的值,大小为17k,这对于一个TLS记录来说是足够大的了。
【BIO_get_write_buf_size】
该函数返回写缓冲区的大小。
【BIO_new_bio_pair】
该函数我们在前面的《3.5 BIO对的创建和应用》中已经做了详细的介绍,其实,它是调用了BIO_new,BIO_make_bio_pair和BIO_set_write_buf_size函数来创建一对BIO对的。如果两个缓冲区长度的参数都为零,那么就会使用默认的缓冲区长度。
【BIO_get_write_guarantee和BIO_ctrl_get_write_guarantee】
这两个函数返回当前能够写入BIO的数据的最大长度。如果往BIO写入的数据长度比该函数返回的数据长度大,那么BIO_write返回的写入数据长度会小于要求写入的数据,如果缓冲区已经满了,则会发出一个重试的请求。这两个函数的唯一不同之处是一个使用函数实现的,一个是使用宏定义实现的。
【BIO_get_read_request和BIO_ctrl_get_read_request】
这两个函数返回要求发送的数据的长度,这通常是在对该BIO对的另一个BIO执行读操作时因为缓冲区数据为空导致失败时发出的请求。所以,这通常用来表明现在应该写入多少数据才能使接下来的读操作能够成功执行,这在TLS/SSL应用程序中是非常有用的,因为对于这个协议来说,读取的数据长度比缓冲区的数据长度通常要有意义的多。如果在读操作成功之后调用这两个函数会返回0,如果在调用该函数之前有新的数据写入(不管是部分还是全部满足需要读取的数据的要求),那么调用该函数也会返回0。理所当然,该函数返回的数据长度肯定不会大于BIO_get_write_guarantee函数返回的数据长度。
【BIO_ctrl_reset_read_request】
该函数就是把BIO_get_read_request要返回值设置为0。
该类型的BIO封装了socket的Connect方法,它使得编程的时候可以使用统一的BIO规则进行socket的connect连接的操作和数据的发送接受,而不用关心具体平台的Socket的connect方法的区别。其相关定义的一些函数如下(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)
【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_read和BIO_write的操作调用底层的连接的IO操作来完成。如果在服务器地址和
端口设置正确,但连接没有建立的时候调用读写操作函数,那么会先进行连接的建立操
作,然后再进行读写操作。
BIO_puts操作是支持的,但是BIO_gets操作不支持,这在该类型BIO的BIO_METHOD结
构定义中就可以看出来。
如果关闭标志设置了,那么在BIO被释放的时候,任何活动的连接和socket都会被关闭。
BIO_reset方法被调用的时候,连接(connect)类型的BIO的任何活动连接都会被关闭,从而回到可以重新跟同样的主机建立连接的状态。
BIO_get_fd函数返回连接类型的BIO的底层socket,当参数c不是NULL的时候,就将该socket赋值给c,当然,socket也作为返回值。c参数应该为int*类型。如果BIO没有初始化,则返回-1。
【BIO_set_conn_hostname】
该函数使用字符串设置主机名,该主机名也可以为IP地址的形式,还可以包括端口号,如hostname:port,hostname/any/other/path和hostname:port/any/other/path也是可以的。返回1。
【BIO_set_conn_port】
该函数设置主机的端口号。该端口号的形式可以为数字的形式,也可以为字符串类似”http”的形式。如果使用字符串形式,首先会使用getservbyname函数搜索其相关的端口,如果没有搜索到,那么就会使用一张缺省的名字端口解释表,目前该表列出的字符串有:http, telnet, socks, https, ssl, ftp, gopher 和 wais.返回1。
需要注意的是:**如果端口名已经作为主机名的一部分设置了,那么它就会覆盖**BIO_set_conn_port函数设置的端口值。有的时候(如有些应用可能不希望用固定的端口连接)可能不方便,这时候可以通过检测输入主机名的字符串中的”:”字符,报错或截取字符串来避免这种情况。
【BIO_set_conn_ip】
该函数使用二进制的模式设置IP地址。返回1。
【BIO_set_conn_int_port】
该函数以整数形式设置主机端口号,参数应该为int*的形式。返回1。
【BIO_get_conn_hostname】
该函数返回连接类型BIO的主机名,如果BIO以及初始化,但是没有设置主机名,那
么返回NULL。返回值因为是一个内部指针,所有不能更改它的值。
【BIO_get_conn_port】
该函数返回字符串类型的端口信息。如果没有设置,就返回NULL。
【BIO_get_conn_ip】
该函数返回二进制形式的IP地址。如果没有设置,返回为全0。
【BIO_get_conn_int_port】
该函数返回整数形式的端口号,如果没有设置,则返回0。
上述四个函数的返回值在连接操作完成之后会被更新。而在此之前,返回值都是应用程序自己设置的。
【BIO_set_nbio】
设置I/O的非阻塞标志。如果参数n为0,则I/O设置为阻塞模式;如果n为1,则I/O设置为非阻塞模式。缺省的模式是阻塞模式。应该在连接建立之前调用本函数,因为非阻塞模式的I/O是在连接过程中设置的。返回值恒为1。
注意:如果是阻塞模式的I/O,执行IO操作时(如读写),如果返回负值,说明就产生了错误的情况,如果返回值是0,一般来说表明连接已经关闭。如果设置为非阻塞模式,那么发出重试的请求就是很正常的事情了。
【BIO_do_connect】
该函数进行给定BIO的连接操作,如果连接成功,返回1,否则返回0或负值。在非阻塞模式的时候,如果调用失败了,可以调用BIO_should_retry函数以决定是否需要重试。
一般来说,应用程序不需要调用本函数,只有在希望将连接过程跟其它IO处理过程独立开来的时候,才需要调用本函数。
在初始化连接的过程的时候,如果返回值失败的原因为BIO_RR_CONNECT,调用BIO_should_io_special返回值可能也为true。如果出现这种情况,说明连接过程被阻塞住了,应用程序应该使用正常的方法进行处理,直到底层的socket连接上了再重试。
【BIO_new_connect】
该函数创建并返回一个连接类型的BIO,其实,它调用了BIO_s_connect、BIO_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);
接受(accept)类型的BIO跟连接(connect)类型BIO是相对应的,它封装了Socket的accept方法及其相关的一些操作,使得能够对不同的平台使用同一的函数进行操作。其定义的相关函数如下:
#include
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)
【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/IP的accept做了封装,所以在使用的时候就可以同一的使用BIO的规则进行操作,而不用担心因为不同的平台带来程序改写或增加移植的工作量,这也是BIO很重要的一个目的。
BIO_read和BIO_write函数操作调用了底层平台连接的I/O相关操作,如果这时候没有连接建立,端口设置正确,那么该BIO就会等待连接的建立。事实上,当一个连接建立的时候,一个新的socket类型BIO就会被创建并附加到BIO链中,形成accept->socket的BIO结构,所以这时候对初始化了的接受socket进行IO操作就会导致它处于等待连接建立的状态。当一个接受类型的BIO在BIO链的末尾的时候,在处理I/O调用之前它会先等待一个连接的建立;如果不是在末尾,那么它简单的把I/O调用传到下一个BIO。
如果接受(accept)类型BIO的关闭标志设置了,那么当BIO被释放的时候,该BIO链上任何活动的连接和socket都会被关闭。
BIO_get_fd和BIO_set_fd可以用来取得和设置该连接的socket描述符,详细情况参看《4.2描述符(fd)类型BIO》。
【BIO_set_accept_port】
该函数使用字串名来设置接受端口。其形式为“host:port”,“host”是要使用的接口,“port”是端口。这两部分都可以为“*”,这时候表示可以使用任意接口和端口。这里的端口的字符串含义跟连接(connect)类型BIO里面定义的一样,可以为数字形式的,可以使用getservbyname函数去匹配得到,还可以使用字符串的表,请参看连接型BIO说明的相关部分。
【BIO_new_accept】
该函数将BIO_new和BIO_set_accept_port函数放在一个函数里面调用,创建一个新的接受(accept)类型的BIO。
【BIO_set_nbio_accept】
该函数设置接受socket是否为阻塞模式(缺省),如果参数n为0,为阻塞模式,n为
1则为非阻塞模式。
【BIO_set_accept_bios】
该函数用来设置一个BIO链,当接受到一个连接的时候,这个设置好的BIO链就会被
复制或附加到整个BIO链上来。有的时候这中处理方法是非常好的,比如,如果每个连接都需要一个缓冲区或SSL类型的BIO,这时候使用本函数就省了很多麻烦了。需要注意的是,在调用本函数后,这个设置的链上的BIO不能自己释放,在接受(accept)BIO被释放后,它们会自动释放。
当该函数调用的时候,其设置的BIO链位于接受BIO和socket类型的BIO之间,即形成:accept->otherbios->socket的新的BIO链。
【BIO_set_bind_mode和BIO_get_bind_mode】
这两个函数用来设置和取得目前的绑定模式。如果设置为BIO_BIND_NORMAL(缺省),那么另外一个socket就不能绑定到同一个端口。如果设置为BIO_BIND_REUSEADDR ,那么另外的socket可以绑定到同一个端口。如果设置为BIO_BIND_REUSEADDR_IF_UNUSED,那么首先会尝试BIO_BIND_NORMAL的模式绑定到端口,如果失败了而且端口没有使用,那么就会使用BIO_BIND_REUSEADDR 模式绑定到端口。
【BIO_do_accept】
该函数有两个功能,当它在接受(accept)BIO设置好之后第一被调用的时候,它会创建一个接受socket并把它跟地址绑定;第二次被调用的时候,它会等待连接的到来。
【注意】
如果服务器端希望可以处理多个连接的情况(通常都是这样的),那么接受BIO必须能够用来处理以后的连接,这时候可以这样做: 等待到一个连接后,调用connection=BIO_pop(accept)
,这样,cnnection会包含一个最近建立的连接的BIO,accept就再次成为一个独立的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);
前面我们已经介绍完source/sink型的BIO了,以后的BIO系列文章将开始介绍过滤(
filter)类型的BIO。那么首先介绍的是一个非常简单的BIO类型——NULL型filter BIO,
其定义如下:
#include
BIO_METHOD * BIO_f_null(void);
在本类型中,只有这个函数是定义了的,该函数返回一个NULL型的过滤BIO_METHOD结构,NULL过滤型BIO是一个不作任何事情的BIO。针对该类型BIO的任何调用都会被简单传递中BIO链中的下一个BIO中去,也就相当于该BIO是不存在的一样。所以,一般来说,该类型的BIO用处不大。
缓冲(buffer)类型BIO是一种过滤(filter)型的BIO,其相关的一些函数定义如
下:
#include
#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)
【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支持所有BIO的I/O函数。写入缓冲(buffer)BIO的数据存储在缓冲区里面,定期写入到BIO链的下一个BIO中,事实上,只有缓冲区已满或者调用了BIO_flush函数时,数据才会写入下面的BIO,所以,当任何存储在缓冲区的数据需要写入的时候(如在使用BIO_pop函数从BIO链中删除一个buffer类型BIO之前),必须使用BIO_flush函数,如果BIO链的末尾是一个非阻塞型的BIO,有时候调用BIO_flush可能出现失败,需要重试的情况。从该类型BIO读取数据时,数据从下一个BIO填充到该BIO的
内部缓冲区中,然后再读出来。该类型BIO支持BIO_gets和BIO_puts方法,事实上,BIO_gets函数是通过在下一个BIO的BIO_read函数来实现的,所以,如果一个BIO不支持BIO_gets方法(如SSL类型的BIO),可以通过预先附加一个buffer类型BIO来实现BIO_gets的功能。
BIO_reset被调用的时候,该类型BIO里面的所有数据都会被清空。
【BIO_get_buffer_num_lines】
返回缓冲区中目前数据的的行数。
【 BIO_set_read_buffer_size、BIO_set_write_buffer_size和 BIO_set_buffer_size】
这三个函数分别设置缓冲类型BIO的读、写或者读写缓冲区的大小。初始的缓冲区大小由宏定义DEFAULT_BUFFER_SIZE决定,默认的是1024。如果设置的缓冲区大小小于DEFAULT_BUFFER_SIZE,那么就会被忽略,也就是说缓冲区大小会保持为DEFAULT_BUFFER_SIZE所定义的大小。当重新设置缓冲区大小时,里面的数据会全部被清空。成功执行返回1,否则返回0。
【BIO_set_buffer_read_data】
该函数清空缓冲区原有的数据,并使用num个buf中的数据填充该缓冲区,如果num的大小大于目前的缓冲区设定大小,那么缓冲区就会自动扩大。成功设置返回1,否则返回0。
该类型为过滤(filter)类型BIO,其定义如下:
#include
#include
BIO_METHOD * BIO_f_base64(void);
【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_gets和BIO_puts的功能。
BIO_flush在该类型BIO被调用的时候,表示需要写入的数据已经写完,用来把最后的一段数据写入到BIO里面去。
【BIO_set_flags】
该函数可以用来设置标记BIO_FLAGS_BASE64_NO_NL,该标记设置后,将把所有数据编码成为一行或者说期望所有数据都在一行上。需要注意的是,由于base64编码本身格式的原因,不能准确可靠的决定编码后的数据块的结束位置,大家使用的时候自己需要注意数据的长度问题。
【例子】
下面的程序将字符串”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);
下面的程序将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)
该类型为过滤(filter)类型BIO,其定义如下:
#include
#include<,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)
【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_CipherInit、EVP_CipherUpdate、EVP_CipherFinal三种方法。它不支持BIO_puts和BIO_gets的方法,如果要使用这两个方法,可以通过在前面附加一个buffer类型的BIO来实现,这在前面我们介绍过。
跟base64型BIO相似,当调用BIO_flush函数时,表明所有数据都已经通过该类型BIO加密了,用来将最后的一段数据通过该BIO进行加密。在进行加密的时候,必须调用BIO_flush函数来把最后的数据通过BIO进行加密,否则最后的数据会在解密的时候出现失败的情况。当从一个加密类型的BIO读取数据时,当读到最后一段数据时,会通过检测EOF自动检测到数据结束标志并自动将这段数据解密。
【BIO_set_cipher】
该函数设置该BIO的加密算法,数据使用参数key为加密密钥,参数iv作为加密的IV(初始化向量)。如果enc设置为1,则为加密,enc设置为0,则为解密。该函数不返回值。
【BIO_get_cipher_status】
该函数是一个BIO_ctrl的宏,用来检测解密是否成功执行。因为在解密的时候(执行读操作的时候),如果最后一段数据发生错误,会返回0,而遇到EOF成功完成操作后也会返回0,所以必须调用本函数确定解密操作是否成功执行了。解密成功返回1,否则返回0。
【BIO_get_cipher_ctx】
该函数也是BIO_ctrl的一个宏定义函数,它返回BIO的内部加密体制。返回的加密体制可以使用标准的加密规则进行设置。这在BIO_set_cipher函数的灵活性不能适应应用程序的需要的时候是很有用的。该函数总是返回1。
该类型为过滤(filter)类型BIO,其定义如下(openssl\bio.h,openssl\evp.h):
#include
#include<,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目录下。大家要看源文件,请参看这个文件。
【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_DigestInit、EVP_DigestUpdate和EVP_DigestFinal三个函数的功能和行为。该类型BIO是完全对称的,也就是说,不管是读数据(BIO_read)还是写数据(BIO_write),都进行相同的摘要操作**。
BIO_gets函数执行的时候,如果给定的size参数足够大,可以完成摘要(digest)计算,那么就会返回摘要值。BIO_puts函数是不支持的,如果需要支持该函数,可以在前面附加一个buffer类型的BIO。
BIO_reset函数重新初始化一个摘要类型的BIO,事实上,它是简单重新调用了EVP_DigestInit函数进行初始化。
注意,在从一个摘要BIO里面读取完摘要信息之后,在重新使用该BIO之前,必须调用BIO_reset或BIO_set_md重新初始化该BIO才行。
【BIO_set_md】
该函数是一个BIO_ctrl函数的宏定义函数,它使用参数md设置给定BIO的摘要算法。该函数必须在执行读写操作之前调用,用来初始化一个摘要类型的BIO。调用成功返回1,否则返回0。
【BIO_get_md】
该函数也是BIO_ctrl函数一个宏定义。它返回BIO摘要方法的指针到mdp参数里面。调用成功返回1,否则返回0。
【BIO_get_md_ctx】
该函数返回摘要BIO的方法结构到mdcp参数里面。该结构可以作为参数使用在EVP_DigestFinal、EVP_SignFinal和EVP_VerifyFinal函数里,这增加了灵活性。因为该函数返回的结构是一个BIO内部的结构,所以对该结构的任何改变操作都会影响到相应的BIO,并且如果该BIO释放了,该结构指针也就无效了。调用成功返回1,否则返回0。
【例子】
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_push在BIO链前面增加一个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));//因为最后一个BIO是null型的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);
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);
从名字就可以看出,这是一个非常重要的BIO类型,它封装了openssl里面的ssl规则和函数,相当于提供了一个使用SSL很好的有效工具,一个很好的助手。其定义如下:
#include
#include
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里面,大家可以参看这个文件得到详细的函数实现信息。
【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_read和BIO_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函数释放。
【BIO_set_ssl】
该函数设置SSL类型BIO的内部ssl指针指向ssl,同时使用参数c设置了关闭标志。
【BIO_get_ssl】
该函数返回SSL类型BIO的内部的SSL结构指针,得到该指针后,可以使用标志的SSL函数对它进行操作。
【BIO_set_ssl_mode】
该函数设置SSL的工作模式,如果参数client是1,那么SSL工作模式为客户端模式,如果client为0,那么SSL工作模式为服务器模式。
【BIO_set_ssl_renegotiate_bytes】
该函数设置需要重新进行session协商的读写数据的长度为num。当设置完成后,在每读写的数据一共到达num字节后,SSL连接就会自动重新进行session协商,这可以加强SSL连接的安全性。参数num最少为512字节。
【BIO_set_ssl_renegotiate_timeout】
该函数跟上述函数一样都是为了加强SSL连接的安全性的。不同的是,该函数采用的参数是时间。该函数设置重新进行session协商的时间,其单位是秒。当SSL session连接建立的时间到达其设置的时间时,连接就会自动重新进行session协商。
【BIO_get_num_renegotiates】
该函数返回SSL连接在因为字节限制或时间限制导致session重新协商之前总共读写的数据长度。
【BIO_new_ssl】
该函数使用ctx参数所代表的SSL_CTX结构创建一个SSL类型的BIO,如果参数client为非零值,就使用客户端模式。
【BIO_new_ssl_connect】
该函数创建一个包含SSL类型BIO的新BIO链,并在后面附加了一个连接类型的BIO。
方便而且有趣的是,因为在filter类型的BIO里,如果是该BIO不知道(没有实现)BIO_ctrl操作,它会自动把该操作传到下一个BIO进行调用,所以我们可以在调用本函数得到BIO上直接调用BIO_set_host函数来设置服务器名字和端口,而不需要先找到连接BIO。
【BIO_new_buffer_ssl_connect】
创建一个包含buffer型的BIO,一个SSL类型的BIO以及一个连接类型的BIO。
【BIO_ssl_copy_session_id】
该函数将BIO链from的SSL Session ID拷贝到BIO链to中。事实上,它是通过查找到两个BIO链中的SSL类型BIO,然后调用SSL_copy_session_id来完成操作的。
【BIO_ssl_shutdown】
该函数关闭一个BIO链中的SSL连接。事实上,该函数通过查找到该BIO链中的SSL类型BIO,然后调用SSL_shutdown函数关闭其内部的SSL指针。
【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就永远不会发出重试的请求。
【例子】
* 一个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 PRN
G)
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);
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;
}
//可以在这里设置验证路径,DH和DSA算法的临时密钥回调函数等等
/* 创建一个新的服务器模式的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, "\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, "
\r\n");
/* 因为使用了buffer类型的BIO,我们最好调用BIO_flush函数 */
BIO_flush(sbio);
BIO_free_all(sbio);
版权声明:本文根据DragonKing牛,E-Mail:[email protected]发布在https://openssl.126.com的系列文章整理修改而成(这个网站已经不能访问了),我自己所做的工作主要是针对新的1.0.2版本进行验证,修改错别字,和错误,重新排版,以及整理分类,配图。 未经作者允许,严禁用于商业出版,否则追究法律责任。网络转载请注明出处,这是对原创者的起码的尊重!!!