1. 程式人生 > >Qt淺談之十六:TCP和UDP(之一)

Qt淺談之十六:TCP和UDP(之一)

一、簡介

       Qt使用QtNetwork模組來進行網路程式設計,提供了一層統一的套接字抽象用於編寫不同層次的網路程式,避免了應用套接字進行網路編的繁瑣(因有時需引用底層作業系統的相關資料結構)。有較底層次的類如QTcpSocket、QTcpServer和QUdpSocket等來表示低層的網路概念;還有高層次的類如QNetworkRequest、QNetworkReply和QNetworkAccessManager使用相同的協議來執行網路操作;也提供了QNetworkConfiguration、QNetworkConfigurationManager和QNetworkSession等類來實現負載管理。

二、分析圖

(1)網路通訊協議

(2)TCP和UDP客戶端和伺服器的建立流程


三、TCP

       TCP是一種可靠的、面向連線、面向資料流的傳輸協議,多數高層網路協議都使用TCP協議,包括HTTP和FTP,TCP協議非常適合資料的連續傳輸。QTcpSocket類為TCP提供了一個介面,繼承自QAbstractSocket。TCP程式設計一般分為客戶端和伺服器端,即C/S(Client/Server)模型。

1、執行圖

伺服器監聽8010埠,客戶端連線。伺服器端接收到連線及其資訊的同時將資訊傳送給所有的客戶端,客戶端顯示獲得的資訊。

2、程式碼

(1)TCP伺服器端(完整程式碼在csdn上)

#include "tcpserver.h"
#include <QApplication>


int main( int argc, char **argv )
{
   	
    QApplication a( argc, argv );
    QTranslator translator(0);
    translator.load("tcpserver_zh",".");
    a.installTranslator(&translator);    
    
    TcpServer *tcpserver = new TcpServer();
    tcpserver->show();
    return a.exec();
}
#include "tcpserver.h"

TcpServer::TcpServer( QWidget *parent, Qt::WindowFlags  f )
    : QDialog( parent, f )
{  
    QFont font("ZYSong18030",12);
    setFont(font);
    	
    setWindowTitle(tr("TCP Server"));

    QVBoxLayout *vbMain = new QVBoxLayout( this );

 	ListWidgetContent = new QListWidget( this); 
    vbMain->addWidget( ListWidgetContent );    

    QHBoxLayout *hb = new QHBoxLayout( );
    
    LabelPort = new QLabel( this );
    LabelPort->setText(tr("Port:"));
    hb->addWidget( LabelPort );
   
    LineEditPort = new QLineEdit(this);
    hb->addWidget( LineEditPort );
    
    vbMain->addLayout(hb);
           
    PushButtonCreate = new QPushButton( this);
    PushButtonCreate->setText( tr( "Create" ) );  
    vbMain->addWidget( PushButtonCreate );

    connect(PushButtonCreate,SIGNAL(clicked()),this,SLOT(slotCreateServer()));	

	port = 8010; 
	LineEditPort->setText(QString::number(port)); 

}

TcpServer::~TcpServer()
{
}

void TcpServer::slotCreateServer()                     
{         
	server = new Server(this,port); 
	connect(server,SIGNAL(updateServer(QString,int)),this,SLOT(updateServer(QString,int)));

	PushButtonCreate->setEnabled(false);
}

void TcpServer::updateServer(QString msg,int length)
{
	ListWidgetContent->addItem (msg.left(length) );
}
#include <QtNetwork>


#include "server.h"

Server::Server(QObject *parent,int port)
    : QTcpServer(parent)
{
	listen(QHostAddress::Any,port);
}

void Server::incomingConnection(int socketDescriptor)
{
	TcpClientSocket *tcpClientSocket = new TcpClientSocket(this);
	connect(tcpClientSocket,SIGNAL(updateClients(QString,int)),this,SLOT(updateClients(QString,int)));
	connect(tcpClientSocket,SIGNAL(disconnected(int)),this,SLOT(slotDisconnected(int)));
	
    tcpClientSocket->setSocketDescriptor(socketDescriptor);

	tcpClientSocketList.append(tcpClientSocket);

}

