使用Qt实现TCP双向通信

3
我正在尝试在两台计算机之间建立一个TCP通信框架。我希望每台计算机都能向另一台发送数据。因此,计算机A将进行计算,并将其发送到计算机B。计算机B将读取这些数据,使用它进行计算,并将结果发送回计算机A。计算机A将等待从计算机B收到数据后才会继续执行下一个计算并将其发送到计算机B。
这个概念理解起来很简单,但是我无法找到一个详细描述TCP双向(双向)通信的示例。我只找到了单向服务器-客户端通信的例子,其中服务器向客户端发送数据。以下是我目前已经仔细研究的一些示例: 我基本上希望有两个“服务器”相互通信。我认为上面的同步方法对于我要做的事情非常重要。但是我正在努力通过单个套接字设置双向通信框架。
如果有人可以指向描述如何设置使用TCP进行双向通信的示例,或者给我一些有关如何设置这个的指针,从我上面链接的示例开始。我对TCP和网络通信框架非常陌生,很可能有很多我误解的地方,因此,如果我能得到有关如何继续的明确指针,那将是非常棒的。

2
为什么要区分“服务器”和“客户端”?一方必须发起连接。一旦TCP连接打开,任何一方都可以在其上发送和接收数据。 - TessellatingHeckler
我不知道Qt或C++,但这看起来很有前途:http://stackoverflow.com/questions/16520744/unable-to-establish-two-way-communication-using-qt - LessQuesar
"服务器-客户端"通信是双向的。服务器和客户端都能够发送和接收数据。您试图做什么而不起作用? - ThatOneDude
4个回答

4
这个答案没有深入探讨细节,但应该能够给你一个大致的想法,因为这似乎是你真正想知道的。我以前从未使用过Qt,我直接使用BSD风格的套接字或我的自己的包装来编写所有网络代码。
需要考虑的事情:
  • 协议。手动编写还是使用现有的?
    • 现有的协议可能会很重,这取决于你的有效载荷的大小。例如HTTP和Google ProtoBuf; 还有更多。
    • 手动编写可能意味着更多的工作,但更可控。有两种通用方法:基于长度和基于标志。
      • 基于长度意味着将长度嵌入到前几个字节中。需要关心字节序。需要考虑消息是否比可以嵌入长度字节中的长度更长。如果您这样做,我强烈建议您在某个数据文件中定义您的数据包格式,然后生成低级数据包编码逻辑。
      • 基于标志意味着当看到某个字符(或序列)时结束消息。常见的标志是'\0''\n'"\r\n"。如果您的协议的其余部分也是基于文本的,则这意味着调试要容易得多。
      • 对于这两种设计,您必须考虑对方尝试发送比您愿意(或能够)存储在内存中的数据更多的情况。在任何一种情况下,将有效载荷大小限制为16位无符号整数可能是一个好主意;您可以使用多个数据包流式传输回复。请注意,严重的协议(基于UDP +加密)通常具有512-1500字节的协议层大小限制,当然应用层可能更大。
      • 对于这两种设计,没有标志的套接字上的EOF意味着您必须删除消息并记录错误。
  • 主循环。Qt可能有您可以使用的主循环,但我不知道。
    • 仅使用阻塞操作开发简单操作是可能的,但我不建议这样做。始终假设网络连接的另一端是一个危险的精神病患者,他知道您住在哪里。
    • 主循环中有两个基本操作:
      • 套接字事件:套接字报告准备好读取或写入。还有其他类型的事件,您可能不会使用,因为大多数有用的信息可以分别在读/写处理程序中找到:异常/优先级,(写入)挂起,读取挂起,错误。
      • 计时器事件:当经过一定的时间间隔时,中断等待套接字事件系统调用并分派到计时器堆。如果您没有任何计时器,则将其视为“无穷大”。但是,如果您有很长的睡眠时间,则可能需要一些任意的、相对较小的数字,例如“ 10秒”或“ 10分钟”,具体取决于您的应用程序,因为长计时器间隔可能会导致各种奇怪的时钟更改,休眠等问题。如果您足够小心并使用正确的API,则可以避免这些问题,但大多数人都不会。
    • 多路复用系统调用的选择:
      • 下面的p版本包括原子信号掩码更改。我不建议使用它们;如果需要信号,请将signalfd添加到集合中,否则请使用信号处理程序和(非阻塞,要小心!)管道模拟它。
      • 编辑于2019年:

        D-Bus和0MQ的文档值得一读,不管你是否使用它们。特别是,值得考虑3种类型的对话:

        • 请求/响应:一个“客户端”发出请求,“服务器”会做三件事情之一:1.有意义地回复,2.回复说它不理解该请求,3.无法回复(由于断开连接或由于错误/敌对的服务器)。不要让未被确认的请求DoS “客户端”!这可能很困难,但这是一个非常普遍的工作流。
        • 发布/订阅:一个“客户端”告诉“服务器”它对某些事件感兴趣。每当事件发生时,“服务器”就向所有已注册的“客户端”发布一条消息。变化:,订阅在一次使用后到期。此工作流的故障模式比请求/响应简单,但请考虑:1.服务器发布了客户端没有要求的事件(因为它不知道,或者因为它还不想要它,或者因为它应该是一次性的,或者因为客户端发送了取消订阅但服务器尚未处理),2.这可能是一个放大攻击(虽然请求/响应也可能如此,请考虑要求请求填充),3.客户端可能已断开连接,因此服务器必须注意取消订阅它们,4.(特别是使用UDP时)客户端可能没有收到早期通知。请注意,单个客户端多次订阅可能是完全合法的;如果没有自然区分数据,您可能需要保留一个cookie来取消订阅。
        • 分发/收集:一个“主服务器”将工作分配给多个“从服务器”,然后收集结果,也称为映射/归约和许多其他重新发明的术语。这类似于上面的组合(客户机订阅可用工作事件,然后服务器向每个客户机发送唯一请求而不是普通通知)。请注意以下其他情况:1.某些从服务器非常慢,而其他从服务器空闲,因为它们已经完成了任务,主服务器可能不得不存储不完整的组合输出,2.某些从服务器可能返回错误答案,3.可能没有任何从服务器,4。

        特别是D-Bus在最初似乎做出了很多奇怪的决定,但有理由(取决于用例)可以证明其正确性。通常,它仅在本地使用。

        0MQ是低级别的,大部分“缺点”都可以通过在其上构建来解决。请注意MxN问题;您可能需要人为地创建一个代理节点,专门处理易受其影响的消息。


