QUdpSocket和QTcpSocket的使用

本文详细介绍了QUdpSocket和QTcpSocket的工作原理及使用方法,包括UDP的无连接特性、数据报的读写操作,以及TCP的连接建立过程、数据传输控制等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载自 http://www.lai18.com/content/606492.html

一、QUdpSocket


1、UDP是一种基于无连接的、不可靠的数据报传输协议。

2、套接字可以当作一种输入输出设备,QUdpSocket可以调用writeDatagram()和readDatagram()对套接字进行读写。每当一次数据报写入完成后会释放bytesWritten()信号。

3、QUdpSocket在读之前要先调用bind()函数进行绑定,如果仅仅只是写的话则无需绑定。

4、当有数据报可读时,QUdpSocket会发出readyRead()信号,可以通过定义关联该信号的槽函数,对数据进行读取。此时hasPendingDatagrams()也会返回true,pendingDatagramSize()可以用于获取数据报长度,然后调用读函数进行数据的读取。

5、QUdpSocket receiver端使用joinMulticastGroup将groupAddress作为参数,在sender端writeDatagram()时写入到groupAddress

void Server::initSocket()  
{  
    udpSocket = new QUdpSocket(this);  
    udpSocket->bind(QHostAddress::LocalHost, 7755);  
  
    connect(udpSocket, SIGNAL(readyRead()),  
            this, SLOT(readPendingDatagrams()));  
}  
  
void Server::readPendingDatagrams()  
{  
    while (udpSocket->hasPendingDatagrams()) {  
        QByteArray datagram;  
        datagram.resize(udpSocket->pendingDatagramSize());  
        QHostAddress sender;  
        quint16 senderPort;  
  
        udpSocket->readDatagram(datagram.data(), datagram.size(),  
                                &sender, &senderPort);  
  
        processTheDatagram(datagram);  
    }  
}



二、QTcpSocket

1、TCP协议是面向连接的、可靠的传输协议

2、QTcpSocket在使用之前,要先调用connectToHost()和目的主机建立连接,数据传输结束后要调用disconnectFromHost()断开连接。连接建立之后会发送connected()信号,连接断开之后会发送disconnected()信号。

3、当有数据可读时会发出readyRead()信号,可以通过byteAvailable()函数获取可读取的字节数。

4、可以调用write()和read()函数对套接字进行读写。

5、服务器编程时,一个服务器可能会连接到好几个客户端,此时可以调用setSocketDescriptor()协议设置套接字描述符。

6、abort()函数,和disconnectFromHost()函数功能类似,abort()会立即关闭套接字,并丢弃可读取的字节。

void Server::InitServer()
{
    tcpServer = new QTcpServer(this);
    tcpServer->listen();//默认监听 QHostAddress::Any,port为0时端口将被自动选择
    //通过tcpServer->serverAddress(),tcpServer->serverPort()获取ip,port
    connect(tcpServer, SIGNAL(newConnection()), this, SLOT(sendFortune()));
}
void Server::sendFortune()
{
    QByteArray block;
    QDataStream out(&block, QIODevice::WriteOnly);
    out.setVersion(QDataStream::Qt_4_0);
    out << (quint16)0;//
    out << fortunes.at(qrand() % fortunes.size());
    out.device()->seek(0);
    out << (quint16)(block.size() - sizeof(quint16));
    // 注意这个QTcpSocket对象由tcpSetver创建,这意味着clientConnection是作为tcpServer的子对象存在
    QTcpSocket *clientConnection = tcpServer->nextPendingConnection();
    connect(clientConnection, SIGNAL(disconnected()),
            clientConnection, SLOT(deleteLater()));
    clientConnection->write(block);
    clientConnection->disconnectFromHost();
}
//为了保证在客户端能接收到完整的文件,我们都在数据流的最开始写入完整文件的大小信息,这样客户端就可以根据大小信息来判断是否接受到了完整的文件。而在服务器端,我们在发送数据时就要首先发送实际文件的大小信息,但是,文件的大小一开始是无法预知的,所以我们先使用了out<<(quint16) 0;在block的开始添加了一个quint16大小的空间,也就是两字节的空间,它用于后面放置文件的大小信息。
当文件输入完成后我们在使用out.device()->seek(0);返回到block的开始,加入实际的文件大小信息,也就是后面的代码,它是实际文件的大小:out<<(quint16) (block.size() – sizeof(quint16));
void Client::InitClient()
{  
    tcpSocket = new QTcpSocket(this);
    connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(readFortune()));
    connect(tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)),this, SLOT(displayError(QAbstractSocket::SocketError)));
}
void Client::requestNewFortune()
{
    blockSize = 0;
    tcpSocket->abort();
    tcpSocket->connectToHost(IP,port);
}

