转载自 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唤醒等待的线程.简单的线程同步