void Server::updateClients(QString msg,int length)
{
	emit updateServer(msg,length);

	for(int i=0;i<tcpClientSocketList.count();i++)
	{
		QTcpSocket *item=tcpClientSocketList.at(i);
    	if(item->write(msg.toLatin1(), length)!=length)
    	{
    		continue ;
    	}	
	}
}

void Server::slotDisconnected(int descriptor)
{
	for(int i=0;i<tcpClientSocketList.count();i++)
	{
		QTcpSocket *item=tcpClientSocketList.at(i);
    	if(item->socketDescriptor ()==descriptor)
    	{
    		tcpClientSocketList.removeAt(i);
    		return;
    	}	
	}	
	return;
}
#include "tcpserver.h"

TcpClientSocket::TcpClientSocket( QObject *parent)
{  
    connect(this, SIGNAL(readyRead()),this, SLOT(dataReceived()));	 
    connect(this, SIGNAL(disconnected()),this, SLOT(slotDisconnected()));
}

TcpClientSocket::~TcpClientSocket()
{
}
                                                                             
void TcpClientSocket::dataReceived()
{
    while (bytesAvailable()>0) 
    {
 		char buf[1024];
 		int length=bytesAvailable();
 		read(buf, length);

		QString msg=buf;
		
		emit updateClients(msg,length);
    }	
}

void TcpClientSocket::slotDisconnected()
{
	emit disconnected(this->socketDescriptor ());
}
(2)TCP客戶端
#include "tcpclient.h"

TcpClient::TcpClient( QWidget *parent, Qt::WindowFlags  f )
    : QDialog( parent, f )
{  
    QFont font("ZYSong18030",12, QFont::Normal);
    setFont(font);
    	
    setWindowTitle(tr("TCP Client"));

    QVBoxLayout *vbMain = new QVBoxLayout( this );
    
 	ListWidgetContent = new QListWidget( this); 
    vbMain->addWidget( ListWidgetContent ); 
    
	QHBoxLayout *hb1 = new QHBoxLayout( );
	  
    LineEditSend = new QLineEdit(this);
    hb1->addWidget( LineEditSend );	

    PushButtonSend = new QPushButton( this);
    PushButtonSend->setText( tr( "Send" ) );  
    hb1->addWidget( PushButtonSend );

	vbMain->addLayout( hb1 );

    QHBoxLayout *hb2 = new QHBoxLayout( );

    LabelUser = new QLabel( this );
    LabelUser->setText(tr("User Name:"));
    hb2->addWidget( LabelUser );
   
    LineEditUser = new QLineEdit(this);
    hb2->addWidget( LineEditUser );

	QHBoxLayout *hb3 = new QHBoxLayout( );

    LabelServerIP = new QLabel( this );
    LabelServerIP->setText(tr("Server:"));
    hb3->addWidget( LabelServerIP );
   
    LineEditServerIP = new QLineEdit(this);
    hb3->addWidget( LineEditServerIP );
    
    QHBoxLayout *hb4 = new QHBoxLayout( );
    
    LabelPort = new QLabel( this );
    LabelPort->setText(tr("Port:"));
    hb4->addWidget( LabelPort );
   
    LineEditPort = new QLineEdit(this);
    hb4->addWidget( LineEditPort );
    
    vbMain->addLayout(hb2);
    vbMain->addLayout(hb3);
    vbMain->addLayout(hb4);
           
    PushButtonEnter = new QPushButton( this);
    PushButtonEnter->setText( tr( "Enter" ) );  
    vbMain->addWidget( PushButtonEnter );

    connect(PushButtonEnter,SIGNAL(clicked()),this,SLOT(slotEnter()));
    connect(PushButtonSend,SIGNAL(clicked()),this,SLOT(slotSend()));	

	serverIP = new QHostAddress();
	port = 8010; 
	LineEditPort->setText(QString::number(port)); 
	
	status=false;
	
	PushButtonSend->setEnabled( false ); 
}

TcpClient::~TcpClient()
{
}
                                                                             
