QTcp

TCP (一)

 

版权声明

该文章原创于Qter开源社区(www.qter.org),作者yafeilinux,转载请注明出处!

导语


 

TCPTransmissionControl Protocol,传输控制协议。与UDP不同,它是面向连接和数据流的可靠传输协议。也就是说,它能使一台计算机上的数据无差错的发往网络上的其他计算机,所以当要传输大量数据时,我们选用TCP协议。

       TCP协议的程序使用的是客户端/服务器(C/S)模式,在Qt中提供了QTcpSocket类来编写客户端程序,使用QTcpServer类编写服务器端程序。我们在服务器端进行端口的**,一旦发现客户端的连接请求,就会发出newConnection()信号,可以关联这个信号到我们自己的槽进行数据的发送。而在客户端,一旦有数据到来就会发出readyRead()信号,可以关联此信号进行数据的接收。其实,在程序中最难理解的地方就是程序的发送和接收了,为了让大家更好的理解,我们在这一节只是讲述一个传输简单的字符串的例子,在下一节再进行扩展,实现任意文件的传输。




环境:Windows Xp + Qt 4.8.5+Qt Creator2.8.0




目录


 

一、服务器端

二、客户端





正文


 

一、服务器端

 

在服务器端的程序中,我们**本地主机的一个端口,这里使用6666,然后关联newConnection()信号与自己写的sendMessage()槽。就是说一旦有客户端的连接请求,就会执行sendMessage()函数,在这个函数里我们发送一个简单的字符串。



1.新建QtGui应用

项目名为tcpServer,基类选择QWidget,类名为Widget。完成后打开项目文件tcpServer.pro并添加一行代码:QT += network ,然后保存该文件。



2.widget.ui的设计区添加一个Label,更改其显示文本为“等待连接”,然后更改其objectNamestatusLabel,用于显示一些状态信息。



3.widget.h文件中做以下更改。

添加头文件:#include <QtNetWork>

添加private对象:QTcpServer *tcpServer;

添加私有槽:

private slots:

voidsendMessage();



4.在widget.cpp文件中进行更改。

在其构造函数中添加代码:

tcpServer = new QTcpServer(this);

if(!tcpServer->listen(QHostAddress::LocalHost,6666))

{  //**本地主机的6666端口,如果出错就输出错误信息,并关闭

    qDebug() <<tcpServer->errorString();

    close();

}

//连接信号和相应槽函数

connect(tcpServer,SIGNAL(newConnection()),this,SLOT(sendMessage()));

我们在构造函数中使用tcpServerlisten()函数进行**,然后关联了newConnection()和我们自己的sendMessage()函数。

下面我们实现sendMessage()函数。

 

void Widget::sendMessage()

{

    //用于暂存我们要发送的数据

    QByteArray block;

   

    //使用数据流写入数据

    QDataStream out(&block,QIODevice::WriteOnly);

   

    //设置数据流的版本,客户端和服务器端使用的版本要相同

    out.setVersion(QDataStream::Qt_4_6);

   

    out<<(quint16) 0;

    out<<tr("hello Tcp!!!");

    out.device()->seek(0);

    out<<(quint16) (block.size() - sizeof(quint16));

   

    //我们获取已经建立的连接的子套接字

    QTcpSocket *clientConnection = tcpServer->nextPendingConnection();

   

    connect(clientConnection,SIGNAL(disconnected()),clientConnection,

          SLOT(deleteLater()));

    clientConnection->write(block);

    clientConnection->disconnectFromHost();

   

    //发送数据成功后,显示提示

    ui->statusLabel->setText("send message successful!!!");

}

这个是数据发送函数,我们主要介绍两点:

 

(1)为了保证在客户端能接收到完整的文件,我们都在数据流的最开始写入完整文件的大小信息,这样客户端就可以根据大小信息来判断是否接受到了完整的文件。而在服务器端,在发送数据时就要首先发送实际文件的大小信息,但是,文件的大小一开始是无法预知的,所以这里先使用了out<<(quint16) 0;在block的开始添加了一个quint16大小的空间,也就是两字节的空间,它用于后面放置文件的大小信息。然后out<<tr("hello Tcp!!!");输入实际的文件,这里是字符串。当文件输入完成后我们再使用out.device()->seek(0);返回到block的开始,加入实际的文件大小信息,也就是后面的代码,它是实际文件的大小:out<<(quint16) (block.size() -sizeof(quint16));

 

