} 在ssl3_read_n的主要逻辑很简单: while (newb < n) { clear_sys_error(); s->rwstate=SSL_READING; i=BIO_read(s->rbio, &(s->s3->rbuf.buf[off+newb]), max-newb); if (i <= 0) { //只要没有读到数据,那么就返回 s->s3->rbuf.left = newb; return(i); } newb+=i; } int ssl3_pending(const SSL *s) { if (s->rstate == SSL_ST_READ_BODY) return 0; return (s->s3->rrec.type == SSL3_RT_APPLICATION_DATA) ? s->s3->rrec.length : 0; } 通过SSL_pending可以判断是否有消息数据还在缓冲区或者还没有到缓冲区,它实际上返回的就是消息的长度,因此如果使用select调用的话,很有可能select检测到的可读情况仅仅只有tcp送来的很少的数据量,远远不够ssl需要的数据量,那么只要SSL_pending返回非0,那么就需要循环调用SSL_read继续读取,否则你会认为这是一个莫名其妙的错误,明明select返回了,为何SSL_read却读不到数据,注意,在ssl读缓冲区被完全的消息填满前,SSL_read是不会返回任何数据的。同样的,SSL_write也是一样的道理,总之在openssl的实现中,一个ssl拥有一个SSL3_BUFFER类型的结构体(v3): typedef struct ssl3_buffer_st { unsigned char *buf; /* at least SSL3_RT_MAX_PACKET_SIZE bytes, size_t len; /* buffer size */ int offset; /* where to 'copy from' */ int left; /* how many bytes left */ } SSL3_BUFFER; 可以看到在ssl_st结构体中有ssl3_state_st类型的字段,ssl3_state_st中有SSL3_BUFFER类型的rbuf和wbuf,它们并不是链表,而是只有一个缓冲区,并且在ssl_write中并没有看到有线程保护的措施,因此每一个ssl连接存在且仅存在一对SSL3_BUFFER,也就是说每次只能由一个线程操作一个读缓冲或者一个写缓冲,这就迎合了openssl文档中的一个FAQ:Is OpenSSL thread-safe? Yes (with limitations: an SSL connection may not concurrently be used by multiple threads).这就是不能在多个线程操作同一个ssl指针的原因,当初这个问题可害得我加了好几个周末的班啊。特别要注意的是,如果用select模型来写基于ssl的程序,一定要弄清楚ssl和tcp语义的不同,也正是这种不同点使得将传统套接字程序移植成ssl套接字程序并不是我一年前认为的那么简单。
电梯直达 跳转到指定楼层 1楼 [收藏(0)] [报告] 发表于 2014-10-20 09:27 |只看该作者 |倒序浏览 分享到: 小弟最近看openssl,从网上最简单的例子开始,发现网上的例子入门可以,没什么实用价值,就像自己封装ssl_Accept,ss_Connect,ssl_Read之类的,前两个还好,但是ssl_read用上非阻塞IO就不顶用了。望前辈指教一下! int ssl_readn(SSL* ssl, void* buf, int n) { int nleft; int nread; char *ptr;
ptr=buf; nleft=n;
while(nleft>0&&SSL_pending(ssl)) { nread=SSL_read(ssl, ptr,nleft); switch(SSL_get_error(ssl,nread)) { case SSL_ERROR_NONE://ret >0 read all printf("SSL_ERROR_NONE\n"); break; case SSL_ERROR_ZERO_RETURN://close printf("SSL_ERROR_ZERO_RETURN\n"); nread=0;//ret 0 goto end; case SSL_ERROR_WANT_READ://try again printf("SSL_ERROR_WANT_READ\n"); break; case SSL_ERROR_WANT_WRITE: printf("SSL_ERROR_WANT_WRITE\n");//try again break; default: err_exit("SSL read problem");