使用Qt的TCP双向通信

时间:2015-06-25 00:45:41

标签: c++ qt sockets tcp

我正在尝试在两台计算机之间设置TCP通信框架。我希望每台计算机都能将数据发送给另一台计算机。因此,计算机A将执行计算,并将其发送到计算机B.计算机B然后将读取该数据,使用它执行计算,并将结果发送回计算机A.计算机A将等到它从计算机B接收到某些东西之前继续执行另一次计算,并将其发送到计算机B.

这似乎在概念上很简单,但我无法找到一个通过TCP详细说明双向(双向)通信的示例。我只找到了单向服务器 - 客户端通信,服务器将数据发送到客户端。这些是我到目前为止密切关注的一些例子:

我基本上希望有两个“服务器”相互通信。我相信上面的同步方法对于我正在尝试做的事情很重要。但我正在努力通过单个插槽设置双向通信框架。

如果有人可以指出我描述如何设置与TCP的双向通信的示例,或者从我上面链接的示例中给出一些关于如何设置它的指示,我将非常感激。我对TCP和网络通信框架都很陌生,可能有很多我可能会误解,所以如果我能得到一些关于如何继续的明确指示会很棒。

4 个答案:

答案 0 :(得分:2)

TCP本质上是双向的。获得一种方式(客户端连接到服务器)。之后,两端都可以完全相同的方式使用send和recv。

答案 1 :(得分:2)

#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();       
}
}

答案 2 :(得分:2)

这个答案不会进入细节,但它应该给你一个大致的想法,因为那是你真正想要的。我之前从未使用过Qt,我直接使用BSD风格的套接字或使用自己的包装器来完成所有网络代码。