2)在服务器端我们可以使用tcpServernextPendingConnection()函数来获取已经建立的连接的Tcp套接字,使用它来完成数据的发送和其它操作。比如这里,我们关联了disconnected()信号和deleteLater()槽,然后我们发送数据

clientConnection->write(block);

然后是clientConnection->disconnectFromHost();

它表示当发送完成时就会断开连接,这时就会发出disconnected()信号,而最后调用deleteLater()函数保证在关闭连接后删除该套接字clientConnection

 

5.这样服务器的程序就完成了,可以先运行一下程序。





二、客户端

我们在客户端程序中向服务器发送连接请求,当连接成功时接收服务器发送的数据。



1.新建QtGui应用,

项目名tcpClient,基类选择QWidget,类名为Widget。完成后打开项目文件tcpClient.pro并添加一行代码:QT += network ,然后保存该文件。



2.我们在widget.ui中添加几个标签Label和两个Line Edit以及一个按钮Push Button。设计效果如下图所示。



其中“主机”后的LineEditobjectNamehostLineEdit,“端口号”后的为portLineEdit

收到的信息”标签的objectNamemessageLabel 



3.widget.h文件中做更改。

添加头文件:#include <QtNetwork>

添加private变量:

QTcpSocket *tcpSocket;

QString message;  //存放从服务器接收到的字符串

quint16blockSize; //存放文件的大小信息

添加私有槽:

private slots:

    void newConnect(); //连接服务器

    void readMessage();  //接收数据

voiddisplayError(QAbstractSocket::SocketError);  //显示错误



4.widget.cpp文件中做更改。

 

1)在构造函数中添加代码:

tcpSocket = new QTcpSocket(this);

connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(readMessage()));

connect(tcpSocket,SIGNAL(error(QAbstractSocket::SocketError)),

         this,SLOT(displayError(QAbstractSocket::SocketError)));

这里关联了tcpSocket的两个信号,当有数据到来时发出readyRead()信号,我们执行读取数据的readMessage()函数。当出现错误时发出error()信号,我们执行displayError()槽函数。

 

(2)实现newConnect()函数。

void Widget::newConnect()

{

    blockSize = 0; //初始化其为0

tcpSocket->abort();//取消已有的连接

 

//连接到主机,这里从界面获取主机地址和端口号

    tcpSocket->connectToHost(ui->hostLineEdit->text(),

                           ui->portLineEdit->text().toInt());

}

这个函数实现了连接到服务器,下面会在“连接”按钮的单击事件槽函数中调用这个函数。

 

(3)实现readMessage()函数。

void Widget::readMessage()

{

    QDataStream in(tcpSocket);

    in.setVersion(QDataStream::Qt_4_6);

    //设置数据流版本,这里要和服务器端相同

    if(blockSize==0) //如果是刚开始接收数据

    {

       //判断接收的数据是否有两字节,也就是文件的大小信息

       //如果有则保存到blockSize变量中,没有则返回,继续接收数据

       if(tcpSocket->bytesAvailable()< (int)sizeof(quint16)) return;

       in>>blockSize;

    }

    if(tcpSocket->bytesAvailable()<blockSize) return;

   //如果没有得到全部的数据,则返回,继续接收数据

    in>> message;

    //将接收到的数据存放到变量中

    ui->messageLabel->setText(message);

    //显示接收到的数据

}

这个函数实现了数据的接收,它与服务器端的发送函数相对应。首先我们要获取文件的大小信息,然后根据文件的大小来判断是否接收到了完整的文件。

 

(4)实现displayError()函数。

void Widget::displayError(QAbstractSocket::SocketError)

{

    qDebug() <<tcpSocket->errorString(); //输出错误信息

}

这里简单的实现了错误信息的输出。

 

(5)我们在widget.ui中进入“连接”按钮的单击事件槽函数,然后更改如下。

void Widget::on_pushButton_clicked()//连接按钮

{

    newConnect(); //请求连接

}

这里直接调用了newConnect()函数。



5.我们运行程序,同时运行服务器程序,然后在“主机”后填入“localhost,在“端口号”后填入“6666,点击“连接”按钮,效果如下。

 

 

可以看到我们正确地接收到了数据。因为服务器端和客户端是在同一台机子上运行的,所以我这里填写了“主机”为“localhost,如果你在不同的机子上运行,需要在“主机”后填写其正确的IP地址。