void TcpClient::slotEnter()                     
{         
	if(!status)
	{
		QString ip=LineEditServerIP->text();
		if(!serverIP->setAddress(ip))
		{
			QMessageBox::information(this,tr("error"),tr("server ip address error!"));
			return;
		}
		if(LineEditUser->text()=="")
		{
			QMessageBox::information(this,tr("error"),tr("User name error!"));
			return ;
		}	
		userName=LineEditUser->text();
		tcpSocket = new QTcpSocket(this);
		connect(tcpSocket,SIGNAL(connected()),this,SLOT(slotConnected()));
		connect(tcpSocket,SIGNAL(disconnected()),this,SLOT(slotDisconnected()));
		connect(tcpSocket, SIGNAL(readyRead()),this, SLOT(dataReceived()));
		
		tcpSocket->connectToHost ( *serverIP, port);
		
		status=true;
	}
	else
	{
		int length = 0; 
		
		QString msg=userName+tr(":Leave Chat Room");
	    if((length=tcpSocket->write(msg.toLatin1(),msg.length()))!=msg.length())
	    {
	    	return ;
	    }			
		tcpSocket->disconnectFromHost();
		
		status=false;
	}
	
}

void TcpClient::slotConnected()                     
{
	int length = 0; 
	PushButtonSend->setEnabled( true );
	PushButtonEnter->setText(tr("Leave"));
	QString msg=userName+tr(":Enter Chat Room");
    if((length=tcpSocket->write(msg.toLatin1(),msg.length()))!=msg.length())
    {
    	return ;
    }	
}

void TcpClient::slotDisconnected()                     
{
	PushButtonSend->setEnabled( false );
	PushButtonEnter->setText(tr("Enter"));
}

void TcpClient::slotSend()                     
{         
	if(LineEditSend->text()=="")
	{
		return ;
	}
	
	QString msg=userName+":"+LineEditSend->text();
	
	tcpSocket->write(msg.toLatin1(),msg.length());
	LineEditSend->clear();
}

void TcpClient::dataReceived()
{
    while (tcpSocket->bytesAvailable()>0) 
    {
 		QByteArray datagram;
 		datagram.resize(tcpSocket->bytesAvailable());
 		QHostAddress sender;


 		tcpSocket->read(datagram.data(), datagram.size());

		QString msg=datagram.data();
	
		ListWidgetContent->addItem (msg.left(datagram.size()));
    }

}

3、其他TCP伺服器和客戶端程式碼

(1)TCP伺服器——基本模式

#ifndef SERVER_H
#define SERVER_H

#include <QObject>
#include <QTcpServer>

class server : public QObject
{
    Q_OBJECT
public:
    explicit server(QObject *parent = 0);

private:
    QTcpServer *tcpServer;
    QTcpSocket *clientConnection;

signals:

public slots:
    void sendMessage();
    void on_Ready_Read();
};

#endif // SERVER_H
#include <QTcpSocket>
#include "server.h"


server::server(QObject *parent) :
    QObject(parent)
{
    tcpServer = new QTcpServer(this);
    // 使用了IPv4的本地主機地址,等價於QHostAddress("127.0.0.1")
    if (!tcpServer->listen(QHostAddress::Any, 59769)) {
        qDebug() << tcpServer->errorString();
    }
    connect(tcpServer, SIGNAL(newConnection()), this, SLOT(sendMessage()));
}

void server::sendMessage()
{
    clientConnection = tcpServer->nextPendingConnection();
    clientConnection->write("hello client\r\n");
    clientConnection->flush();   
    clientConnection->waitForBytesWritten(3000);
    connect(clientConnection, SIGNAL(readyRead()), this, SLOT(on_Ready_Read())); 
    connect(clientConnection, SIGNAL(disconnected()), clientConnection, SLOT(deleteLater()));
}
void server::on_Ready_Read()
{ 
    QByteArray block = clientConnection->readAll(); 
    qDebug() << "server = " << block; 
    QString commandXml = QString("<event>" " <object>USER</object>" " <action>LOGINSUCCESS</action>" " <data>" " </data>" "</event>"); 
    QByteArray data = commandXml.toUtf8(); 
    QString str = QString("%1:%2,").arg(data.size()).arg(QString::fromUtf8(data)); 
    clientConnection->write(str.toUtf8()); clientConnection->disconnectFromHost();
}

