[英]Communication with Openssl epoll server to multiple clients simultaneously in C/C++
我有一個 SSL 服務器(下面列出的代碼)連接到多個 SSL 客戶端。 我使用的是單個上下文,初始化是
SSL_CTX *ctx;
SSL *ssl[MAXEVENTS];
SSL_library_init();
ctx = InitServerCTX(); // initialize SSL
...
...
然后我有以下一段代碼
ssl[i] = SSL_new(ctx); // get new SSL state with context
SSL_set_fd(ssl[i], infd); // set connection socket to SSL state
然后我執行SSL_accept(ssl[i])
。
所有這些都是使用 epoll(邊沿觸發模式)執行的。 我已經修改了https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/ 中的示例,參考https://www.cs使用 SSL。 utah.edu/~swalton/listings/articles/ssl_server.c作為參考
這樣做的邏輯是
events = new epoll_event[MAXEVENTS * sizeof event];
// The event loop
while (true)
{
int n, i;
n = epoll_wait (efd, events, MAXEVENTS, -1);
for (i = 0; i < n; i++)
{
if ((events[i].events & EPOLLERR) ||
(events[i].events & EPOLLHUP) ||
(!(events[i].events & EPOLLIN)))
{
// An error has occured on this fd, or the socket is not
// ready for reading (why were we notified then?)
fprintf (stderr, "epoll error\n");
close (events[i].data.fd);
continue;
} else if (sfd == events[i].data.fd) {
// We have a notification on the listening socket, which
// means one or more incoming connections.
while (1)
{
struct sockaddr in_addr;
socklen_t in_len;
int infd;
char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
in_len = sizeof in_addr;
infd = accept (sfd, &in_addr, &in_len);
if (infd == -1)
{
if ((errno == EAGAIN) ||(errno == EWOULDBLOCK)) {
// We have processed all incoming
// connections.
break;
} else {
perror ("accept");
break;
}
}
s = getnameinfo (&in_addr, in_len,
hbuf, sizeof hbuf,
sbuf, sizeof sbuf,
NI_NUMERICHOST | NI_NUMERICSERV);
if (s == 0) {
printf("Accepted connection on descriptor %d "
"(host=%s, port=%s)\n", infd, hbuf, sbuf);
}
ssl[i] = SSL_new(ctx); // get new SSL state with context
SSL_set_fd(ssl[i], infd); // set connection socket to SSL state
int ret;
if ( (ret=SSL_accept(ssl[i])) == FAIL ) { // do SSL-protocol accept
ERR_print_errors_fp(stderr);
printf("Performing exchange Error 1.\n");
int error = SSL_get_error(ssl[i], 0);
//TODO A retry timer or retry counter. Cannot keep retrying perpetually.
if (ret <=0 && (error == SSL_ERROR_WANT_READ)) {
//Need to wait until socket is readable. Take action?
//LOG the reason here
perror ("Need to wait until socket is readable.");
} else if (ret <=0 && (error == SSL_ERROR_WANT_WRITE)) {
//Need to wait until socket is writable. Take action?
//LOG the reason here
perror ("Need to wait until socket is writable.");
} else {
//LOG the reason here
perror ("Need to wait until socket is ready.");
}
shutdown (infd, 2);
SSL_free (ssl[i]);
continue;
}
// Make the incoming socket non-blocking and add it to the
// list of fds to monitor.
s = SocketNonBlocking (infd);
if (s == -1) {
abort ();
}
event.data.fd = infd;
event.events = EPOLLIN | EPOLLET | EPOLLHUP;
s = epoll_ctl (efd, EPOLL_CTL_ADD, infd, &event);
if (s == -1) {
perror ("epoll_ctl");
abort ();
}
}
continue;
現在,
while (1)
{
ssize_t count;
char buf[1024];
char reply[1024];
printf("Performing exchange.\n");
const char* HTMLecho="<html><body><pre>%s</pre></body></html>\n\n";
ShowCerts(ssl[i]); // get any certificates
count = SSL_read(ssl[i], buf, sizeof(buf)); // get request
int32_t ssl_error = SSL_get_error (ssl[i], count);
switch (ssl_error) {
case SSL_ERROR_NONE:
printf("SSL_ERROR_NONE\n");
break;
case SSL_ERROR_WANT_READ:
printf("SSL_ERROR_WANT_READ\n");
break;
case SSL_ERROR_WANT_WRITE:
printf("SSL_ERROR_WANT_WRITE\n");
break;
case SSL_ERROR_ZERO_RETURN:
printf("SSL_ERROR_ZERO_RETURN\n");
break;
default:
break;
}
if (( count > 0 ) )
{
buf[count] = 0;
printf("count > 0 Client msg: \"%s\"\n", buf);
sprintf(reply, HTMLecho, buf); // construct reply
SSL_write(ssl[i], reply, strlen(reply)); // send reply
} else if ((count < 0) ){
printf("count < 0 \n");
if (errno != EAGAIN)
{
printf("count < 0 errno != EAGAIN \n");
perror ("read");
done = 1;
}
break;
} else if (count==0){
ERR_print_errors_fp(stderr);
epoll_ctl(efd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
printf("count == 0 Client Disconnected.\n");
done = 1;
break;
}
}
if (done)
{
printf("Freeing data.\n");
int sd = SSL_get_fd(ssl[i]);
SSL_free(ssl[i]); // release SSL state
close(sd); // close connection
//close (events[i].data.fd);
}
}
這適用於一台服務器 - 一個客戶端。 但是當我嘗試連接兩個客戶端時,最后連接的客戶端是唯一接收數據的客戶端。 之前連接的客戶端只是一直掛着,沒有任何活動。
更新我發現這里存在一些索引問題。 epoll 示例中的變量i
的值與我認為應該對應的值不對應。 我嘗試連接兩個客戶端,我最初認為i
應該為第二個客戶端增加,但事實並非如此。 它仍然保持0
。
好的,我解決了這個問題。 我的問題源於不正確的索引。 我依賴的變量i
沒有按照我預期的方式運行。 (請參閱我的問題中的更新)
首先我聲明std::map<int,SSL*> sslPairMap;
然后我將成功的 fd 和 SSL 接受插入到 C++ 中的 std::map 中。 在 C 中,可以使用基於結構的配對。 這里有一個例子https://github.com/dCache/dcap/blob/b432bd322f0c1cf3e5c6a561845899eec3acad1e/plugins/ssl/sslTunnel.c
//(c) 2014 enthusiasticgeek for stack overflow
sslPairMap.insert(std::pair<int,SSL*>(infd, ssl));
// Make the incoming socket non-blocking and add it to the
// list of fds to monitor.
s = AibSocketNonBlocking (infd);
if (s == -1) {
abort ();
}
aibevent.data.fd = infd;
aibevent.events = EPOLLIN | EPOLLET | EPOLLHUP;
s = epoll_ctl (efd, EPOLL_CTL_ADD, infd, &aibevent);
if (s == -1) {
perror ("epoll_ctl");
abort ();
}
在此之后,我只需從地圖中檢索 SSL*,以確保我不會無意中更改索引。 std::map 拯救了這一天
//(c) 2014 enthusiasticgeek for stack overflow
while (1)
{
ssize_t count;
char buf[1024];
char reply[1024];
printf("Performing exchange where i = %d.\n",i);
const char* HTMLecho="<html><body><pre>%s</pre></body></html>\n\n";
ShowCerts(sslPairMap[aibevents[i].data.fd]); // get any certificate
count = SSL_read(sslPairMap[aibevents[i].data.fd], buf, sizeof(buf)); // get request
ssl_error = SSL_get_error (sslPairMap[aibevents[i].data.fd], count);
switch (ssl_error) {
case SSL_ERROR_NONE:
printf("SSL_ERROR_NONE\n");
break;
case SSL_ERROR_WANT_READ:
printf("SSL_ERROR_WANT_READ\n");
break;
case SSL_ERROR_WANT_WRITE:
printf("SSL_ERROR_WANT_WRITE\n");
break;
case SSL_ERROR_ZERO_RETURN:
printf("SSL_ERROR_ZERO_RETURN\n");
break;
default:
break;
}
if (( count > 0 ) )
{
buf[count] = 0;
printf("count > 0 Client msg: \"%s\"\n", buf);
sprintf(reply, HTMLecho, buf); // construct reply
SSL_write(sslPairMap[aibevents[i].data.fd], reply, strlen(reply)); // send reply
break;
} else if ((count < 0) ){
printf("count < 0 \n");
if (errno != EAGAIN)
{
printf("count < 0 errno != EAGAIN \n");
perror ("read");
done = 1;
}
break;
} else if (count==0){
ERR_print_errors_fp(stderr);
epoll_ctl(efd, EPOLL_CTL_DEL, aibevents[i].data.fd, NULL);
printf("count == 0 Client Disconnected.\n");
done = 1;
break;
}
}
if (done)
{
printf("Freeing data.\n");
int sd = SSL_get_fd(sslPairMap[aibevents[i].data.fd]);
if(ssl_error == SSL_ERROR_NONE){
SSL_shutdown(sslPairMap[aibevents[i].data.fd]);
}
SSL_free(sslPairMap[aibevents[i].data.fd]); // release SSL state
close(sd); // close connection
//close (aibevents[i].data.fd);
erase_from_map(sslPairMap, aibevents[i].data.fd);
}
}
如果有人遇到這種情況,另一種方法是將SSL*
指針存儲在事件數據本身中:
events[i].data.u64 = (long long)ssl;
當您需要從中讀取/寫入時:
auto ssl = (SSL*)events[i].data.u64;
SSL_read(ssl, someBuffer, sizeof(someBuffer));
例如。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.