QTcpSocket详细描述:
QTcpSocket
类提供一个TCP套接字
TCP
是一个面向连接,可靠的的通信协议,非常适合于连续不断的数据传递
QTcpSocket
QAbstractSocket类非常方便的一个子类,让你创建一个TCP连接和数据流交流。
注意:TCP套接字不能以QIODevice::Unbuffered模式来打开

Symbian系统上,程序想用这个类的话必须拥有NetworkServices平台支持,如果客户机缺少这个能力,将会导致"恐惧"(不明白为什么这么翻译)

成员函数:
QTcpSocket::QTcpSocket ( QObject * parent = 0 )
UnconnectedState态创建一个QTcpSocket对象
QTcpSocket::~QTcpSocket () [virtual]‘
析构函数,销毁对象

QTcpServer

提供一个TCP基础服务类继承自QObject
这个类用来接收到来的TCP连接,可以指定TCP端口或者用QTcpServer自己挑选一个端口,可以监听一个指定的地址或者所有的机器地址。
调用listen()监听所有的连接,每当一个新的客户端连接到服务端就会发射信号newConnection()
调用nextPendingConnection()来接受待处理的连接。返回一个连接的QTcpSocket(),我们可以用这个返回的套接字和客户端进行连接

如果有错误,serverError()返回错误的类型。调用errorString()来把错误打印出来
当监听连接时候,可以调用serverAddress()serverPort()来返回服务端的地址和端口。
调用close()来关闭套接字,停止对连接的监听。
尽管QTcpServer大多时候设计使用事件循环,也可以不适用事件循环,可以使用waitForNewConnection()会一直阻塞,知道一个连接可以用或者超时。
Symbian平台上,同样的需要NetworkServices平台支持。

成员函数
------------------------------
void close() 关闭服务,然后服务器讲不再监听任何连接
QString errorString()const错误时候返回错误的字符串
------------------------------
bool hasPendingConnections()const如果服务端有一个待处理的连接,就返回真,否则返回假
QTcpSocket* nextPendingConnection()
返回一个套接字来处理一个连接,这个套接字作为服务端的一个子对象,意味着当QTcpServer对象销毁时候,这个套接字也自动删除,当使用完后明确的删除这个套接字也好,这样可以避免内存浪费。当没有可处理的连接时候,这个函数返回0
注意:返回的套接字不能再其他线程中使用。如果想在其他线程中使用,那么你需要重载incomingConnection()
-------------------------------------
void incomingConnection(int socketDescriptor)[virtual protected]
QTcpServer有一个新连接时候调用这个虚函数,socketDescriptor参数是新连接的套接字描述符
这个函数新建一个QTcpSocket套接字,建立套接字描述符,然后存储套接字在一个整形的待连接链表中。最后发射信号newConnection()
重写这个函数,当一个新连接时候,来调整这个函数的行为。
当服务端使用QNetworkProxy服务器代理时候,使用一般的套接字函数套接字描述符可能不可以用,这时候应该使用QTcpSocket::setSocketDescriptor()来设置描述符
--------------------------------------
bool isListening()const
当服务端正在监听连接时候返回真,否则返回假
bool listen( const QHostAddress & address = QHostAddress::Any,quint16 port = 0 )
告诉服务端监听所有来自地址为address端口为Port的连接,如果Port0,那么会自动选择,如果addressQHostAddress::Any,那么服务端监听所有连接,成功返回1,否则返回0
int maxPendingConnections()const
返回最大允许连接数。默认是30
void setMaxPendingConnections(int numConnections)
设定待处理的连接最大数目为numConnections,当超过了最大连接数后,客户端仍旧可以连接服务端,但是服务端不在接受连接,操作系统会把这些链接保存在一个队列中。
-----------------------------------------
QNetworkProxy proxy()const
返回这个套接字的网络代理层。
void setProxy(const QNetworkProxy & networkProxy)
设置这个套接字的网络代理层,进制使用代理时候,使用QNetworkProxy::NoProxy类型,例如server->setProxy(QNetworkProxy::NoProxy);

quint16serverPort()const serverAddress()
当服务端正在监听时候,返回服务端的端口和地址

-----------------------------------------
bool waitForNewConnection(int msec=0,bool *timedOut=0)
最大等待msec毫秒或者等待一个新连接可用。如果一个连接可用,返回真,否则返回假。如果msec不等于0,那么超时将会被调用
这是一个阻塞函数,最好用在单线程应用程序中。由于会阻塞。当没有事件循环可用时候,这个函数很好用。
非阻塞的选项和信号newConnection()关联起来。
msec-1时候,函数将没有超时。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值