#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#define MAXBUF 1024
/************关于本文档********************************************
*filename: ssl-server.c
*purpose: 演示利用 OpenSSL 库进行基于 IP层的 SSL 加密通讯的方法,这是服务器端例子
*********************************************************************/
int main(int argc, char **argv)
{
int sockfd, new_fd;
socklen_t len;
struct sockaddr_in my_addr, their_addr;
unsigned int myport, lisnum;
char buf[MAXBUF + 1];
SSL_CTX *ctx;
if (argv[1])
myport = atoi(argv[1]);
else
myport = 8000;
if (argv[2])
lisnum = atoi(argv[2]);
else
lisnum = 2;
/* SSL 库初始化 */
SSL_library_init();
/* 载入所有 SSL 算法 */
OpenSSL_add_all_algorithms();
/* 载入所有 SSL 错误消息 */
SSL_load_error_strings();
/* 以 SSL V2 和 V3 标准兼容方式产生一个 SSL_CTX ,即 SSL Content Text */
ctx = SSL_CTX_new(SSLv23_server_method());
/* 也可以用 SSLv2_server_method() 或 SSLv3_server_method() 单独表示 V2 或 V3标准 */
if (ctx == NULL) {
ERR_print_errors_fp(stdout);
exit(1);
}
/* 载入用户的数字证书, 此证书用来发送给客户端。 证书里包含有公钥 */
if (SSL_CTX_use_certificate_file(ctx,"cacert.pem", SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stdout);
exit(1);
}
/* 载入用户私钥 */
if (SSL_CTX_use_PrivateKey_file(ctx,"privkey.pem", SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stdout);
exit(1);
}
/* 检查用户私钥是否正确 */
if (!SSL_CTX_check_private_key(ctx)) {
ERR_print_errors_fp(stdout);
exit(1);
}
/* 开启一个 socket 监听 */
printf("socket creating...... \t\t");
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("socket");
exit(1);
}
else
printf("socket created.\n");
bzero(&my_addr, sizeof(my_addr));
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(myport);
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
printf("bingding......\n");
if (bind(sockfd, (struct sockaddr *) &my_addr, sizeof(struct sockaddr))
== -1) {
perror("bind");
exit(1);
} else
printf("binded.\n");
if (listen(sockfd, lisnum) == -1) {
perror("listen");
exit(1);
} else
printf("begin listen......\n");
while (1) {
SSL *ssl;
len = sizeof(struct sockaddr);
/* 等待客户端连上来 */
if ((new_fd =
accept(sockfd, (struct sockaddr *) &their_addr,
&len)) == -1) {
perror("accept");
exit(errno);
} else
printf("server: got connection from %s, port %d, socket %d\n",
inet_ntoa(their_addr.sin_addr),
ntohs(their_addr.sin_port), new_fd);
/* 基于 ctx 产生一个新的 SSL */
ssl = SSL_new(ctx);
/* 将连接用户的 socket 加入到 SSL */
SSL_set_fd(ssl, new_fd);
/* 建立 SSL 连接 */
if (SSL_accept(ssl) == -1) {
perror("accept");
close(new_fd);
break;
}
/* 开始处理每个新连接上的数据收发 */
bzero(buf, MAXBUF + 1);
printf("[Server write]:");
fflush(stdout);
scanf("%s",buf);
/* 发消息给客户端 */
len = SSL_write(ssl, buf, strlen(buf));
if (len <= 0) {
printf("The message '%s' send failfully!errno:%d,error info:'%s'\n",buf, errno, strerror(errno));
goto finish;
} else
printf("The message '%s' send successfully,the amount bytes sent :%d\n",
buf, len);
bzero(buf, MAXBUF + 1);
/* 接收客户端的消息 */
len = SSL_read(ssl, buf, MAXBUF);
if (len > 0)
printf("接收到来自客户端的消息:'%s',共%d个字节的数据。\n",
buf, len);
else
printf
("消息接收失败!错误代码是%d,错误信息是'%s'\n",
errno, strerror(errno));
/* 处理每个新连接上的数据收发结束 */
finish:
/* 关闭 SSL 连接 */
SSL_shutdown(ssl);
/* 释放 SSL */
SSL_free(ssl);
/* 关闭 socket */
close(new_fd);
}
/* 关闭监听的 socket */
close(sockfd);
/* 释放 CTX */
SSL_CTX_free(ctx);
return 0;
}
都说细节决定成败,我觉得的编程来说,特别是面试的时候细节最能决定的是关键时候你能装的程度,所以我想有个系列记录我遇到的各种我遇到的细节问题,以备不时之需啊。
cout<<"Hello,world"<<endl;作为我真正学习写程序的起点我一直对其怀有感激之心,想到大一学C++的时候看到这个的时候觉得,这就是写程序吗?这就是我以前梦想的能够让电脑听我指挥,什么软件,游戏,病毒的开发吗?不像啊,这个也没什么作用啊,就看着个黑屏幕显示一句话,什么也做不了,那时也不懂得什么东西的学习都是漫长的过程,但是好在后面也没想太多,相比当时大多数兴趣被这黑屏浇灭而后再也没有研究过编程的同学,我还算幸运的莫名的坚持了下来。这几天,无意中看到一个C++教材第一页的hello world,脑子里面竟然想到当时我初学的时候老师跟我们说想把东西输出到屏幕上还可以用cout,cerr和clog,我还记得那个时候我还都试了试,发现都可以,但是也没有太在意其中的学问,只是疑问一样的功能为什么要搞三个名字,老师自然也是搬ppt的东西,也没有解释。后来我学会了查msdn,查到了这三个的区别在于重定向和缓冲,直到前几天,我居然在项目中遇到了有关这三个的问题,虽然不难解决,于是我觉得我还是有必要研究研究这个细节。
一切都先从msdn开始比较靠谱,对于这三个的差异,msdn是这么解释的:
The ostream class, through the derived class basic_ostream, supports the predefined stream objects:
-
cout standard output
-
cerr standard error with limited buffering
-
clog similar to cerr but with full buffering
这里面关键的一个词是buffering(什么?重定向,这里暂时没有看到,生命有限,一次不能研究的太多),buffer存在于计算机软硬件的各个方面,缓冲区的意义很好理解,最大的好处无非是可以提高效率,每天跑到银行存1000块钱和你十天凑齐10000块钱一次性向银行存入,所耗费的时间果断不能是一样的,所以无论在哪个方面缓冲都是人类的好朋友啊。这三个的差别从字面上看,第一个根本没有提buffering什么事,第二个有limited buffering,第三个是full buffering,有什么区别呢,先从第一个开始吧,继续在msdn中查找cout,看看这家伙怎么说的。让人是失望的是这里面基本没什么有用的信息,但是都说超链接改变世界,这里面的example有个cerr的超连接,正好它也排在第二个,顺手看看cerr好了,里面是这样描述的:
The object controls unbuffered insertions to the standard error output as a byte stream. Once the object is constructed, the expression cerr.flags & unitbuf is nonzero.
对比cout和前面的内容,unbuffered这个关键词再一次引起我们的关注,另外后面还多了一句话,提到了flags和unitbuf,在看了clog发现和cout没什么差别之后,每一个发育自然并且想研究一下的人都会查看一下unitbuf或者flags。查看flags发现这是一个和fmtflags相关的函数,而且后面还有一个例子:
#include <iostream> #include <fstream> int main ( ) { using namespace std; cout << cout.flags( ) << endl; cout.flags( ios::dec | ios::boolalpha ); cout << cout.flags( ); }
运行一下会查看到cout的flag值,会看到输出是两个很奇怪的整数531和16896,这个时候就要去看看fmtflags到底是什么,msdn上是这么写的:
Constants to specify the appearance of output.
static const fmtflags boolalpha(15), dec(10), fixed(14), hex(12), internal(9), left(7), oct(11), right(8), scientific(13), showbase(4), showpoint(5), showpos(6), skipws(1), unitbuf(2), uppercase(3), adjustfield, basefield, floatfield;
第一反应是这是什么玩意儿,还好msdn是高大全的参考,在Remark部分可以看到如下语句:
Supports the manipulators in ios.
The type is a bitmask type that describes an object that can store format flags.
两层含义,第一个支持ios里面的操作符,第二个是这是一个bitmask类型,也就是说上面的18个fmtflags类型定义的玩意儿都是这个类型的,ios里面的操作符很好理解,就是那些可以定义cout(当然同样作为输出流的cerr,clog也是可以被定义的)格式的东西,比如最简单的cout<<hex<<12;可以输出十六进制的12,也就是说fmtflags这个的作用就是使用另一种方式设定格式,设定的方法前面那个例子中也显示了,虽然是定义了18个fmtflags变量,但是在这个词条的底部也说明了最后三个是前面15个某些的组合,也就是说可以大胆的猜想flags有15位,具体这15个具体有什么作用,对此有兴趣的可以继续探究,上面每个变量旁边的括号中的数字是我亲测得每个对应的flags的位,假设最低位是从1开始的。
另外,上面的程序531转换成二进制就是1000000001,也就是默认的cout设定的是dec和skipws两个位,skipws全名叫做skipwhitespace,这也很容易解释了cout的时候输出的十进制的数并且能够忽略前导空格,这算一个细节吧。
在所有的flag里面,凭借一股风骚的意识你应该能发现有一个很眼熟啊--unitbuf,这个在前面cerr的说明里出现过,滚动一下鼠标可以发现cerr的说明中说这个位在cerr里面是非0的,而其余两个没有特别说明,那么我们就看看初始情况下clog和cerr的flag是什么,可以发现clog和cout是一样的,cerr却是1000000011,是不是有种看破的感觉,unitbuf既然有个buf,再加上cerr关于buffer的特别说明,可以猜想这个位就是控制buf的,再看看它的说明:
unitbuf, to flush output after each insertion.
翻译成中文就是每次输出的时候刷新输出,这句话在你什么也不知道的时候确实很疑惑,什么叫flush?前面说了这里面的符号位和ios的操作都是对应的,那么看看ios里面有什么相关的内容好了,找到ios members可以看到其中有个函数也是较unitbuf,这个时候就得赶紧点进去看看,Remark部分是这么写的:
Note that endl also flushes the buffer.
如果你从没有接触过这方面的内容,那么恭喜你,除了endl能换行之外,你至少知道了endl还可以刷新缓冲区,现在面临一个新问题,什么叫flushes the buffer,msdn不好找了,这就要回到缓冲区这个概念上来了。
所谓缓冲区前面已经大概的解释了,你每天能赚1000(貌似有点多),然后你去存银行,这就是没有缓冲,但是你嫌这样太麻烦,你决定先攒几天钱再存银行,这样的话就是有缓存。但是总会有个条件让你达到要去存这个行为,这个条件或者是到了多少天,或者是到了多少钱,或者是你老婆迫使你赶紧去存,因为身上现金多了容易遭抢啊或者现金多了容易起干坏事,毕竟男人的银行卡一般都不在自己身上。但是事务都有两面性,虽然你每天存很耗时间,但是至少保证比好几天一存的安全性大(假设银行是信得过的)。
输出缓冲区和上面这个过程也很像,如果不是设置的强制刷新的这个位,那么缓冲区只能在两种情况下刷新,第一个是缓冲区满了(时间到了或者钱总量到了),或者是强制要其刷新,比如使用了endl,flush等等(你老婆叫你去存钱),或者设置了unitbuf位。回到这三个输出流上面来,cerr既然设置了unitbuf项的位,它每次输出都会刷新输出缓冲区,这样的好处就是在程序出了一些特殊错误的情况下仍然能够有输出(费时但是安全),比如递归堆栈崩溃,而cout不会,那么刷新缓冲区是往哪儿刷新呢?在这里是往输出设备(不一定是屏幕),通俗的说就是如果刷新没有进行,那么就不会显示在控制台的窗口上,好了,费了这么多话,最关键的要眼见为实,怎么样才能看到这一个现象呢?按照上面费的这么多话,cerr和cout在表现上应该是不同的,所以我们用这两个试试看。
想看到这个过程,我决定用一个Sleep,用
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <resolv.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#define MAXBUF 1024
void ShowCerts(SSL * ssl)
{
X509 *cert;
char *line;
cert = SSL_get_peer_certificate(ssl);
if (cert != NULL) {
printf("The data certificate info:\n");
line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
printf("certificate: %s\n", line);
free(line);
line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
printf("publish author: %s\n", line);
free(line);
X509_free(cert);
} else
printf("No certificate info\n");
}
/************关于本文档********************************************
*filename: ssl-client.c
*purpose: 演示利用 OpenSSL 库进行基于 IP层的 SSL 加密通讯的方法,这是客户端例子
*********************************************************************/
int main(int argc, char **argv)
{
int sockfd, len;
struct sockaddr_in dest;
char buffer[MAXBUF + 1];
SSL_CTX *ctx;
SSL *ssl;
/*( if (argc != 3) {
printf("参数格式错误!正确用法:IP Port\n");
exit(0);
}
*/
/* SSL 库初始化,参看 ssl-server.c 代码 */
SSL_library_init();
OpenSSL_add_all_algorithms();
SSL_load_error_strings();
ctx = SSL_CTX_new(SSLv23_client_method());
if (ctx == NULL) {
ERR_print_errors_fp(stdout);
exit(1);
}
/* 创建一个 socket 用于 tcp 通信 */
printf("socket creating...... \t\t");
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Socket");
exit(errno);
}
printf("socket created.\n");
/* 初始化服务器端(对方)的地址和端口信息 */
printf("address creating...... \t\t");
bzero(&dest, sizeof(dest));
dest.sin_family = AF_INET;
dest.sin_port = htons(8000);
if (inet_aton("10.70.253.201", (struct in_addr *) &dest.sin_addr.s_addr) == 0) {
perror(argv[1]);
exit(errno);
}
printf("address created.\n");
/* 连接服务器 */
printf("server connecting......\n\t\t");
if (connect(sockfd, (struct sockaddr *) &dest, sizeof(dest)) != 0) {
perror("Connect ");
exit(errno);
}
printf("server connected.\n");
/* 基于 ctx 产生一个新的 SSL */
ssl = SSL_new(ctx);
SSL_set_fd(ssl, sockfd);
/* 建立 SSL 连接 */
if (SSL_connect(ssl) == -1)
ERR_print_errors_fp(stderr);
else {
printf("\n\nConnected with %s encryption\n", SSL_get_cipher(ssl));
ShowCerts(ssl);
}
/* 接收对方发过来的消息,最多接收 MAXBUF 个字节 */
bzero(buffer, MAXBUF + 1);
/* 接收服务器来的消息 */
len = SSL_read(ssl, buffer, MAXBUF);
if (len > 0)
printf("[Cleint recv]:%s,the bytes:%d。\n",
buffer, len);
else {
printf("error number:%d,error info:%s\n",
errno, strerror(errno));
goto finish;
}
bzero(buffer, MAXBUF + 1);
printf("[Client send]:");
fflush(stdout);
scanf("%s",buffer);
/* 发消息给服务器 */
len = SSL_write(ssl, buffer, strlen(buffer));
if (len < 0)
printf
("消息'%s'发送失败!错误代码是%d,错误信息是'%s'\n",
buffer, errno, strerror(errno));
else
printf("send %s successfully the bytes:%d\n",
buffer, len);
finish:
/* 关闭连接 */
SSL_shutdown(ssl);
SSL_free(ssl);
close(sockfd);
SSL_CTX_free(ctx);
return 0;
}