3
#include <QAbstractSocket>
#include <QtNetwork>
#include <QTcpServer>
#include <QTcpSocket>

QTcpSocket*   m_pTcpSocket;

连接主机:使用TCP套接字建立连接并实现您的插槽。如果有可用的数据字节,将发出readyread()信号。

void connectToHost(QString hostname, int port){
    if(!m_pTcpSocket)
{
    m_pTcpSocket = new QTcpSocket(this);
    m_pTcpSocket->setSocketOption(QAbstractSocket::KeepAliveOption,1);
}
connect(m_pTcpSocket,SIGNAL(readyRead()),SLOT(readSocketData()),Qt::UniqueConnection);
connect(m_pTcpSocket,SIGNAL(error(QAbstractSocket::SocketError)),SIGNAL(connectionError(QAbstractSocket::SocketError)),Qt::UniqueConnection);
connect(m_pTcpSocket,SIGNAL(stateChanged(QAbstractSocket::SocketState)),SIGNAL(tcpSocketState(QAbstractSocket::SocketState)),Qt::UniqueConnection);
connect(m_pTcpSocket,SIGNAL(disconnected()),SLOT(onConnectionTerminated()),Qt::UniqueConnection);
connect(m_pTcpSocket,SIGNAL(connected()),SLOT(onConnectionEstablished()),Qt::UniqueConnection);

if(!(QAbstractSocket::ConnectedState == m_pTcpSocket->state())){
    m_pTcpSocket->connectToHost(hostname,port, QIODevice::ReadWrite);
}
}

写:

void sendMessage(QString msgToSend){
QByteArray l_vDataToBeSent;
QDataStream l_vStream(&l_vDataToBeSent, QIODevice::WriteOnly);
l_vStream.setByteOrder(QDataStream::LittleEndian);
l_vStream << msgToSend.length();
l_vDataToBeSent.append(msgToSend);

m_pTcpSocket->write(l_vDataToBeSent, l_vDataToBeSent.length());
}

阅读:

void readSocketData(){
while(m_pTcpSocket->bytesAvailable()){
    QByteArray receivedData = m_pTcpSocket->readAll();       
}
}

谢谢!这非常有帮助。我已经实现了所有插槽,现在正在尝试测试我的代码。我在计算机上运行了两个相同应用程序的实例,并连接到同一个主机(即QHostAddress :: Broadcast [255.255.255.255])。当我连接时,我会收到以下消息序列:“套接字已开始建立连接。”->“套接字未连接。”->“权限被拒绝。”您有任何想法为什么会发生这种情况吗?如果可以帮助的话,我可以发布我的代码。再次感谢! - mtwister
我注意到上面的代码片段包括QTcpServer... 我需要在我的代码中添加它吗?我目前只有一个socket... - mtwister
你需要在TCP服务器上监听传入的连接,以建立连接。server = new QTcpServer(this); connect(server, SIGNAL(newConnection()), this, SLOT(newConnection())); server->listen(QHostAddress::Any, port);void newConnection() { QTcpSocket *socket = server->nextPendingConnection(); } - Wee

2

TCP本质上是双向的。先让一端工作(客户端连接到服务器),之后两端都可以使用相同的方式发送和接收数据。


感谢您的帮助。当我从服务器端进行连接时,我会按照以下方式“connect”:connect(this,& LocalServer :: newConnection,[&](){mSocket = nextPendingConnection();}); 而从客户端连接:connect(mLocalServer-> mSocket,& QTcpSocket :: readyRead,[&](){QTextStream T(mLocalServer-> mSocket); ui->listWidget-> addItem(T.readAll());}); 我是否有两个“connect”语句指定服务器和客户端的函数? 我认为这就是我在如何实现发送/接收功能上感到困惑的地方... 再次感谢。 - mtwister
你有一个(Q)TcpSocket,可以从中进行“读取”和“写入”。一旦建立连接,您可以根据需要自由地进行读写操作。定义一些协议,例如您在问题中描述的那样,并实现它。从您提供的链接文章来看,也许http://www.bogotobogo.com/Qt/Qt5_QTcpSocket_Signals_Slots.php会有所帮助。 - ThatOneDude

0

看一下 QWebSocket,它基于 HTTP,并且还允许使用 HTTPS。


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