(2)TCP伺服器——執行緒處理

#ifndef MYSERVER_H
#define MYSERVER_H

#include <QTcpServer>
#include <QDebug>
#include "mythread.h"

class MyServer : public QTcpServer
{
    Q_OBJECT
public:
    explicit MyServer(QObject *parent = 0);
    void StartServer();

protected:
    void incomingConnection(int  socketDescriptor);
signals:

public slots:

};

#endif // MYSERVER_H
#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QThread>
#include <QTcpSocket>
#include <QDebug>

class MyThread : public QThread
{
    Q_OBJECT
public:
    explicit MyThread(int ID, QObject *parent = 0);
    void run();

signals:
    void error(QTcpSocket::SocketError socketerror);

public slots:
    void readyRead();
    void disconnected();

private:
    QTcpSocket *socket;
    int socketDescriptor;

};
#endif // MYTHREAD_H
#include <QCoreApplication>
#include "myserver.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    MyServer server;
    server.StartServer();

    return a.exec();
}
#include "myserver.h"

MyServer::MyServer(QObject *parent) :
    QTcpServer(parent)
{
}

void MyServer::StartServer()
{
    if (!listen(QHostAddress::Any, 1234)) {
        qDebug() << "Could not start server";
    }
    else {
        qDebug() << "Listening...";
    }
}

void MyServer::incomingConnection(int socketDescriptor)
{
    qDebug() << socketDescriptor << ":connecting...";
    MyThread *thread = new MyThread(socketDescriptor, this);
    connect(thread, SIGNAL(finished()), this, SLOT(deleteLater()));
    thread->start();
}
#include "mythread.h"

MyThread::MyThread(int ID, QObject *parent) :
    QThread(parent)
{
    socketDescriptor = ID;
}


void MyThread::run()
{
   //thread starts here
    qDebug() << socketDescriptor << ":starting thread";
    socket = new QTcpSocket;
    if (!socket->setSocketDescriptor(socketDescriptor)) {
        emit error(socket->error());
        return;
    }
    connect(socket, SIGNAL(readyRead()), this, SLOT(readyRead()), Qt::DirectConnection);
    connect(socket, SIGNAL(disconnected()), this, SLOT(disconnected()), Qt::DirectConnection);
    qDebug() << socketDescriptor << ":client connected!";
    exec();
}
void MyThread::readyRead()
{
    QByteArray Data = socket->readAll();
    qDebug() << socketDescriptor << ":Data in:" << Data;
    socket->write(Data);
}
void MyThread::disconnected()
{
    qDebug() << socketDescriptor << ":Disconnected!";
    socket->deleteLater();
    exit(0);
}

(3)TCP伺服器——執行緒池

#ifndef MYSERVER_H
#define MYSERVER_H

#include <QTcpServer>
#include <QThreadPool>
#include <QDebug>
#include "myrunnable.h"

class MyServer : public QTcpServer
{
    Q_OBJECT
public:
    explicit MyServer(QObject *parent = 0);
    void StartServer();

protected:
    void incomingConnection(int handle);

private:
    QThreadPool *pool;

signals:

public slots:

};

#endif // MYSERVER_H
#ifndef MYRUNNABLE_H
#define MYRUNNABLE_H

#include <QRunnable>
#include <QTcpSocket>
#include <QDebug>

class MyRunnable : public QRunnable
{
public:
    MyRunnable();
    int SocketDescriptor;

protected:
    void run();
};

#endif // MYRUNNABLE_H
#include "myserver.h"

MyServer::MyServer(QObject *parent) :
    QTcpServer(parent)
{
    pool = new QThreadPool(this);
    pool->setMaxThreadCount(5);
}
void MyServer::StartServer()
{
    if (listen(QHostAddress::Any, 1234)) {
        qDebug() << "Server started!";
    }
    else {
        qDebug() << "Server did not start!";
    }
}
void MyServer::incomingConnection(int handle)
{
    MyRunnable *task = new MyRunnable();
    task->setAutoDelete(true);
    task->SocketDescriptor = handle;
    pool->start(task);
}
#include "myrunnable.h"

