QT-TCP网络编程
总体认识:
Qt NetWork提供了用于编写TCP/IP网络应用程序的各种类:
TCP的QTcpSocket和QTcpServer
UDP的QUdpSocket
TCP通信:
传输控制协议(transmission control protocol,TCP);可靠的,面向流和连接的传输协议,适合用于连续数据的传输.
通信必须先建立TCP连接;通信端需存在客户端和服务端.
服务端:
服务端必须使用QTcpServer进行端口监听,建立服务器;使用QTcpSocket建立连接,然后使用套接字(socket)进行通信.
QTcpServer的父类是QObject.
服务端程序首先需要使用listen()开始服务器监听,设置服务器监听的IP地址和端口,一般一个服务器只监听某个端口的网络连接.
当有新客户端连入时,QTcpServer的incomingConnection()会创建一个与该客户端连接的QTcpSocket对象,然后发送newConnection()信号;在该槽函数中可以用nextPendingConnection()接受客户端的连接,然后使用对应socket与客户端通信.
QTcpSocket是从QIODevice间接继承的类,因此是一种I/O设备类;
QTcpSocket其他函数都是从其父类QAbstractSocket继承或重定义的;
客户端:
客户端通过QTcpSocket对象与服务器建立连接并保持通信;客户端socket首先通过connectToHost()尝试连接服务器,需指定服务器IP地址和监听端口,该函数以异步方式连接到服务器,不会阻塞程序运行,连接成功后QTcpSocket发送connected()信号.
客户端与服务器建立连接后,各自的socket对象就可以向缓冲区写数据或读数据,当缓冲区有新数据进入时,socket会发送readyRead()信号,一般在对应槽函数读取缓冲区数据.
读取数据与写入数据的缓冲区是分开的,异步工作,不会冲突.
项目:局域网内部通信部分代码展示与解读
服务端:
//初始化服务端
void WidgetManager::initTCPServer()
{
QString localIP = getLocalIP_server(); //获取服务端需要的本地IP
tcpserver = new QTcpServer(this); //基于本类创建的tcp server对象,该通信程序只同时存在一个服务端
connect(tcpserver,SIGNAL(newConnection()),this,SLOT(do_newConnection())); //将QTcpServer的newConnection信号与槽函数连接,当出现新客户端连入时触发自动调用处理
}
服务端的QTcpSocket对象初始化设置为nullptr不指向,后续用作虚拟客户端的对象;
//服务端获取本地IP
QString WidgetManager::getLocalIP_server()
{
QString hostName = QHostInfo::localHostName(); //本地主机名
QHostInfo hostInfo = QHostInfo::fromName(hostName); //返回对应主机名的IP地址
QString localIP = "";
QList<QHostAddress> addrList = hostInfo.addresses(); //本机的IP地址列表
if(addrList.isEmpty())
return localIP;
foreach (QHostAddress aHost, addrList) {
if(QAbstractSocket::IPv4Protocol == aHost.protocol()){
localIP = aHost.toString(); //查找IPv4协议的IP地址
break;
}
}
return localIP;
}
void WidgetManager::serverStartListen(QString serverIP,QString serverPort)
{
QString IP = serverIP; //本机监听为127.0.0.1,也可以是QHostAddress::LocalHost
quint16 port = serverPort.toInt();
QHostAddress address(IP); //IP转换为QHostAddress用于参数
tcpserver->listen(address,port); //调用listen开始监听
tcp_server_textfield_append("***开始监听 :"+tcpserver->serverAddress().toString()+" 端口: "+QString::number(tcpserver->serverPort()));
tcp_server_state_changed(QString("监听状态 : 正在监听"));
}
void WidgetManager::serverStopListen()
{
if(tcpserver->isListening()) //正在监听
{
if(tcpserver_socket != nullptr) //socket对象存在
{
if(tcpserver_socket->state() == QAbstractSocket::ConnectedState) //状态为已连接
{
tcpserver_socket->disconnectFromHost(); //虚拟客户端从服务端断开
}
}
tcpserver->close(); //关闭服务端,此处为停止服务端本身的监听行为
tcp_server_state_changed(QString("监听状态 : 停止监听"));
}
}
void WidgetManager::do_newConnection()
{
tcpserver_socket = tcpserver->nextPendingConnection(); //接受新连接
connect(tcpserver_socket,SIGNAL(connected()),this,SLOT(do_clientConnected())); //连接函数与槽函数绑定
do_clientConnected(); //槽函数试运行,同时让假定服务端创建的虚拟客户端连接
connect(tcpserver_socket,SIGNAL(disconnected()),this,SLOT(do_clientDisconnected())); //断连函数与槽函数绑定
connect(tcpserver_socket,&QTcpSocket::stateChanged,this,&WidgetManager::do_socketStateChanged); //状态检测函数与槽函数绑定
do_socketStateChanged(tcpserver_socket->state()); //试运行确定连接状态
connect(tcpserver_socket,SIGNAL(readyRead()),this,SLOT(do_socketReadyRead())); //readyRead函数绑定槽函数,缓冲区存在数据后即可触发
}
void WidgetManager::do_socketStateChanged(QAbstractSocket::SocketState socketState)
{
switch (socketState) {
case QAbstractSocket::UnconnectedState:
tcp_server_state_changed("socket 状态: UnconnectedState"); break;
case QAbstractSocket::HostLookupState:
tcp_server_state_changed("socket 状态: HostLookupState"); break;
case QAbstractSocket::ConnectingState:
tcp_server_state_changed("socket 状态: ConnectingState"); break;
case QAbstractSocket::BoundState:
tcp_server_state_changed("socket 状态: BoundState"); break;
case QAbstractSocket::ClosingState:
tcp_server_state_changed("socket 状态: ClosingState"); break;
case QAbstractSocket::ListeningState:
tcp_server_state_changed("socket 状态: ListeningState"); break;
}
}
void WidgetManager::do_clientDisconnected()
{
tcp_server_textfield_append("**client socket disconnected");
tcpserver_socket->deleteLater(); //删除服务端的socket对象
}
客户端:
//初始化客户端
void WidgetManager::initTCPClient()
{
tcpClient = new QTcpSocket(this);
QString localIP = getLocalIP_client();
// 保留IP显示状态
connect(tcpClient,SIGNAL(connected()),this,SLOT(do_connected())); //槽函数连接情况与服务端基本一致
connect(tcpClient,SIGNAL(disconnected()),this,SLOT(do_disconnected()));
connect(tcpClient,&QTcpSocket::stateChanged,this,&WidgetManager::do_socketStateChange);
connect(tcpClient,SIGNAL(readyRead()),this,SLOT(do_socketReadyReadClient()));
}
void WidgetManager::clientStartConnect(QString address, QString port)
{
QString addr = address;
quint16 prt = port.toInt();
tcpClient->connectToHost(addr,prt); //客户端尝试连接服务端,由服务端决策连接结果
}
void WidgetManager::clientStopConnect()
{
if(tcpClient->state() == QAbstractSocket::ConnectedState)
tcpClient->disconnectFromHost(); //客户端主动断开,服务端会有响应
}
void WidgetManager::clientSendData(QString data)
{
QString msg = data;
QByteArray str = msg.toUtf8();
str.append('\n');
tcpClient->write(str);
tcpClient->flush(); //写入即发送的刷新函数
}
其余函数与上服务端一致,另编写见完整代码;
此版于2023/10/1 10:56更新,参考QT6开发指南