我正在尝试使用ASIO编写一个允许服务器充当客户端的应用程序。例如:
我有3台服务器需要相互通信。在与网络中的其他服务器通信时,他们需要能够充当客户端。所有3台服务器都可以通过unix域套接字或带有SSL的TCP / IP来处理请求。
以下是数据的流动方式:
1)独立客户端连接到服务器A(通过unix域套接字)并向其发送请求。
2)服务器尝试回答请求,但如果不能,则启动与服务器B的TCP / IP连接(现在服务器A充当服务器B的客户端)并将请求转发给它。服务器还“篡改”数据包,告诉服务器B不要将消息转发到另一台服务器,这样就不会创建无限循环。
3)服务器B如果可以处理请求,则响应服务器A.
4)如果服务器B可以处理请求,则服务器A将响应返回给独立客户端。
5)如果服务器B无法处理请求,服务器A会尝试联系服务器C,服务器D,服务器E等
这有效... UNTIL Server B拥有自己的独立客户端,尝试在服务器A尝试联系服务器B的同时联系服务器A.它创建一个colision,两个服务器将无限期地等待获得响应从另一个。使用截止时间计时器我可以避免无限期等待,但它无法解决问题。
这样做的正确方法是什么?
编辑:我将服务器拆分为2个类(Server和PeerProxy),这些类在不同的线程中运行,但我仍然遇到了死锁。
这就是我所做的。我将Unix侦听器和TCP侦听器拆分为类Server和PeerProxy。服务器拥有自己的io_service,PeerProxy也拥有自己的io_service。当Server启动时,它也会启动PeerProxy在第二个线程中运行(因此它不会阻止Server的执行)。数据流现在就像这样:
独立客户端 - >服务器A(无法回答) - > PeerProxy B - >服务器B(得到答案) - > PeerProxy B - >服务器A - >独立客户端
同样的问题当服务器A的独立客户端在服务器A转到PeerProxy B的同时转到PeerProxy A时,我就陷入僵局。
答案 0 :(得分:2)
您应该在服务器中异步处理每个请求,即将处理分离为单独的执行线程。这样服务器保持响应,即他们可以在与其他客户端或服务器通信时对新请求作出反应。
因此,在您的情况下,当两个客户端1和2向服务器A和B发送请求时,只有其他服务器可以应答(或不响应),这两个服务器可能如下所示:
Server A: Server B:
Thread 0 | Thread 1 | Thread 2 Thread 0 | Thread 1 | Thread 2
listen... listen...
-> req 1 -> req 2
listen... | handle req 1 listen... | handle req 2
listen... | forward to B listen... | forward to A
-> req B | wait... -> req A | wait...
listen... | wait... | handle req B listen... | wait... | reject req A
listen... | -> B: rejected | answer req B listen... | wait...
listen... | forward to C listen... | -> A: answer
listen... | -> C: answer listen... | req 2 done
listen... | req 1 done listen...
listen... listen...
这里,每个服务器的线程0除了监听传入请求和处理这些请求的其他线程的旋转之外没有其他目的。其他每个线程都可以通过应答或将其转发给所有服务器来处理一个请求,或者如果它被“污染”则拒绝它。
注意:这些线程根本不一定是真正的线程对象。它们可以是ASIO async * -calls的序列,也可以是某些线程框架(如TBB)中的轻量级任务。
更新:我会发布一些sceleton伪代码,告诉你如何使用Boost.Asio实现服务器。为此,我想产生一个我认为对于理解Asio执行有用的概念:你可以看到它像状态机,其中async_*
调用是状态转换,而处理程序是状态。通常你有一个async_*
- 每个处理程序执行调用,这意味着你从一个状态转到另一个状态。如果在处理程序中有多个后续async_*
- 调用,则表示处理程序正在生成辅助执行线程。如果处理程序不调用任何async_*
函数,则相应的执行线程结束。
现在来实施。
线程0像典型的Asio教程一样,创建一个套接字并监听传入的连接。唯一的事情是在每个新的客户端连接上产生一个新的执行线程(读取:处理程序序列):
accept_handler(client connection) {
async_read(client_request, request_handler) //spawn new thread of execution
async_accept(next client connection, accept_handler) //transition to accept_handler
}
线程N:以request_handler
:
request_handler(client_request) {
if canProcess
async_send_answer(client, done_handler) //transition to done_handler
else //relay request to first server on list
async_connect(first server on list, connect_handler) //transition to connect_handler
}
done_handler
通常会记录成功的答案,不调用另一个async_*
函数,这意味着将关闭与客户端的连接,并且执行的线程结束。 / p>
将请求发送到其他服务器的处理程序序列是典型的connect-send-receive-disconnect序列:
connect_handler -- async_send(request) ---------> send_handler
send_handler -- async_read(answer) ----------> read_handler
read_handler (no answer) -- async_connect(next server) --> connect_handler
如果从其中一个服务器收到答案或者列表中没有其他服务器,则该循环结束:
read_handler (answer ok) -- async_send_answer(client) --> done_handler
read_handler (no more servers) -- async_send_fail(client) ----> done_handler
答案 1 :(得分:0)
这是一个简单的竞争条件。您希望实现某种原子锁变量或信号量或标志,因此如果一个服务器即将向另一个服务器发送请求,它将从那时起拒绝来自其他服务器的任何传入请求。我可能会使用std :: atomic来实现它。