MyRunnable::MyRunnable()
{
}

void MyRunnable::run()
{
    if (!SocketDescriptor)  return;

    QTcpSocket socket;
    socket.setSocketDescriptor(SocketDescriptor);

    socket.write("hello world\r\n");
    socket.flush();
    socket.waitForBytesWritten();
    socket.close();
}
#include <QCoreApplication>
#include "myserver.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    MyServer Server;
    Server.StartServer();
    return a.exec();
}

(4)TCP伺服器——高階非同步方式並使用執行緒池

儘可能理解這種方式,效率相對比較客觀。執行結果:


#ifndef MYSERVER_H
#define MYSERVER_H

#include <QTcpServer>
#include <QTcpSocket>
#include <QAbstractSocket>
#include "myclient.h"

class MyServer : public QTcpServer
{
    Q_OBJECT
public:
    explicit MyServer(QObject *parent = 0);
    void StartServer();

protected:
    void incomingConnection(int handle);

signals:

public slots:

};

#endif // MYSERVER_H
#ifndef MYCLIENT_H
#define MYCLIENT_H

#include <QObject>
#include <QTcpSocket>
#include <QDebug>
#include <QThreadPool>
#include "mytask.h"

class MyClient : public QObject
{
    Q_OBJECT
public:
    explicit MyClient(QObject *parent = 0);
    void SetSocket(int Descriptor);

signals:

public slots:
    void connected();
    void disconnected();
    void readyRead();
    void TaskResult(int Number);

private:
    QTcpSocket *socket;
};

#endif // MYCLIENT_H
#ifndef MYTASK_H
#define MYTASK_H

#include <QRunnable>
#include <QDebug>

class MyTask : public QObject, public QRunnable
{
    Q_OBJECT
public:
    MyTask();

signals:
    void Result(int Number);

protected:
    void run();
};

#endif // MYTASK_H
#include "myserver.h"

MyServer::MyServer(QObject *parent) :
    QTcpServer(parent)
{
}
void MyServer::StartServer()
{
    if (listen(QHostAddress::Any, 1234)) {
        qDebug() << "Server started!";
    }
    else {
        qDebug() << "Server did not start!";
    }
}

void MyServer::incomingConnection(int handle)
{
    MyClient *client = new MyClient(this);
    client->SetSocket(handle);
}
#include "myclient.h"

MyClient::MyClient(QObject *parent) :
    QObject(parent)
{
    QThreadPool::globalInstance()->setMaxThreadCount(15);
}

void MyClient::SetSocket(int Descriptor)
{
    socket = new QTcpSocket(this);

    connect(socket, SIGNAL(connected()), this, SLOT(connected()));
    connect(socket, SIGNAL(disconnected()), this, SLOT(disconnected()));
    connect(socket, SIGNAL(readyRead()), this, SLOT(readyRead()));

    socket->setSocketDescriptor(Descriptor);
    qDebug() << "client connected";
}

void MyClient::connected()
{
    qDebug() << "client connected event";
}

void MyClient::disconnected()
{
    qDebug() << "client disconnected";
}

void MyClient::readyRead()
{
    qDebug() << socket->readAll();
    //Time Consumer
    MyTask *mytask = new MyTask;
    mytask->setAutoDelete(true);
    connect(mytask, SIGNAL(Result(int)), this, SLOT(TaskResult(int)));
    QThreadPool::globalInstance()->start(mytask);
}

void MyClient::TaskResult(int Number)
{
    //right here
    QByteArray Buffer;
    Buffer.append("\r\nTask Result = ");
    Buffer.append(QString::number(Number));

    socket->write(Buffer);
}
#include "mytask.h"

MyTask::MyTask()
{
}

void MyTask::run()
{
    //time consumer
    qDebug() << "Task Start";
    int iNumber = 0;
    for (int i = 0; i < 100; i++) {
        iNumber += i;
    }
    qDebug() << "Task Done";

    emit Result(iNumber);
}