void Client::readFortune()
{
    QDataStream in(tcpSocket);
    in.setVersion(QDataStream::Qt_4_0);
    if (blockSize == 0) {
        if (tcpSocket->bytesAvailable() < (int)sizeof(quint16))//数据包大小小于两个字节
            return;
        in >> blockSize;
    }
    if (tcpSocket->bytesAvailable() < blockSize)
        return;
    QString nextFortune;
    in >> nextFortune;
}

继承QTcpServer类实现incomingConnection
void FortuneServer::incomingConnection(qintptr socketDescriptor)
{
    QString fortune = fortunes.at(qrand() % fortunes.size());
    FortuneThread *thread = new FortuneThread(socketDescriptor, fortune, this);
    connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
    thread->start();
}
FortuneThread派生自QThread实现run函数
void FortuneThread::run()
{
	QTcpSocket tcpSocket;
	if(!tcpSocket->setSocketDescriptor(socketDescriptor))
	{
		emit error(tcpSocket.error());
        return;
	}
	QByteArray block;
	QDataStream out(&block,QIODevice::WriteOnly);
	out.setVersion(QDataStream::Qt_4_0);
	out << (quint16)0;
	out << fortune;
	out.device()->seek(0);
	out << (quint16)(block.size()) - sizeof(quint16);

	tcpSocket->write(block);
	tcpSocket->disconnectFromHost();
	tcoSocket->waitForDisconnect();
}
//blockClient
void BlockingClient::requestNewFortune()
{
    thread.requestNewFortune(IP,Port);
} 

//在主线程中运行
void FortuneThread::requestNewFortune(const QString &hostName, quint16 port)
{
    QMutexLocker locker(&mutex);//锁住互斥量
    this->hostName = hostName;
    this->port = port;
    if (!isRunning())//第一次回调到启动子线程
        start();
    else
        cond.wakeOne();//之后设置为IP和port后就直接唤醒线程
}
//在子线程中运行
void FortuneThread::run()
{
    mutex.lock();
    QString serverName = hostName;
    quint16 serverPort = port;
    mutex.unlock();
    while (!quit) {
        const int Timeout = 5 * 1000;

        QTcpSocket socket;
        socket.connectToHost(serverName, serverPort);

        if (!socket.waitForConnected(Timeout)) {
            emit error(socket.error(), socket.errorString());
            return;
        }

        while (socket.bytesAvailable() < (int)sizeof(quint16)) {
            if (!socket.waitForReadyRead(Timeout)) {
                emit error(socket.error(), socket.errorString());
                return;
            }
        }

        quint16 blockSize;
        QDataStream in(&socket);
        in.setVersion(QDataStream::Qt_4_0);
        in >> blockSize;

        while (socket.bytesAvailable() < blockSize) {
            if (!socket.waitForReadyRead(Timeout)) {
                emit error(socket.error(), socket.errorString());
                return;
            }
        }
        mutex.lock();//加锁
        QString fortune;
        in >> fortune;
        emit newFortune(fortune);


        cond.wait(&mutex);//释放锁并且等待唤醒即等待主线程设置hostName,port
        serverName = hostName;
        serverPort = port;
        mutex.unlock();
    }
}
//两个线程中都会访问serverName,serverPort所以需要加锁,同时使用QWaitCondition唤醒等待的线程.简单的线程同步




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值