考虑的事情:

  • 协议。手卷还是现有的?
    • 现有协议可以是重量级的,具体取决于您的有效负载的样子。示例包括HTTP和Google ProtoBuf;还有很多
    • 手动可能意味着更多的工作,但更多的控制。有两种通用方法:基于长度和基于哨兵。
      • 基于长度意味着将长度嵌入到第一个字节中。需要关心字节序。需要考虑一条消息是否比长度字节中嵌入的更长的内容。如果这样做,我强烈建议您在某个数据文件中定义数据包格式,然后生成低级数据包编码逻辑。
      • 基于Sentinel意味着在看到某个字符(或序列)时结束消息。常见的哨兵是'\0''\n'"\r\n"。如果您的协议的其余部分也是基于文本的,这意味着它更容易调试。
      • 对于这两种设计,您必须考虑如果另一方尝试发送的数据超出您愿意(或能够)存储在内存中的数据,会发生什么。在任何一种情况下,将有效负载大小限制为16位无符号整数可能是一个好主意;您可以使用多个数据包传输回复。请注意,严重协议(基于UDP +加密)通常具有512-1500字节的协议层大小限制,但应用层当然可能更大。
      • 对于这两种设计,套接字上的EOF没有具有标记意味着您必须删除消息并记录错误。
  • 主循环。 Qt可能有一个你可以使用的,但我不知道。
    • 可以使用单独的阻止操作开发简单的操作,但我不推荐它。 始终假设网络连接的另一端是一个危险的精神病患者,他知道你住在哪里。
    • 主循环中有两个基本操作:
      • 套接字事件:套接字报告已准备好进行读取或准备写入。您可能还会使用其他类型的事件,因为大多数有用的信息可以在读/写处理程序中单独找到:例外/优先级,(写)挂起,读取挂起,错误。
      • 计时器事件:当某个时间增量已经过去时,中断wait-for-socket-events系统调用并调度到计时器堆。如果您没有,请通过系统调用&#34; infinity&#34;的概念。但是,如果你长时间睡觉,你可能想要一些任意的,相对数字的&#34; 10秒&#34;或者&#34; 10分钟&#34;取决于您的应用程序,因为长时间间隔可以通过时钟更改,休眠等来执行各种奇怪的操作。如果您足够小心并使用正确的API,但大多数人都不会这样做,可以避免这些。
    • 多路系统调用的选择:
      • 以下p版本包括原子信号掩码更改。我不建议使用它们;相反,如果您需要信号,请将signalfd添加到集合中,或者使用信号处理程序和(非阻塞,小心!)管道来模拟它。
      • select / pselect是经典之作,随处可见。不能有超过FD_SETSIZE个文件描述符,这些描述符可能非常小(但如果你足够小心,可以在命令行上#define d。使用稀疏集合效率低。超时为微秒selectpselectpoll的纳秒时间,但您可能无法实现这一点。只有在没有其他选择的情况下才能使用此功能。
      • ppoll / FD_SETSIZE解决了稀疏集的问题,更重要的是解决了超过poll个文件描述符的问题。它确实使用了更多内存,但使用起来更简单。 ppoll是POSIX,epoll是特定于GNU的。对于两者而言,API为超时提供纳秒粒度,但您可能无法获得该超时。如果您需要BSD兼容性并且不需要大规模的可扩展性,或者如果您只有一个套接字并且不想处理epoll的麻烦,我建议您这样做。
      • epoll解决了每次都必须重新指定文件描述符和事件列表的问题。通过保留文件描述符列表。除此之外,这意味着当发生低级内核事件时,无论用户程序是否已经在系统调用中,都可以使poll 立即知道。支持边缘触发模式,但除非您确定了解它,否则不要使用它。它的API只为超时提供毫秒级的粒度,但无论如何,这可能都是你可以依赖的。如果你只能定位Linux,我强烈建议你使用它,除非你可以一次只保证一个套接字,在这种情况下kqueue更简单。
      • epoll位于BSD派生系统上,包括Mac OS X.它应该解决与O_NONBLOCK相同的问题,但不是通过使用文件描述符来保持简单,而是全部各种奇怪的结构,并没有遵循&#34;只做一件事&#34;原理。我从来没用过它。如果您需要在BSD上实现大规模可扩展性,请使用此选项。
      • IOCP。这只存在于Windows和一些不起眼的Unixen上。我从未使用它,它具有明显不同的语义。使用此,但要注意,这篇文章的大部分内容都不适用,因为Windows很奇怪。但是,为什么要将Windows用于任何严肃的系统?
      • io_uring。 Linux 5.1中的新API。显着减少系统调用和内存副本的数量。值得一提的是,如果你有很多套接字,但由于它是如此新的,你必须提供一个后备路径。
    • 处理程序实现:
      • 当多路复用系统调用表示事件时,查找该文件编号的处理程序(某些具有虚函数的类)并调用相关事件(注意可能有多个事件)。
      • 确保所有套接字都已设置connect并禁用Nagle的算法(因为您自己正在进行缓冲),除了accept之前可能{建立了连接,因为这需要混淆逻辑,特别是如果你想要使用多个DNS结果。
      • 对于TCP套接字,您需要的只是listen套接字处理程序中的read/writeaccept / {{1}中的connect系列ed处理程序。对于其他类型的套接字,您需要send/recv系列。请参阅&#34;另见&#34;在他们的手册页中获取更多信息 - 有时候其中一个对你有用,在之前执行此操作你在API设计中硬编码太多了。
      • 你需要认真考虑缓冲。缓冲读取意味着您需要能够检查数据包的标头,以查看是否有足够的字节来对其执行任何操作,或者您是否必须将字节存储到下次。另外请记住,您可能会同时收到多个数据包(我建议您重新考虑您的设计,以便在发送下一个数据包之前得到回复之前不要 阻止阻止)。缓冲写入比你想象的更难,因为你想要在有&#34;可以写&#34;即使在没有数据写入的套接字上也是如此。应用程序永远不应该自己编写,只编写一个写入队列。虽然TCP_CORK可能意味着不同的设计,但我还没有使用它。
    • 提供迭代所有套接字的网络级公共API。如果需要,在更高级别实施;请记住,您可能拥有各种具有特殊用途的内部文件描述符。
  • 以上所有内容适用于服务器和客户端。正如其他人所说,一旦建立连接就没有真正的区别。

编辑2019:

无论您是否使用,D-Bus和0MQ的文档都值得一读。特别值得考虑的是3种对话:

  • 请求/回复:a&#34;客户&#34;提出请求和&#34;服务器&#34;执行以下三项操作之一:1。有意义地回复,2。回复它不理解请求,3。未能回复(由于断开连接,或由于错误/恶意服务器)。不要让未经承认的请求DoS&#34;客户&#34;!这可能很困难,但这是非常常见的工作流程。
  • 发布/订阅:&#34;客户&#34;告诉&#34;服务器&#34;它对某些事件感兴趣。每次事件发生时,&#34;服务器&#34;向所有注册的&#34;客户&#34;发布消息。变化:,订阅在一次使用后到期。此工作流程具有比请求/回复更简单的故障模式,但考虑:1。服务器发布客户端没有要求的事件(或者因为它不知道,或者因为它没有想要它尚未,或者因为它应该是一个单击,或者因为客户端发送了取消订阅但是服务器还没有处理它),2。这可能是放大攻击(虽然这也可以用于请求/回复,但考虑要求填写请求),3。客户端可能已断开连接,因此服务器必须注意取消订阅它们,4。(特别是如果使用UDP)客户端可能没有收到较早的通知。请注意,单个客户端多次订阅可能是完全合法的;如果没有自然歧视的数据,您可能需要保留一个cookie才能取消订阅。
  • 分发/收集:a&#34; master&#34;将工作分配给多个&#34;奴隶&#34;,然后收集结果,即为同一事物映射/减少任何其他重新发明的术语。这类似于上述的组合(客户端订阅工作可用事件,然后服务器向每个客户端发出唯一请求而不是正常通知)。请注意以下附加情况:1。某些从属设备非常慢,而其他从属设备处于空闲状态,因为它们已经完成了任务,主设备可能必须存储不完整的组合输出,2。某些从设备可能会返回错误的答案, 3.可能没有任何奴隶,4。

D-Bus特别做出了很多看起来很奇怪的决定,但确实有理由(根据用例可能相关或不相关)。通常,它仅在本地使用。

0MQ是较低级别的,其大多数是&#34;缺点&#34;通过建立它来解决。小心MxN问题;您可能希望人为地创建一个代理节点,仅用于容易发生的消息。

答案 3 :(得分:0)

看看QWebSocket,这是基于HTTP的,它也允许HTTPS