在C/C++中与Openssl epoll服务器同时与多个客户端进行通信
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/要使用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));
例如。
- 如何将 Firebase 与基于 Linux 的客户端应用配合使用,以便与服务器进行双向消息通信
- 提升::Asio 异步聊天客户端停止与服务器通信
- UDP客户端/服务器程序在不同的IP和端口上通信超过2个插座
- 如何让客户端通过modbus-tcp协议与多个服务器通信?
- 如何将数据序列化为C++ zmq 客户端和 Python zmq 服务器之间的通信
- 使用UNIX套接字C++客户端与Node.js服务器通信的简单示例
- 嗅探 QWebsocket 服务器 - 客户端通信
- 证书验证在客户端服务器通信中使用boost :: asio和openssl失败
- 客户端JavaScript和C 套接字服务器之间的通信
- 如何在服务器和客户端之间进行异步通信
- C++:一个客户端与多个服务器通信
- C++:服务器和客户端之间的经典通信练习
- C++ 客户端无法与 Java 服务器通信
- 使用 C/C++、SSL 协议的客户端和服务器通信不起作用
- 如果传递长类型,32位客户端如何与64位服务器通信
- c++tcp多线程客户端/服务器-如何与线程套接字处理程序进行通信
- C/C++服务器,通过stdin/stdout与客户端通信(阻塞stdin,直到读取了大量字节)
- 套接字编程中的客户端到客户端通信与许多客户端
- 使用C++和Boost::Asio的N方客户端到客户端通信
- C ++ Winsock - 服务器只与单个客户端通信,而它应该与每个客户端通信