在C/C++中与Openssl epoll服务器同时与多个客户端进行通信

Communication with Openssl epoll server to multiple clients simultaneously in C/C++

本文关键字:客户端 通信 C++ 中与 Openssl 服务器 epoll      更新时间:2023-10-16

我有一个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/要使用SSL,请参考https://www.cs.utah.edu/~swalton/lists/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 errorn");
            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>nn";
        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_NONEn");
                    break;
         case SSL_ERROR_WANT_READ:
                    printf("SSL_ERROR_WANT_READn");
                    break;
         case SSL_ERROR_WANT_WRITE:
                    printf("SSL_ERROR_WANT_WRITEn");
                    break;
         case SSL_ERROR_ZERO_RETURN:
                    printf("SSL_ERROR_ZERO_RETURNn");  
                    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>nn";
                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_NONEn");
                            break;
                case SSL_ERROR_WANT_READ:
                            printf("SSL_ERROR_WANT_READn");
                            break;
                case SSL_ERROR_WANT_WRITE:
                            printf("SSL_ERROR_WANT_WRITEn");
                            break;
                case SSL_ERROR_ZERO_RETURN:
                            printf("SSL_ERROR_ZERO_RETURNn");  
                            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));

例如。