Qt下的TCP通信

Qt下的TCP通信主要涉及两个类,QTcpServer和QTcpSocket,两个类看起来都是和Tcp相关,但两者继承自完全不同的类。注意两个类在使用的时候,都需要在qmake中添加 QT +=network。
QTcpServer主要继承自QObject,主要是用到QObject用到的信号和槽,而QTcpSocket则继承自QAbstractSocket,而QAbstractSocket继承自QIODevice,从继承的关系就可以看出QTcpServer主要是用于接受客户端的连接,并且返回QTcpSocket,而QTcpSocket主要负责具体的Socket的读写工作,当然也需要具体的工作在QTcpSocket中间插入相应的处理逻辑。

QTcpServer主要API

QTcpServer建立一个服务器非常的简单,回忆一下传统建立服务器的方法是(在Linux系统下)调用顺序是:socket(),bind(),listen(),accept(),而QTcpServer绑定IP地址和端口只需要一个API:

bool 
listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0)

QHostAddress是QT提供的指定IP地址的一个类,可以直接使用字符串实例化,默认的参数QHostAddress:Any相当于Linux下的INADDR_ANY参数,是绑定系统下的所有IP地址;
port是端口号,端口号为quint16,范围是0~65535,同时不要注意不要占用特殊的端口号;
QTcpServer有两种监听客户端的方式:堵塞和非堵塞。堵塞方式涉及的API如下:

bool QTcpServer::waitForNewConnection(int msec = 0, bool *timedOut = nullptr)

堵塞API的缺点在于这个函数会堵塞整个线程,如果整个程序只有一个线程,那个这个函数会导致GUI界面无法及时相应用户的输入,因此不推荐使用。
另外一种方式是非阻塞的方式,通过QT的信号-槽的方式,当QTcpServer发现有新连接时,会主动发送信号,

[signal] void QTcpServer::newConnection()

我们只需要将信号连接到对应的处理槽函数即可,然后再对应的槽函数中调用下面的API,就可以得到用于接受和发送信息的QTcpSocket;

virtual QTcpSocket *nextPendingConnection()

如果两个Socket之间是短连接,那么在处理完一次相应信息之后,连接就可以关闭;而如果两个Socket是长连接,那么可以使用一个容器比如QList,把对应的socket保存起来,用于轮询每个QTcpSocket处理信息。

QTcpSocket对应的API

QTcpSocket主要有两个作用,一个作用是用于客户端中,同时负责连接服务器和用于收发信息,而用于服务端,前文已经提到主要用于收发信息。
连接服务器的API主要有:

//用于绑定服务端地址和端口号
bool QAbstractSocket::bind(const QHostAddress &address, quint16 port = 0, QAbstractSocket::BindMode mode = DefaultForPlatform)
bool QAbstractSocket::bind(quint16 port = 0, QAbstractSocket::BindMode mode = DefaultForPlatform)
//用于发起连接
[virtual] void QAbstractSocket::connectToHost(const QString &hostName, quint16 port, QIODevice::OpenMode openMode = ReadWrite, QAbstractSocket::NetworkLayerProtocol protocol = AnyIPProtocol)
[virtual] void QAbstractSocket::connectToHost(const QHostAddress &address, quint16 port, QIODevice::OpenMode openMode = ReadWrite)

可以使用QAbstractSocket提供的信号或者函数确定是否连接成功,主要涉及下面的API:

[signal] void QAbstractSocket::connected()
[signal] void QAbstractSocket::disconnected()
//获取QTcpSocket的State
QAbstractSocket::SocketState QAbstractSocket::state() const
/*
QAbstractSocket::UnconnectedState	0	the socket is not connected.
QAbstractSocket::HostLookupState	1	The socket is performing a host name lookup.
QAbstractSocket::ConnectingState	2	The socket has started establishing a connection.
QAbstractSocket::ConnectedState		3	A connection is established.
QAbstractSocket::BoundState			4	The socket is bound to an address and port.
QAbstractSocket::ClosingState			6	The socket is about to close (data may still be waiting to be written).
QAbstractSocket::ListeningState		5	For internal use only.
*/

QTcpSocket在任何时候,使用从QIODevice继承的接口发送数据,由于在网络中通常以字节流的方式发送数据,因此在Qt中推荐将发送和接收的信息存储在QByteArray中,这样会比 char buf[BUF_SIZE]更加省力,也能更好的使用Qt提供的接口。

qint64 write(const char *data, qint64 maxSize)
qint64 write(const char *data)
qint64 write(const QByteArray &byteArray)

QTcpSocket读数据相对复杂一些,因为发是随时可以发的,但是读需要提前连接相应的信号槽,然后在槽函数中进行逻辑处理。信号如下:

[signal] void QIODevice::readyRead()

之后使用下面的API进行读取数据

qint64 read(char *data, qint64 maxSize)
QByteArray read(qint64 maxSize)
QByteArray readAll()

推荐使用第三种读取方式,因为QT的帮助文档中说,[signal] void QIODevice::readyRead()这个信号只有在新数据来临时才会触发,而不是只要系统的缓冲区有数据就触发。这相当于如果只读一次,但这一次没有将缓冲区中的数据读干净,那么这部分数据不会再次触发这个信号;而如果读多次,会导致下一次首先会读到上次没有读干净的内容,很容易影响数据的解析,所以推荐使用readAll()函数,可以直接一次将所有缓冲区的数据读干净。