Java网络编程消息协调

4
我有两个运行在不同机器上的进程,它们通过TCP套接字进行通信。
这两个进程都有代码作为服务器和客户端。
例如,ProcessA已经打开了一个服务器套接字,绑定在端口X,而ProcessB已经打开了一个服务器套接字,绑定在端口Y。
ProcessA打开一个客户端套接字来连接ProcessB,并开始发送消息并接收响应(当然是通过相同的TCP连接)。
一旦ProcessB接收到消息并处理它,它会发送响应,但也可以通过第二个TCP连接发送消息,即ProcessB已经打开了一个客户端套接字连接到ProcessA的端口X。
因此,消息流通过2个不同的TCP连接。
我的问题是:假设这个“架构”不能改变且必须保持不变:
我遇到的问题是,间歇性地,从ProcessB发送到ProcessA的消息通过ProcessB打开的客户端套接字的TCP连接到达ProcessA之前,从ProcessB作为响应发送到ProcessA的消息通过ProcessA连接的客户端套接字的TCP连接到达ProcessA。
也就是说,两个消息流同时发生。
(1)  
ProcessA ---->(msg)----> ProcessB(PortY)  (TCP1)
ProcessB does processing   
ProcessB(portY)--->(response)----->ProcessA (TCP1)  
ProcessB--->(msg)----->ProcessA(portX)  (TCP2)

(2)  
ProcessA ---->(msg)----> ProcessB(PortY)  (TCP1)
ProcessB does processing   
ProcessB--->(msg)----->ProcessA(portX)  (TCP2)
ProcessB(portY)--->(response)----->ProcessA  (TCP1)

编辑(ejp请求后):
如何强制/确保ProcessB在从ProcessB的客户端套接字打开到ProcessA的服务器端口X发送消息之前,ProcessB不会通过连接发送消息,直到来自ProcessB的服务器端口Y的回复消息到达ProcessA?即只有上述流程(1)。
请注意,ProcessB是多线程的,并且处理过程并不简单。

更新:
也许这是我的误解,但当进程通过套接字发送数据并返回应用程序控制时,这并不意味着接收方已经接收到数据。
因此,如果一个进程通过2个套接字发送数据,操作系统是否存在竞争条件?

更新2:
在我得到Vijay Mathew的答案后:
如果我像建议的那样进行锁定,操作系统(即IP层)是否保证按顺序发送数据?即完成一个传输,然后发送下一个?还是它们将被多路复用并具有相同的问题?

谢谢


你能澄清一下吗?你的意思是B不应该通过端口X向A发送下一个请求,直到通过端口X收到了A之前的回复?所以端口Y在这里完全无关。 - user207421
@ejp:有两个TCP连接。B不应该在A通过portX(在A中打开)发送先前消息的回复被A接收之前向A发送消息。这样更清楚吗?因此,portY并不是无关紧要的。它是A发送的第一条消息的TCP连接。 - Cratylus
你的段落开头“我如何确保”根本没有提到端口Y。所以它与你刚才说的不一致。请你把这些都调整到一致吗?所以我理解B在端口Y有待处理的回复时,不应该通过端口X发送任何内容。反过来也是这样吗? - user207421
@ejp:我进行了修改。现在好一些吗?是的,你对“B在端口X上有待响应时不应该发送任何内容”这一理解是正确的。反过来则不是必需的,所以不用改。 - Cratylus
3个回答

1

同步问题可能不在TCP协议中,而是在线程处理程序选择何时唤醒消息到达时的线程方面。从您的问题性质来看,我理解PortX“(Msg)”在PortY“(Response)”之后非常快地发送。这意味着线程处理程序偶尔可以选择唤醒其中一个正在侦听的线程。

解决问题的一种简单但丑陋且不完整的方法是在响应和下一条消息之间插入短暂的休眠时间。休眠时间必须足够长,以确信其他进程已经在接收响应之前唤醒。这种方法不完整,因为尽管您正在增加正确同步处理的机会,但操作系统负载和网络拥塞等问题始终可能会将您的消息推回到响应之后。然后你又回到了起点。另一个丑陋的部分是睡眠浪费时间并减少最大吞吐量。只是不那么频繁。所以...

要完全解决此问题,您需要让每个套接字侦听器知道它刚刚接收到的消息是否是要处理的下一条消息,还是可能有更早的消息必须先处理。通过为每个进程发送的所有消息按顺序编号来实现此目的。然后,接收进程就知道是否有任何消息丢失。

你需要想出一种方法,让每个套接字上的侦听器相互协商,以确保接收到的消息按传输顺序进行处理。有许多实际解决方案,但在抽象、概念层面上,它们都归结为同一件事。

线程1: A) ProcessA(PortX)线程接收到消息并唤醒。
B) 如果序列号指示存在丢失的消息,则 B1) 在ProcessA(PortY)上同步并等待()。 B2) 唤醒后,返回B) C) {没有消息丢失} 处理消息。 D) 返回A)

线程2: A) ProcessA(PortY)接收响应并唤醒。 B) 处理响应。 C) 通知所有人()。 D) 返回A)

最通用的实际解决方案可能涉及单个套接字侦听器实例将所有新消息添加到优先级队列中,以便最早发送的消息始终位于队列头部。然后,线程1和线程2都可以在该实例上等待,直到到达可以处理的消息。

一个更简单但不太可扩展的解决方案是让每个线程自己监听和等待(响应)处理程序在处理后发出通知。

祝你好运,虽然经过这么长时间,它可能已经解决了。


1

显而易见的解决方案是:

LockObject lock;

ProcessA ---->(msg)----> ProcessB(PortY)

// Processing the request and sending its response 
// makes a single transaction.
synchronized (lock) {
    ProcessB does processing   
    ProcessB(portY)--->(response)----->ProcessA (TCP1)
}

// While the processing code holds the lock, B is not
// allowed to send a request to A.
synchronized (lock) {
    ProcessB--->(msg)----->ProcessA(portX)  (TCP2)
}

也许这是我的误解,但当 processB 将要通过套接字发送数据时,控制权是否会在数据被另一端接收后从操作系统返回到应用程序?不会的,因此操作系统可以缓冲数据,ProcessB 将移动到第二个锁并通过第二个套接字传递数据。因此仍然会存在两个发送之间的竞争条件,对吗? - Cratylus

0
显而易见的问题是,你为什么在意呢?如果你有需要在两端同步的操作,请自行进行同步。不要期望 TCP 会为你完成这个任务,因为这不是它的作用。

我不确定你建议什么。我想知道的只是,例如,如果我遵循Vijay Mathew的建议,由于数据传输方式的原因,我是否仍然会得到竞态条件。也就是说,操作系统将缓冲数据并将它们多路复用地发送到网络上。 - Cratylus
我想问的是,在你的应用程序中,为什么会首先构成竞态条件。如果在收到消息A的答复之前无法发送消息B,请不要这样做。确保的最简单方法是将它们都放在同一段顺序逻辑中。我不明白的是为什么两个独立的进程会关心彼此的状态。 - user207421
我不能在回复前一个消息 A 之前发送消息 B。这两个单独的过程实现一个单一的功能单元。这样更清晰吗? - Cratylus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接