#include <QCoreApplication>
#include "myserver.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    MyServer Server;
    Server.StartServer();

    return a.exec();
}

(5)TCP客戶端——非同步等待

#ifndef SOCKETTEST_H
#define SOCKETTEST_H

#include <QObject>
#include <QTcpSocket>

class SocketTest : public QObject
{
    Q_OBJECT
public:
    explicit SocketTest(QObject *parent = 0);
    void Connect();

private:
    QTcpSocket *socket;

signals:

public slots:

};

#endif // SOCKETTEST_H
#include "sockettest.h"

SocketTest::SocketTest(QObject *parent) :
    QObject(parent)
{
}

void SocketTest::Connect()
{
    socket = new QTcpSocket(this);
    socket->connectToHost("203.116.165.138", 80);

    if (socket->waitForConnected(3000)) {
        qDebug() << "connected!";

        //send
        socket->write("hello server\r\n\r\n\r\n");
        socket->waitForBytesWritten(1000);
        socket->waitForReadyRead(3000);
        qDebug() << "Reading:" << socket->bytesAvailable();

        qDebug() << socket->readAll();

        socket->close();
    }
    else {
        qDebug() << "not connected!";
    }
}
(6)TCP客戶端——訊號槽連線
#include <QCoreApplication>
#include <QTextCodec>
#include "sockettest.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QTextCodec *codec = QTextCodec::codecForName("System");
    QTextCodec::setCodecForLocale(codec);
    QTextCodec::setCodecForCStrings(codec);
    QTextCodec::setCodecForTr(codec);
    SocketTest mTest;
    mTest.Test();
    return a.exec();
}
#ifndef SOCKETTEST_H
#define SOCKETTEST_H

#include <QObject>
#include <QDebug>
#include <QTcpSocket>
#include <QAbstractSocket>

class SocketTest : public QObject
{
    Q_OBJECT
public:
    explicit SocketTest(QObject *parent = 0);
    void Test();

private:
    QTcpSocket *socket;

signals:

public slots:
    void connected();
    void disconnected();
    void bytesWritten(qint64 bytes);
    void readyRead();
    void displayError(QAbstractSocket::SocketError);
};

#endif // SOCKETTEST_H
#include "sockettest.h"

SocketTest::SocketTest(QObject *parent) :
    QObject(parent)
{
}

void SocketTest::Test()
{
    socket = new QTcpSocket(this);
    connect(socket, SIGNAL(connected()), this, SLOT(connected()));
    connect(socket, SIGNAL(disconnected()), this, SLOT(disconnected()));
    connect(socket, SIGNAL(bytesWritten(qint64)), this, SLOT(bytesWritten(qint64)));
    connect(socket, SIGNAL(readyRead()), this, SLOT(readyRead()));
    connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(displayError(QAbstractSocket::SocketError)));

    qDebug() << "connecting...";
    socket->connectToHost("203.116.165.139", 80);

    if (!socket->waitForConnected(1000)) {
        qDebug() << "Error:" << socket->errorString();
    }
}

void SocketTest::connected()
{
    qDebug() << "connected!";
    socket->write("HEAD / HTTP/1.0\r\n\r\n\r\n");
}

void SocketTest::disconnected()
{
    qDebug() << "disconnected!";
    socket->disconnectFromHost();
}

void SocketTest::bytesWritten(qint64 bytes)
{
    qDebug() << "we wrote:" << bytes;
}

void SocketTest::readyRead()
{
    qDebug() << "reading...";
    qDebug() << socket->readAll();
}
void SocketTest::displayError(QAbstractSocket::SocketError)
{
    qDebug() << socket->errorString();
}

四、TCP總結

      以上各節程式碼均可單獨採用實現伺服器和客戶端的執行,可以選擇設計自己的模式。

      TCP伺服器的效率還取決於使用者設計的模式,好的設計能可以容錯、快速處理大批量的任務、減小系統的負荷、節約記憶體等好處,因此不停的總結TCP的程式碼,並在其基礎上提高是很有必要的。

【待續UDP】