Я пишу серверную программу с C ++.
я использовал мультиплексирование сокетов epoll для linux и openssl 1.0.2 для безопасного уровня
на Centos 6.9 final (с 4 ядрами, 2 ГБ ОЗУ, аркой x64).
Целью этой серверной программы является библиотека веб-сокетов / сокетов для моих проектов.
Моя проблема в том,
Функции SSL_accept и SSL_do_handshake немного медленные.
я сделал немного статики для моей проблемы;
для 50k EPOLL_CTL_ADD (я имею в виду, 50k пользовательских соединений одновременно / многократно) мое основное чтение
функция (логика SSL_read, SSL_accept) тратит всего 92,8 секунды.
за эти 92,8 секунды функции SSL_accept (или SSL_do_handshake [я пробовал]) тратят 81,9 секунды.
но когда я закрываю свои алгоритмы ssl (сделанные с использованием определений) и повторно подчеркиваю, чтение основной функции используется [read (function]), тратя 10.4 секунд на 50k с использованием тех же функций в этой статике.
Средние значения;
Без SSL: моя основная функция чтения тратит в среднем 0,2 мс на каждый вызов чтения (и выполняет мои верхние функции) из цикла epoll (10.4 / 50000)
С SSL: моя основная функция чтения тратит в среднем 1,85 мс на каждый вызов чтения (и выполняет мои верхние функции) из цикла epoll (92,8 / 50000)
В этих условиях мой сервер может принимать только 18 ~ 19 тысяч пользователей одновременно (я пробовал много раз) (версия SSL в 9.25 раз медленнее, чем версия без SSL). И в это время происходит много блокировок самого моего сервера.
Заметки:
У меня вопрос, почему мои функции SSL_accept / SSL_do_handshake работают медленно.
Я много чего перепробовал за 3 ~ 5 месяцев. Но я не мог понять это.
Моя логика тратить время:
###
timeMs = CurTime()
SSL_accept(...)
timeTotalVariable += (CurTime() - timeMs)
###
Это мой псевдокод:
"call" ctx = SSL_CTX_new(SSLv23_method())
; close sslv2 & sslv3 protos
; set SSL_MODE_RELEASE_BUFFERS | SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_OP_NO_COMPRESSION | SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
; set SSL_SESS_CACHE_OFF (if i use built-in openssl server cache, there is nothing big different)
; SSL_CTX_set_session_id_context ...
; SSL_CTX_set_verify(cx, SSL_VERIFY_NONE, NULL);
; SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE);
; SSL_CTX_set_tmp_dh(ctx, ...
; SSL_CTX_use_certificate_chain_file(...
; SSL_CTX_use_PrivateKey_file(...
; SSL_CTX_check_private_key(...
; SSL_CTX_set_cipher_list(...
; SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE);
; SSL_CTX_set_ecdh_auto(ctx, 1); (or set curve with "prime256v1")
"call" bind(srvSock, ...
"call" srvSock = listen(...
"call" add_epoll(srvSock, ReadFlag
###
"func" Read_Handle(Sock // from my epoll( loops
if (Sock == srvSock)
"call" Accept_Handle(Sock
else
"call" Real_Read_Handle(Sock
"end func"
"func" Real_Read_Handle(Sock
if (!SSL_is_init_finished(ssl)) {
timeMs = CurTime()
"call" ret = SSL_accept(ssl);
timeTotalVariable += (CurTime() - timeMs)
"call" err = SSL_get_error(ssl, ret);
if (err == SSL_ERROR_WANT_READ) {
set_epoll(Sock, ReadFlag
return;
}
if (err == SSL_ERROR_WANT_WRITE) {
set_epoll(Sock, WriteFlag
return;
}
...
}
"call" SSL_read(ssl, ...
...
"end func"
"func" Accept_Handle(Sock
"call" newSock = accept(Sock, ...
"call" MyNonBlockingSocketSetFunction(Sock
"call" ssl = SSL_new(ctx
"call" SSL_set_fd(ssl, newSock);
"call" SSL_set_accept_state(ssl
"call" BIO_set_nbio(SSL_get_rbio(ssl), 0);
"call" BIO_set_nbio(SSL_get_wbio(ssl), 0);
"call" add_epoll(newSock, ReadFlag // handshake will do at main read function.
"end func"
мои коды очень большие (я давно кодирую этот сервер), поэтому я дал псевдокоды.
как я уже сказал, я много чего пробовал, но не получилось.
проблема в strace (я думаю);
read(9898, "\26\3\3\0F", 5) = 5
read(9898, "\20\0\0BA\4@/\241|9\325\247\351S\265\7<\204\5\260\203H\\\314\212\301\324B\f\353+"..., 70) = 70
read(9898, "\24\3\3\0\1", 5) = 5
read(9898, "\1", 1) = 1
read(9898, "\26\3\3\0(", 5) = 5
read(9898, ">\205\203\0006\354\337\264{\214\6\0\4\343\311Ai%\30\347\20\307\300\253+\200}B\\\326:\211"..., 40) = 40
write(9898, "\26\3\3\0\312\4\0\0\306\0\0\1,\0\300\17\225!A4\310\312(\231.\263\23\t\265\3}\211"..., 258) = 258
read(9896, "\27\3\3\1\347", 5) = 5
read(9896, "t()R\2\347pvE\341\n\272C\231\267\352.\21G\212u\203wO\v\16\326\4\352\205\326\340"..., 487) = 487
read(9895, "\26\3\3\0F", 5) = 5
read(9895, "\20\0\0BA\4\213\335\242OV\2154\371\345\2321\257\217\377\236\"xN\206\322\205I{\242\307\276"..., 70) = 70
read(9895, "\24\3\3\0\1", 5) = 5
read(9895, "\1", 1) = 1
read(9895, "\26\3\3\0(", 5) = 5
read(9895, "^\341Ii\273\35\nMe\214\303\352aE\236\26q\333\274\366\375\255@;\275Ad\204ko\223\377"..., 40) = 40
write(9895, "\26\3\3\0\312\4\0\0\306\0\0\1,\0\300\17\225!A4\310\312(\231.\263\23\t\265\3}d"..., 258) = 258
read(9894, "\27\3\3\1\346", 5) = 5
read(9894, "\n+e\16\\\330\305\322\364\367j\356b\336\3T3\va\200\324\03107_\320,\22H\33\3\350"..., 486) = 486
read(9892, "\26\3\3\0F", 5) = 5
read(9892, "\20\0\0BA\4Q\214t7$\276$\3744\247\364,\320\376\225\2623z\204\254U\355\17\323\214S"..., 70) = 70
read(9892, "\24\3\3\0\1", 5) = 5
read(9892, "\1", 1) = 1
read(9892, "\26\3\3\0(", 5) = 5
read(9892, "\201\214T8\257nBM{\210\202V\25\340R\315)\320\343'h\341\353\351\6f!$\314\230\300\221"..., 40) = 40
write(9892, "\26\3\3\0\312\4\0\0\306\0\0\1,\0\300\17\225!A4\310\312(\231.\263\23\t\265\3}h"..., 258) = 258
Это рукопожатие вызовы чтения / записи на SSL_accept моего сервера.
но я протестировал nginx, openlitespeed, nodejs и т. д.
Страс-лог этих серверов вот так;
read(8247, "\26\3\1\1.\1\0\1*\3\3T\214\201\305\2\365\260\r\210\212\232j\1778\315\301\312\235\354e\4"..., 1024) = 307
write(8247, "\26\3\3\0=\2\0\0009\3\3\16\241r76\203\245e\373\10rF\330l\235-\3\2436Y\326"..., 1821) = 1821
read(8178, "\26\3\1\1.\1\0\1*\3\3\367._\207\353\316\262j\277\332\352\237\363\343\30\351\370X\210\34x"..., 1024) = 307
write(8178, "\26\3\3\0=\2\0\0009\3\3\347\322\200\222\206\336y\2\243\213<\235\265\230\33\315\375\306H5\7"..., 1821) = 1821
read(8280, "\26\3\1\1.\1\0\1*\3\3\35\214\374)Vg7\225\36\340\251*\234j\35>O\234\3Dw"..., 1024) = 307
write(8280, "\26\3\3\0=\2\0\0009\3\3\240q\207T\331\262\314\261o\3\307L{U\20c\270\232\377(\364"..., 1821) = 1821
read(8567, "\26\3\1\1.\1\0\1*\3\3\255\270\3447\362\226\17\276x\205\305\334C\16$@Zd\2\353\304"..., 1024) = 307
write(8567, "\26\3\3\0=\2\0\0009\3\3\355\314fko\25C\235\260\213\273\263o`\2\234:\344\27\341]"..., 1821) = 1821
для рукопожатия, 8 частичное чтение на моем сервере ???
почему моя функция SSL_accept работает медленно?
PS. Извините за мой плохой грамматик.
Редактировать; Части настроек SSL (части настроек ssl легко воспроизвести, но часть мультиплексирования сокетов очень трудно воспроизвести)
SSL_CTX *ctx = NULL;
void Init_SSL() {
OPENSSL_config(NULL);
SSL_library_init();
SSL_load_error_strings();
OpenSSL_add_all_algorithms();
}
void Init_Server() {
#if OPENSSL_VERSION_NUMBER < 0x10100000L
ctx = SSL_CTX_new(SSLv23_method());
#else
ctx = SSL_CTX_new(TLS_method());
#endif
if (ctx == NULL) {
cout << "can not create ctx" << endl;
return;
}
SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2);
SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv3);
SSL_CTX_set_mode(ctx, SSL_MODE_RELEASE_BUFFERS);
SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE);
SSL_CTX_set_mode(ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
SSL_CTX_set_options(ctx, SSL_OP_NO_COMPRESSION);
SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
#ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
SSL_CTX_set_options(ctx, SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION);
#endif
SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
const unsigned char *TmpStr = "SslSrv";
SSL_CTX_set_session_id_context(ctx, TmpStr, strlen(TmpStr));
SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL);
}
bool LoadCert() {
DH *dh;
BIO *bio = BIO_new_file("server.dh", "r");
if (bio == NULL) {
cout << "dh file error" << endl;
return false;
}
dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
BIO_free(bio);
if (dh == NULL) {
cout << "dh params error" << endl;
return false;
}
const int size = BN_num_bits(dh->p);
if (size < 1024) {
cout << "dh bits size error" << endl;
return false;
}
SSL_CTX_set_options(ctx, SSL_OP_SINGLE_DH_USE);
int r = SSL_CTX_set_tmp_dh(ctx, dh);
if (!r) {
cout << "cannot set tmp dh" << endl;
return false;
}
}
if (SSL_CTX_use_certificate_chain_file(ctx, "server.crt") <= 0) {
cout << "cannot load cert" << endl;
return false;
}
if (SSL_CTX_use_PrivateKey_file(ctx, "server.key", SSL_FILETYPE_PEM) <= 0) {
cout << "cannot load priv key file" << endl;
return false;
}
if (!SSL_CTX_check_private_key(ctx)) {
cout << "priv key file is wrong" << endl;
return false;
}
// this list from nodejs (default list)
if (SSL_CTX_set_cipher_list(ctx, "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES256-SHA384:ECDHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA256:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA") == 0) {
cout << "cannot set cipher list" << endl;
return false;
}
SSL_CTX_set_options(ctx, SSL_OP_SINGLE_ECDH_USE);
#if SSL_CTRL_SET_ECDH_AUTO
SSL_CTX_set_ecdh_auto(ctx, 1);
#endif
return true;
}
есть ли пропущенные или недействительные вещи в этих настройках?
Благодарю.
SSL Handshake включает в себя важную операционную логику, включает шифрование.
Я перечисляю вам некоторые причины медленной обработки:
Поэтому, если у вас нет рукопожатия SSL в вашем приложении, ваше приложение избегает всех вышеперечисленных операций и, очевидно, увеличит скорость работы.
Одна из наиболее важных причин низкой скорости заключается в том, что, если учесть все вышеизложенное, между клиентом и сервером существует очень много связей, и расшифровка / шифрование данных выполняется синхронно, что также уменьшает параллельную обработку.
Других решений пока нет …